Skip to content
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ scripts/deploy.sh barista # generate headers, run gates, compile, flash
the model to be named, because the board holds one at a time and deploying
replaces it.

Before the first fetch, install the Hugging Face CLI (`hf`), which
`fetch_model.sh` requires to download the released assets:

```bash
uv tool install huggingface_hub # provides `hf`
```

`deploy.sh` auto-detects the board's serial port on Linux
(`/dev/ttyACM*` for native USB CDC, `/dev/ttyUSB*` for a UART bridge) and on
macOS (`/dev/cu.usbmodem*`). If several ports match, or detection does not fit
your wiring, pin it explicitly: `PORT=/dev/ttyACM0 scripts/deploy.sh barista`.

`fetch_model.sh` checks the inference assets against a SHA-256 and byte size
pinned in the script, and cross-checks the release's own `metadata.json` against
those same pins. It installs nothing unless every check passes, so a failed
Expand Down
19 changes: 15 additions & 4 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,21 @@ find_esptool() {
}
ESPTOOL=$(find_esptool)

# `|| true`: a failing glob would otherwise abort the assignment under `set -e`,
# before the message below can print.
PORT=${PORT:-$(ls /dev/cu.usbmodem* 2>/dev/null | head -1 || true)}
[ -n "$PORT" ] || { echo "no /dev/cu.usbmodem* found; plug the board in, or set PORT=..." >&2; exit 1; }
# Both OS families name the same hardware differently: macOS calls the Arduino
# bridge /dev/cu.usbmodem* (and older FTDI/CP210x boards /dev/cu.usbserial-*),
# while Linux calls the native USB CDC device /dev/ttyACM* and a UART bridge
# /dev/ttyUSB*. Scan them all so the same command works on either platform.
# With more than one board attached the first match is arbitrary, so the
# message tells the user to set PORT to the exact path.
find_port() {
local pat
for pat in /dev/ttyACM* /dev/ttyUSB* /dev/cu.usbmodem* /dev/cu.usbserial-*; do
ls $pat 2>/dev/null && return 0
done
return 0
}
PORT=${PORT:-$(find_port | head -1)}
[ -n "$PORT" ] || { echo "no serial port found (looked for /dev/ttyACM*, /dev/ttyUSB*, /dev/cu.usbmodem*); plug the board in, or set PORT=/dev/ttyACM0 (Linux) or PORT=/dev/cu.usbmodemNNNN (macOS)" >&2; exit 1; }

# Every required artifact is checked before anything is generated or built, so a
# missing file is reported as a list rather than discovered halfway through.
Expand Down
42 changes: 41 additions & 1 deletion tests/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,49 @@ def test_a_missing_port_stops_before_generating(self):
"vocab.json", "layout.json"])
r = self.run_deploy("barista", PORT="")
self.assertNotEqual(r.returncode, 0)
self.assertIn("no /dev/cu.usbmodem*", r.stderr)
self.assertIn("no serial port found", r.stderr)
self.assertEqual(self.calls(), [])


class SerialPortIsAutoDetected(DeployHarness):
"""deploy.sh picks a port when PORT is unset. The scan covers both OS
families: Linux names the board /dev/ttyACM* or /dev/ttyUSB*, macOS
/dev/cu.usbmodem*. These tests stub `ls` so they are independent of the
hardware attached to the runner."""

def setUp(self):
super().setUp()
self.artifacts("barista", ["model.bin", "tokenizer.json",
"vocab.json", "layout.json"])

def run_with_ls(self, ls_body):
stub_ls = self.bin / "ls"
stub_ls.write_text(ls_body)
os.chmod(stub_ls, 0o755)
return self.run_deploy("barista", PORT="")

def test_linux_ttyACM_is_detected(self):
r = self.run_with_ls("""#!/bin/sh
case "$1" in
/dev/ttyACM*) echo /dev/ttyACM0; exit 0 ;;
*) exit 1 ;;
esac
""")
self.assertEqual(r.returncode, 0, r.stderr)
flash = [c for c in self.calls() if "write_flash" in c]
self.assertIn("/dev/ttyACM0", flash[0])

def test_macos_cu_usbmodem_is_detected(self):
r = self.run_with_ls("""#!/bin/sh
case "$1" in
/dev/cu.usbmodem*) echo /dev/cu.usbmodem1410; exit 0 ;;
*) exit 1 ;;
esac
""")
self.assertEqual(r.returncode, 0, r.stderr)
flash = [c for c in self.calls() if "write_flash" in c]
self.assertIn("/dev/cu.usbmodem1410", flash[0])


if __name__ == "__main__":
unittest.main()