Skip to content

init ble_advertising_scanning_test #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/treadmill-ci-ble-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Licensed under the Apache License, Version 2.0 or the MIT License.
# SPDX-License-Identifier: Apache-2.0 OR MIT
# Copyright Tock Contributors 2024.
# This workflow contains Treadmill-based hardware CI for nightly BLE testing.
#
# Treadmill is a distributed hardware testbed developed within the Tock OS
# project. For more information on Treadmill, have a look at its documentation
# [1] or repository [2].
#
# This workflow specifically targets BLE advertising and scanning tests on multiple
# boards attached to a specific supervisor.
#
# [1]: https://book.treadmill.ci/
# [2]: https://github.com/treadmill-tb/treadmill
# [3]: https://book.treadmill.ci/user-guide/github-actions-integration.html
# TEST
name: treadmill-ci-ble-test
env:
TERM: xterm # Makes tput work in actions output
# Controls when the action will run.
on:
# Manual trigger
workflow_dispatch:
inputs:
tock-kernel-ref:
description: 'Ref (revision/branch/tag) of the upstream Tock repo to test'
required: true
default: 'master'
libtock-c-ref:
description: 'Ref (revision/branch/tag) of the upstream libtock-c repo to test'
required: true
default: 'master'
# Add push trigger for your branch
push:
branches:
- dev/mult-board-treadmill
permissions:
contents: read
jobs:
prepare-ble-test:
runs-on: ubuntu-latest
outputs:
# This is a fixed list containing just the BLE advertising and scanning test
hwci-tests-json: ${{ steps.prepare-test.outputs.hwci-tests-json }}
steps:
- name: Prepare BLE test
id: prepare-test
run: |
# Instead of analyzing changes, we specifically select the BLE test
echo 'hwci-tests-json=["tests/ble_advertising_scanning_test.py"]' >> "$GITHUB_OUTPUT"
echo "Selected test: tests/ble_advertising_scanning_test.py"
run-treadmill-ci:
needs: [prepare-ble-test]
uses: ./.github/workflows/treadmill-ci.yml
with:
# Only run on a specific repository
repository-filter: 'tock/tock-hardware-ci'
# Provide access to the required Treadmill secrets
job-environment: 'treadmill-ci'
# This workflow tests the tock-hardware-ci scripts itself, so take the
# current GITHUB_SHA:
tock-hardware-ci-ref: ${{ github.sha }}
# Use the provided upstream Tock kernel / userspace components:
tock-kernel-ref: ${{ inputs.tock-kernel-ref }}
libtock-c-ref: ${{ inputs.libtock-c-ref }}
# Pass our fixed test JSON
tests-json: ${{ needs.prepare-ble-test.outputs.hwci-tests-json }}
# Specify that this is for the BLE test which needs multiple boards
multi-board: 'true'
supervisor-id: 'fb1384d5-e1a5-469c-beb4-0d4d215c9793'
board-descriptors: >-
board_descriptors/fb1384d5-e1a5-469c-beb4-0d4d215c9793/board-nrf52840dk-001050202501.yml board_descriptors/fb1384d5-e1a5-469c-beb4-0d4d215c9793/board-nrf52840dk-001050244773.yml
secrets: inherit
164 changes: 164 additions & 0 deletions hwci/tests/ble_advertising_scanning_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# hwci/tests/ble_advertising_scanning_test.py
#
# Multi‑board integration test:
# • Board 0 runs the ble_advertising example
# • Board 1 runs the ble_passive_scanning example
#
# We verify that the advertiser prints its “start advertising” message and that
# the scanner eventually prints at least one advertisement.

import logging
import time
import re
from core.test_harness import TestHarness


class BleAdvertisingScanningTest(TestHarness):
"""
Multi‑board test:
1. Advertiser reports it is advertising with the expected device name.
2. Scanner prints at least one advertisement that contains our company ID
(0x13, 0x37) inside the manufacturer‑specific field.
"""

EXPECTED_DEVICE_NAME = "TockOS"

MANUFACTURER_DATA = "06 00"
MANUFACTURER_DATA_RE = re.compile(r"\bff\s+06\s+00\b", re.IGNORECASE)

def test(self, boards):
if len(boards) < 2:
raise ValueError(
"Need at least 2 boards for BLE advertising/scanning test!"
)

advertiser, scanner = boards[0], boards[1]

# Safety: ensure distinct UARTs
if advertiser.uart_port == scanner.uart_port:
raise ValueError(
f"Both boards are using the same serial port: {advertiser.uart_port}. "
f"Each board must have a unique serial port. "
f"Board 1 SN: {getattr(advertiser, 'serial_number', 'unknown')}, "
f"Board 2 SN: {getattr(scanner, 'serial_number', 'unknown')}"
)

logging.info(
f"Advertiser (SN: {advertiser.serial_number}) using port: {advertiser.uart_port}"
)
logging.info(
f"Scanner (SN: {scanner.serial_number}) using port: {scanner.uart_port}"
)

# Clean slate
advertiser.erase_board()
scanner.erase_board()
advertiser.serial.flush_buffer()
scanner.serial.flush_buffer()

advertiser.flash_kernel()
scanner.flash_kernel()

advertiser.flash_app("ble_advertising")
scanner.flash_app("ble_passive_scanning")

logging.info(
"Flashed ble_advertising -> board0, ble_passive_scanning -> board1."
)

adv_done = False # advertiser start message seen
scan_done = False # manufacturer data seen

scanner_output: list[str] = []
advertiser_output: list[str] = []

start_time = time.time()
TIMEOUT = 30 # seconds

while True:
if time.time() - start_time > TIMEOUT:
break

# ---------- Advertiser ----------
line_adv = advertiser.serial.expect(
".+\r?\n", timeout=1, timeout_error=False
)
if line_adv:
text_adv = line_adv.decode("utf-8", errors="replace").strip()
advertiser_output.append(text_adv)
logging.debug(f"[Advertiser] {text_adv}")

if re.search(
rf"(Now advertising .*'{self.EXPECTED_DEVICE_NAME}'|"
rf"Begin advertising!? *{self.EXPECTED_DEVICE_NAME})",
text_adv,
):
adv_done = True
logging.info(
f"Advertiser started advertising as '{self.EXPECTED_DEVICE_NAME}'"
)

# ---------- Scanner ----------
line_scan = scanner.serial.expect(".+\r?\n", timeout=1, timeout_error=False)
if line_scan:
text_scan = line_scan.decode("utf-8", errors="replace").strip()
scanner_output.append(text_scan)
logging.debug(f"[Scanner] {text_scan}")

if self.MANUFACTURER_DATA_RE.search(text_scan):
logging.info(
f"Scanner detected our expected manufacturer data ({self.MANUFACTURER_DATA})"
)
scan_done = True

if adv_done and scan_done:
break

# Final pass over accumulated output
full_scan_out = "\n".join(scanner_output)
logging.info(f"Collected {len(scanner_output)} lines from scanner")

if not scan_done and self.MANUFACTURER_DATA_RE.search(full_scan_out):
logging.info("Found manufacturer data in combined scanner output")
scan_done = True

# Assemble error report if needed
errors = []

if not adv_done:
errors.append(
f"Advertiser never printed its start‑advertising line containing "
f"'{self.EXPECTED_DEVICE_NAME}'."
)

if not scan_done:
data_fields = re.findall(r"Data:\s*([0-9a-fA-F ]+)", full_scan_out)

err = (
"Scanner board never detected an advertisement with the expected "
f"manufacturer data ({self.MANUFACTURER_DATA}).\n"
)
if data_fields:
err += "\nData fields seen in advertisements:\n"
for i, data in enumerate(data_fields[:10], 1):
err += f" {i:2d}: {data}\n"
if len(data_fields) > 10:
err += f" … and {len(data_fields) - 10} more\n"
else:
err += "No advertisement ‘Data: …’ lines were captured!\n"

sample = "\n".join(scanner_output[:20])
err += "\nSample scanner output:\n" + sample
if len(scanner_output) > 20:
err += f"\n… and {len(scanner_output) - 20} more lines"

errors.append(err)

if errors:
raise Exception("\n\n".join(errors))

logging.info("BLE advertising + scanning test passed successfully!")


# For manual invocation
test = BleAdvertisingScanningTest()