./scripts/dev is the only supported public firmware command. It never
installs dependencies or contacts hardware unless a command explicitly asks to
flash over USB.
| Task | Command |
|---|---|
| Inspect profile compatibility | ./scripts/dev profiles |
| Build firmware | ./scripts/dev build --hardware <hardware> --machine <machine> |
| Build and install over USB | ./scripts/dev build flash --confirm --hardware <hardware> --machine <machine> --port <port> |
| Flash an existing image | ./scripts/dev flash --confirm --hardware <hardware> --machine <machine> --port <port> |
| Monitor USB serial | ./scripts/dev monitor --port <port> --speed 115200 |
| Run host tests | ./scripts/dev test normal |
| Preview Web UI designs | ./scripts/dev preview |
| Validate changes | ./scripts/dev validate |
Successful firmware builds automatically retain ELF/BIN pairs in
artifacts/firmware/<hardware>--<machine>/<ELF SHA>/, including build without
flash. The directory also contains image identity for matching a crash log to
its ELF; ./scripts/dev clean keeps these archives.
At the end of every successful command that includes build, the terminal
highlights the absolute build-output folder and the absolute shotstopper.bin
path ready for installation.
The design preview server renders the embedded Web UI in a desktop browser for design iteration. Only the server itself is part of the project; every design asset it serves is a local file that never enters Git:
- Project tooling:
scripts/webui-preview/server.js, started with./scripts/dev preview(use--port 4174when the default port 4173 is busy). It listens only on this computer, reads the current firmware markup, styles, and scripts on every page load, and stops with Ctrl-C. - Local design assets: the
webui-mockups/folder at the repository root. The folder is Git-ignored, so a fresh clone starts without it and mockup work never produces commits, documentation, or tests.
The server offers two kinds of pages. Reference views are rendered live
from the firmware sources: / shows the Home screen, /mobile-menu the
bottom-navigation sample views, and /micra the machine diagnostics, with
the firmware stylesheet at /app.css. Everything else is served from
webui-mockups/ by file name: drop proposal.html into the folder and
open http://127.0.0.1:4173/proposal; proposal.css and proposal.js
resolve the same way, so adding or removing a mockup never requires a
server change.
The preview does not connect to a device or change its settings; the values it shows are illustrative. The header's How shots stop icons and panel run against a small simulated controller in the page, so you can try the mode and guard switches; reloading starts over. Mockups and assets created for the preview are development resources, not product files: they must not generate commits, documentation updates, or tests, and they never ship in firmware assets. Bringing a finished design into the product is a separate, deliberate change.
Review the selected profiles and physical safety before adding --confirm.
The flag authorizes hardware access; it does not skip image, profile, or
partition checks. Controllers on an earlier project layout move up with a
preserving one-time USB installation
using the normal flash command. It keeps settings and saved history and starts
only the newer storage areas empty; subsequent updates can use OTA. Use --erase-all only for an explicitly
chosen clean installation:
./scripts/dev build flash --confirm \
--hardware esp32-s3-relay-x1-speaker \
--machine la-marzocco-linea-micra \
--port /dev/cu.usbmodem2101 \
--erase-allThe clean install erases both firmware slots and all persisted data: settings, Wi-Fi credentials, presets, scale preferences, shot history, curves, and the last-shot record. Record anything needed before running it.
--image <path> flashes an existing verified project image and is rejected
with build. --no-check skips only local image identity verification; it
does not skip partition checks. --port must name a connected USB serial
device. On macOS use /dev/cu.usbmodem*; on Linux use /dev/ttyACM* or
/dev/ttyUSB*.
Upload a built image to a controller on the same network without a USB cable:
./scripts/dev build ota --confirm \
--hardware esp32-s3-relay-x1-speaker \
--machine la-marzocco-linea-micra \
--host 192.168.1.50 \
--yes --wait-for-confirmation--host accepts the controller's station address, or 192.168.4.1 on its
access point. The hidden prompt, or SHOTSTOPPER_DEVICE_PASSWORD, supplies
the device password; commands never take it as an argument. --image <path>
uploads an existing image instead of building. --yes answers the commit
question; --wait-for-confirmation independently verifies the rebooted image.
Transfers are resumable: an interrupted run queries the confirmed offset and
repeats only the missing ranges. Safety behavior, session details, and
troubleshooting: OTA.
Every project build requires an exact hardware and machine profile. Use
./scripts/dev profiles to inspect built-in profiles and physically compatible
pairs. The compatible built-in pairs are listed in
Build profiles; each machine profile
declares its compatible hardware profiles. The optional --development
profile enables the development admin/JTAG settings; --release is the
default. These options are compile-time only and are never persisted.
./scripts/dev validate --risk R2 builds each official validation pair;
--risk R3 also checks each pair with Cppcheck and a GCC warning build.
R2 and R3 run two lanes at once: the host tests, and the firmware steps in a
fixed order (route capacity, Web contracts, then each pair's build, Cppcheck,
and warning build). Each step's output appears in the log as one block when
the step finishes. After any failure neither lane starts another step;
steps already running finish first. The warning build uses its own tree,
build-idf/warnings/<hardware>--<machine>/, so the gate's
build-idf/<hardware>--<machine>/ images stay incremental. Host tests and
Cppcheck use one job per CPU.
The supported transient options are --webui-language, --flags, --o0,
--og, --o2, and --os. The build also accepts
--force-sdkconfig-regenerate to recreate its selected variant's configuration
from repository defaults without cleaning the build tree. Builds do this
automatically when selected defaults change or a legacy tree has no recorded
input fingerprint. Regeneration discards local menuconfig choices; an
unchanged variant retains them. The flag is rejected for standalone flash, OTA,
and monitor. The stored device password is never accepted on a
command line; use the hidden prompt or the documented USB serial procedure.
The current n16r8 resource baseline applies to the default -O2 builds;
the historical n8r4 baseline applies to -Os. Other optimization levels
retain the physical image and memory placement checks; see Build.
Use ./scripts/dev doctor to inspect local tooling and
./scripts/dev context <area> for focused repository paths. Use
./scripts/dev classify before a change and ./scripts/dev validate for the
required validation gate. Missing dependencies are reported as failures;
scripts never install them automatically. doctor also reports each tool's
resolved path and version and warns when a Homebrew-managed tool is outdated,
so checks always run against the machine's current releases. It also shows
whether the optional ccache is installed: when it is, firmware builds use it
automatically, which makes rebuilds after a flag or configuration change much
faster. Set IDF_CCACHE_ENABLE=0 to build without it.
Node.js and Cppcheck carry no project version pin: every script resolves them
from PATH, so install current releases with your package manager (see
Build). ./scripts/dev analyze needs
Cppcheck on PATH; see Static analysis for local setup.
Firmware builds require ESP-IDF 6.1.0 and fail if the IDF component lock
changes during resolution. Review upgrades through Build.
To regenerate the immutable time-zone table after reviewing a new IANA
release, place the official tzdata2026d.tar.gz archive in
temp/ai_temp_timezone_impl/ and run:
python3 scripts/generate_timezones.py \
temp/ai_temp_timezone_impl/tzdata2026d.tar.gz \
src/ShotStopperTimeZoneData.hThe script checks the pinned archive SHA-256 and release version, uses the
local zic compiler, and writes the bounded 2025–2099 table. It never
downloads data. Review the generated diff and run the normal validation gate.
For a future release, update the pinned version and hash deliberately along
with boundary fixtures and resource measurements.
For a failed boot or a changed partition layout, reinstall the complete image
over USB with --erase-all and follow Emergency recovery.