The workstation app lit an LED on GPIO 23 with the card. This brings that
across, with three deliberate differences: it never blocks the reader, it
cannot be fatal, and its state is reported.
WMT toggled the LED directly inside card_inserted, on the reader thread, where
a slow write stalls card detection. Here every change is queued to a worker, so
insert() returns immediately (tested with a deliberately slow pin).
Two modes, because the light can mean two things:
error (default) lit while the reader runs, dark while reading, blinks on a
bad read - a health indicator
presence lit only while a card is on the reader
gpiozero raises BadPinFactory on a machine with no GPIO, and that subclasses
ImportError - so a single except ImportError reports 'gpiozero is not
installed' when the library is present but has no usable pin provider. The
library check and the pin construction are therefore separate.
Vendors gpiozero + colorzero (~180 KB) so the LED installs offline too, and
deliberately not setuptools: gpiozero declares it via colorzero, but it is only
a packaging requirement and is never imported at runtime. Installing to the
bundled wheels therefore uses --no-deps, saving 818 KB.
Verified end to end over a pseudo-terminal with a fake pin: a real RDM6300
frame drives the LED through the app's own CLI in both modes.
A device with no internet could not set itself up: the auto-install needed
PyPI. The rdm6300 and pyserial wheels now ship inside the package, so first
start works with no network at all.
Both wheels are pure Python (py3-none-any / py2.py3-none-any), which is what
makes this possible - the same files work on armv7l, aarch64 and x86_64. This
is the difference from the workstation app's Files/repository cache, which
held aarch64 manylinux binaries and so could not be committed; that cache only
ever existed on the Pi devices.
- bundled_wheel_dir() locates them; ensure_dependencies() prefers them
automatically, so the service needs no --wheel-dir.
- verify_wheels() checks SHA256SUMS before installing. A wheel that fails its
checksum is refused rather than installed, then PyPI is tried, and if that is
unreachable it reports exit 2 instead of proceeding unverified.
- local_wheel_missing() falls back to PyPI when the bundle does not cover a
requirement, instead of attempting a --no-index install that must fail.
- An explicit --wheel-dir that is incomplete or unverified is called out, not
silently swapped for PyPI.
Verified end to end: built wheel and sdist both contain the wheels; a venv
with no rdm6300 self-provisions with a dead proxy and no PIP_NO_INDEX; and a
tampered wheel is refused with nothing installed.
A freshly flashed device needed manual pip work before the reader would
start. It now checks its dependencies, installs whatever is missing, and
carries on.
- bootstrap.py: import check, pip invocation, and a re-check that does not
trust pip's exit code alone. Installs nothing when the libraries are
already present, so a normal start does no network work.
- --wheel-dir installs from local wheels with --no-index, for devices with
no internet. --no-install-deps opts out entirely.
- Outside a venv pip gets --break-system-packages, which Debian and
Raspberry Pi OS require before touching the system Python.
- reload_library() re-imports rdm6300 when it was installed at runtime, since
the module-level import already ran and failed.
- A failed install reports exit 2 and the exact manual command rather than
raising; stdout is flushed so the explanation is not printed after the
error when piped.
Card reading (rdm6300 + pyserial) printing card inserted/removed with the
full card data, plus a --serve mode that publishes taps over HTTP so a web
page can authenticate a tap.
The bridge reports card ids only - mapping a card to a user stays on the
server. The tap is latched for --tap-ttl seconds, including after the card is
lifted, and is single use, so a page reload cannot replay one tap into two
logins.
Port selection identifies the reader by serial number or by-id path instead of
/dev/ttyUSBn, and refuses to guess when a selector matches several adapters.
deploy/ has the systemd unit, a udev rule for a stable /dev/rdm-reader, and
install.sh. Covered by unit tests plus PTY end-to-end tests.