Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fd88449
docs: add hardware-in-the-loop testing design proposal
claude Aug 14, 2026
41f53e3
ci: pin all actions and Python dependencies; update HIL design
claude Aug 15, 2026
9653c32
docs: add HIL runner setup runbook for the Raspberry Pi
claude Aug 16, 2026
984c4a9
docs: cut runner setup down to the minimum
claude Aug 16, 2026
c06c794
docs: simplify runner setup for the actual bench
claude Aug 16, 2026
f6c433b
docs: package runner setup as a Raspberry Pi Connect artefact
claude Aug 16, 2026
d7e15a5
build: commit prebuilt Connect update artefact and checksum
claude Aug 16, 2026
02da188
docs: add pre-merge artefact URL for testing
claude Aug 16, 2026
7567cdb
docs: replace OTA artefact with a plain setup script
claude Aug 17, 2026
962e256
ci: add HIL bench smoke workflow
claude Aug 17, 2026
f08ac74
ci: put the venv on PATH for HIL smoke
claude Aug 17, 2026
cad0383
fix: pin the bench clone to a known ref, guard .env values
claude Aug 17, 2026
9761bf0
ci: flash every bench board and health-check its API
claude Aug 17, 2026
25ae1ff
feat: add board identify mode to the HIL harness [skip ci]
claude Aug 17, 2026
e6ef1e3
fix: make the HIL harness find its own tools [skip ci]
claude Aug 17, 2026
3f18e6e
ci: record per-stage timings in the HIL harness
claude Aug 17, 2026
f8962aa
fix: decompress gzip and retry in the HIL HTTP checks
claude Aug 17, 2026
a389523
docs: correct the bare-board fault predictions with measured results …
claude Aug 17, 2026
bbdbb31
docs: replace HIL throughput estimates with measured numbers
claude Aug 17, 2026
17316c5
feat: wait on the boot console and broaden the HTTP API checks
claude Aug 18, 2026
be59fc6
fix: reset each board before watching for its ready marker
claude Aug 18, 2026
42f91e5
fix: allow plain-text routes and prime the USB link before use
claude Aug 18, 2026
8967ccb
Merge branch 'main' into claude/hil-testing-design-s564ln
mullinmax Aug 23, 2026
74b829c
Merge branch 'main' into claude/hil-testing-design-s564ln
mullinmax Aug 25, 2026
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto

# Bench setup scripts run as POSIX sh on the Pi; CRLF would break the shebang.
dev/hil/*.sh text eol=lf
12 changes: 6 additions & 6 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
pull-requests: write
steps:
- name: Check out code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.10"

Expand Down Expand Up @@ -71,7 +71,7 @@ jobs:

- name: Upload update artifacts
if: ${{ github.event_name == 'pull_request' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: update-files
path: pr-artifacts
Expand All @@ -88,7 +88,7 @@ jobs:
- name: Publish raw update files
if: ${{ github.event_name == 'pull_request' }}
id: publish_raw_updates
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
# GitHub's git-data API intermittently returns 5xx; retry instead
# of failing the whole build on a transient server error.
Expand Down Expand Up @@ -227,7 +227,7 @@ jobs:

- name: Comment with update artifact links
if: ${{ github.event_name == 'pull_request' }}
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
RAW_LINKS_JSON: ${{ steps.publish_raw_updates.outputs.links }}
with:
Expand Down Expand Up @@ -347,7 +347,7 @@ jobs:
- name: Create or update GitHub Release
if: ${{ env.SHOULD_RELEASE == 'true' }}
id: create_release
uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
tag_name: ${{ env.VERSION }}
name: ${{ env.VERSION }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docs-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
ref: ${{ github.head_ref }}

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.11'

Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/hil-flash-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: HIL flash and health check

# Builds firmware for each attached board, flashes it, and health-checks the
# API over both USB and HTTP once the board boots.
#
# Deliberately no `pull_request` trigger — this targets a self-hosted runner on
# a private network and physically reflashes hardware. Fork gating is designed
# in dev/hil/DESIGN.md §4 and is not built yet.
#
# The `push` trigger exists so this can be validated before merging, since a
# workflow_dispatch workflow is not dispatchable until it reaches the default
# branch. Drop the push trigger once this is on main.

on:
workflow_dispatch:
inputs:
skip_http:
description: "USB checks only (skip the network stack)"
type: boolean
default: false
skip_flash:
description: "Health-check what is already flashed, do not reflash"
type: boolean
default: false
inventory_only:
description: "Just print each board's chip id, for building VECTOR_HIL_BOARD_MAP"
type: boolean
default: false
identify:
description: "Blink each board in turn so you can see which physical board is which"
type: boolean
default: false
push:
branches:
- claude/hil-testing-design-s564ln
paths:
- dev/hil/flash_and_check.py
- .github/workflows/hil-flash-check.yml

permissions:
contents: read

# Flashing is destructive and the bench is one set of boards. Never interleave.
concurrency:
group: hil-bench
cancel-in-progress: false

jobs:
flash-and-check:
runs-on: [self-hosted, vector-hil]
timeout-minutes: 45

steps:
# actions/checkout is safe here ONLY because every trigger above is
# repo-internal. If a fork-reachable trigger is ever added, this must go
# back to running from the runner's pinned clone ($VECTOR_HIL_REPO) —
# otherwise a fork PR would execute its own harness code on the bench.
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Prepare environment
run: |
test -x "$VECTOR_HIL_VENV/bin/python" || { echo "bench venv missing"; exit 1; }
echo "$VECTOR_HIL_VENV/bin" >> "$GITHUB_PATH"

# VECTOR_HIL_BOARD_MAP, VECTOR_HIL_WIFI_* and friends arrive from the
# runner's .env, which the runner process exports into every job. They
# are deliberately NOT restated in an `env:` block here: the `env`
# context only covers workflow/job/step-level vars, so `${{ env.X }}`
# would evaluate to empty and shadow the real value.
- name: Flash and health-check every board
run: |
args=""
if [ "${{ inputs.skip_http }}" = "true" ]; then args="$args --skip-http"; fi
if [ "${{ inputs.skip_flash }}" = "true" ]; then args="$args --skip-flash"; fi
if [ "${{ inputs.inventory_only }}" = "true" ]; then args="$args --inventory-only"; fi
if [ "${{ inputs.identify }}" = "true" ]; then args="$args --identify"; fi
python dev/hil/flash_and_check.py $args

- name: Board serial logs on failure
if: failure()
run: |
# A board that failed its health check may still be printing something
# useful; grab a few seconds of console from each.
for dev in /dev/ttyACM*; do
echo "--- $dev"
timeout 8 cat "$dev" || true
done
135 changes: 135 additions & 0 deletions .github/workflows/hil-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: HIL smoke

# Proves the bench runner is wired up correctly: the runner picks up jobs, the
# bench environment reaches them, the boards enumerate, and each one answers
# over serial. It does not test firmware — see dev/hil/DESIGN.md for that.
Comment thread
mullinmax marked this conversation as resolved.
#
# Deliberately no `pull_request` trigger. This targets a self-hosted runner on
# a private network, and a pull_request trigger would let any fork PR run code
# against it. Fork gating is designed in DESIGN.md §4 and is not built yet.
#
# The `push` trigger exists so this can be validated before merging, since a
# workflow_dispatch workflow is not dispatchable until it reaches the default
# branch. Drop the push trigger once this is on main.

on:
workflow_dispatch:
push:
branches:
- claude/hil-testing-design-s564ln

permissions: {}

# The bench is a single set of physical boards. Queue, never interleave.
concurrency:
group: hil-bench
cancel-in-progress: false

jobs:
smoke:
runs-on: [self-hosted, vector-hil]
timeout-minutes: 15

# No actions/checkout on purpose: under the design's trust model the bench
# runs harness code from a trusted checkout the runner already has, not
# from the PR. $VECTOR_HIL_REPO points at it.

steps:
- name: Runner host
run: |
echo "user: $(whoami)"
echo "kernel: $(uname -srm)"
echo "arch: $(dpkg --print-architecture)"
free -h | awk '/Mem:/ {print "memory: " $2 " total, " $7 " available"}'
echo "uptime:$(uptime -p | sed 's/^up//')"

- name: Bench environment
run: |
fail=0
for var in VECTOR_HIL_REPO VECTOR_HIL_VENV VECTOR_HIL_WIFI_SSID VECTOR_HIL_WIFI_PASSWORD; do
eval "val=\${$var:-}"
if [ -z "$val" ]; then
echo "MISSING: $var"
fail=1
else
echo "set: $var"
fi
done
[ "$fail" -eq 0 ] || {
echo "The runner's .env is incomplete. It is read at service start:"
echo " cd ~/actions-runner && sudo ./svc.sh stop && sudo ./svc.sh start"
exit 1
}
test -x "$VECTOR_HIL_VENV/bin/python" || { echo "venv python missing at $VECTOR_HIL_VENV"; exit 1; }
test -d "$VECTOR_HIL_REPO/.git" || { echo "no clone at $VECTOR_HIL_REPO"; exit 1; }

# dev/detect_boards.py shells out to a bare `mpremote` from PATH and
# silently returns {} when it is missing, so the venv must be ON PATH
# for later steps - invoking its python by absolute path is not enough.
echo "$VECTOR_HIL_VENV/bin" >> "$GITHUB_PATH"

- name: Serial access
run: |
if ! ls /dev/ttyACM* >/dev/null 2>&1; then
echo "no /dev/ttyACM* devices - check the USB hub and power"
exit 1
fi
ls -l /dev/ttyACM*
id -nG | grep -qw dialout || {
echo "runner user is not in the dialout group; reboot or restart the service"
exit 1
}

- name: mpremote sees the boards
run: |
# Checked separately because detect_boards.py collapses "mpremote is
# missing", "mpremote failed", and "no boards attached" into the same
# empty result, which makes a real failure indistinguishable from an
# empty bench.
command -v mpremote || { echo "mpremote not on PATH"; exit 1; }
mpremote version

echo "--- mpremote devs ---"
mpremote devs | tee /tmp/devs.txt
test -s /tmp/devs.txt || {
echo "mpremote sees no devices, though /dev/ttyACM* exist."
echo "Boards may be busy or in an odd state; power-cycle the hub and retry."
exit 1
}

- name: Detect and query boards
run: |
# detect_boards.py prints progress text before the JSON, so the
# machine-readable part is the last line only.
raw=$(python "$VECTOR_HIL_REPO/dev/detect_boards.py")
echo "$raw"
json=$(printf '%s\n' "$raw" | tail -1)

python - "$json" <<'PY'
import json, subprocess, sys

boards = json.loads(sys.argv[1])

if not boards:
sys.exit("no boards detected")

rc = 0
for kind, ports in sorted(boards.items()):
for port in ports:
probe = subprocess.run(
["mpremote", "connect", port, "exec",
"import systemConfig; print(systemConfig.vectorSystem, systemConfig.SystemVersion)"],
capture_output=True, text=True, timeout=60)
if probe.returncode:
print(f"FAIL {kind:12} {port:16} {probe.stderr.strip()}")
rc = 1
else:
print(f"ok {kind:12} {port:16} {probe.stdout.strip()}")

print(f"\n{sum(len(p) for p in boards.values())} board(s) responding")
sys.exit(rc)
PY

- name: Summary
if: always()
run: echo "Bench smoke ${{ job.status }}"
4 changes: 2 additions & 2 deletions .github/workflows/specialfeatures-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
ref: ${{ github.head_ref }}

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.11'

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/validate-json-configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/version-bump-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
ref: ${{ github.head_ref }}

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"

Expand Down Expand Up @@ -59,4 +59,4 @@ jobs:
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add src/common/SharedState.py src/em/systemConfig.py src/sys11/systemConfig.py src/wpc/systemConfig.py src/data_east/systemConfig.py src/whitestar/systemConfig.py src/classic/systemConfig.py
git commit -m "ci: auto-bump required patch versions"
git push origin HEAD:${{ github.head_ref }}
git push origin HEAD:${{ github.head_ref }}
Loading
Loading