One LED cannot say two things at once, and in the default presence
mode the status LED is already carrying "a card is here". The result
was that a bad read - a checksum failure - was reported not at all:
verified by driving the LED worker, which showed presence mode
producing no light on invalid_card while error mode blinked.
A second LED on its own pin (GPIO 24 = physical pin 18, matching the
workstation app) now pulses for --error-pulse seconds on a bad read,
leaving the status light to keep tracking the card. This also gives
ERROR_BLINK_SECONDS, previously dead, a real use.
--error-led-pin N / --error-led-active-low / --error-pulse S
--no-error-led restores the single-LED blink
install.sh rewrites the unit's ExecStart for all of it, verified for
the default, active-low and no-error-led cases.
Also extracts the repeated backend-opening code into _open_led(),
which keeps the BadPinFactory-is-an-ImportError subtlety in one place
instead of two, and treats the error pin colliding with the status
pin as "no error LED" rather than opening one pin twice.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
active_high was hardcoded to True, so an LED wired the other way
round - cathode to the pin, anode to 3.3 V, or an inverting
transistor driver - had no configuration at all. The symptom is easy
to misread as a dark LED: the light actually does the opposite, on
with no card and off with one.
Exposed as --led-active-low on the app and on install.sh, which
rewrites the unit's ExecStart like the other LED options. The
startup line now names the polarity, so the journal says which
configuration is actually running:
LED on GPIO 23 (presence mode, active-high, gpiozero)
Default is unchanged, so existing installs behave identically.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
--led-pin is the BCM GPIO number, not the physical header pin, and
getting it wrong produces a dark LED with no error at all: the app
still reports "LED on GPIO 23" and looks healthy. The three-row table
did not cover most of the header, so the fix for a wire on any other
pin was left as guesswork.
Lists all 22 usable pins both ways round, gives the concrete
"physical pin 23 needs --led-pin 11" case, names the pins that must
not be used (the reader's own UART on 8/10, reserved ID_SD/ID_SC on
27/28, and the power pins), and adds a blink-each-candidate script
that finds the pin by experiment. Verified the table against the
standard 40-pin pinout.
Also drops a duplicated sentence about /dev/serial0 in Wiring.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the "Running at boot" section with a five-step guide that
starts from enabling the UART, the step that is easiest to miss and
the one that produces the "device not found" failure:
1. enable the serial port with raspi-config (and answer No to the
login shell, or the console holds the port)
2. confirm which device exists, with a table mapping what you see
to the --device value to pass
3. install the service, with both possible failure messages and
what each one means
4. verify via systemd, the journal and the status page
5. fall back to a foreground run and --list-ports when cards are
not detected
Also fixes a contradiction: the section claimed the installer
defaults --device to /dev/rdm-reader when it is /dev/serial0, while
the options table three lines below said /dev/serial0. That mismatch
is exactly what leads someone to install against the wrong device.
Notes that --no-service does not skip the device checks, verified
against the script's control flow, and drops the raspi-config menu
number rather than assert one that varies by release.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GET / now returns a dashboard for the reader: mode, port, uptime,
card presence, the current card, event counters and a live event log
seeded from /api/taps and updated over the /api/events SSE stream.
The page is read-only on purpose - it has no claim button, because
POST /api/card/claim spends the tap and merely viewing the status
would otherwise consume a real login.
Serving the page requires allowing same-origin browser requests: a
browser sends Origin even for a request to the same origin, so
_cors_ok() used to reject the page's own API calls with 403 unless
--allow-origin was passed. Loopback and same-host origins are now
accepted, and the Access-Control-Allow-Origin echo is applied
consistently in one helper instead of four copies.
--no-page reverts POST-free deployments to API-only.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- keyboard.py: emulate a commercial USB RFID reader by typing card ids
into the focused window via a /dev/uinput virtual keyboard
- led.py / bridge.py: drive the status LED from card events
- cli.py, card.py: wire up the new options and event flow
- deploy: harden install.sh and the systemd unit
- README: document keyboard mode, LED support, and deployment
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>