The box program carried 160 lines of dead code and a public repo held hotspot passwords
A tidy-up of main.py with the same MQTT, NVS and screen contract, so that the next person can read it. Verified on the PC with a stubbed device environment driving every branch (65 checks: boot from empty storage, each card type, positive / negative / foreign / undecodable replies, the 10 s timeout, lamp and reset topics, MQTT / WiFi / reader recovery, heartbeat, OTA accept and reject paths); nothing here can run on the device itself. Removed without changing behaviour: every commented-out line, duplicate and unused imports, the timestamp helper and reset-UID constant nobody used, the seen_uids set with its uid_count/uid_<n> NVS bookkeeping (never read for anything; old keys stay in flash, ignored), labels that were "updated" but never created, the serial recomputed on every pass, callback locals leaking into globals, and the non-blocking WiFi reconnect that the blocking one on the next line always overrode. Widgets now live in `ui` under plain names (label_set / set_msg tolerate a widget not yet drawn, which is what lets setup() read NVS before the screen exists); lamp colours are a table. Deliberate small changes, all in the box's favour: - Reads only card blocks 10 (type) and 12 (operator id). Blocks 4-9 held fields that stopped being published long ago. RFIDUnit.read() authenticates the block's own sector each call, so the reads do not depend on each other. - A Reset card fires once per tap (tap_handled). Before, one left on the reader re-sent RESET after every server reply. - A failed heartbeat publish drops the MQTT client so the throttled reconnect runs. Before, it flagged the RFID reader for a rebuild and never touched MQTT. - No NVS write on every Product tap - it rewrote identical values. - The serial is drawn at boot instead of "SR: LOADING..." until WiFi finished; "RECIEVED" fixed on screen. - The personal hotspots (EHTISHAM, A16, okay., StormFiber) are gone: this is a public repo. One alternative network and one alternative broker remain commented, as the convention says. Kept on purpose: an Operator message still carries the PREVIOUS card's uid. That is a server-facing contract and changes separately. version.txt is NOT bumped - nothing reaches a box until it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>main
parent
0d7c5781b0
commit
133cfbac1e
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -21,18 +21,18 @@ Devices update themselves at boot from this repo's `main` branch on Gitea, over
|
|||
|
||||
## How the program works
|
||||
|
||||
Single-file, cooperative main loop: `setup()` once, then `while True: loop()`. No threads or asyncio; all state is module-level globals (functions declare `global`). Anything added to the loop must stay non-blocking: timers use `utime.ticks_ms()` deadlines (`ack_deadline`, the 5 s throttles in `check_wifi()` / `reconnect_mqtt()`), not sleeps. The exception is `connect_wifi()`, which blocks and retries recursively up to `MAX_RETRIES`; `loop()` falls back to it whenever WiFi is down.
|
||||
Single-file, cooperative main loop: `setup()` once, then `while True: loop()`. No threads or asyncio; all state is module-level globals (functions declare `global`). Anything added to the loop must stay non-blocking: timers use `utime.ticks_ms()` deadlines (`ack_deadline`, the 5 s throttle in `reconnect_mqtt()`), not sleeps. The exception is `connect_wifi()`, which blocks and retries recursively up to `MAX_RETRIES`; `loop()` falls back to it whenever WiFi is down.
|
||||
|
||||
### Identity and persistence
|
||||
|
||||
- `serial` = hex of `machine.unique_id()`. It is the MQTT client id, the `device_serial` in every published message, and what every incoming message is matched against. Shown on screen as `SR:`.
|
||||
- NVS namespace `rfid_data` (`load_from_nvs()` / `save_to_nvs()`): `count` (product counter), `color` (last lamp colour), `operator_id`, and `uid_count` + `uid_<n>` (the `seen_uids` set, no longer used for counting). These survive reboots; a `Reset` card or the reset topic clears them.
|
||||
- NVS namespace `rfid_data` (`load_from_nvs()` / `save_to_nvs()`): `count` (product counter), `color` (last lamp colour) and `operator_id`. Older builds also wrote `uid_count` + `uid_<n>` (a set of seen UIDs that never affected anything); those keys are left in place and ignored. These survive reboots; a `Reset` card or the reset topic clears them.
|
||||
|
||||
### Card handling (`loop()`)
|
||||
|
||||
- `read_all_fields()` reads the card's text blocks positionally with lengths `[4, 5, 6, 8, 9, 10, 12]` -> `(sku, color, size, article, remarks, cardtype, operator_id)`. Only `cardtype` and `operator_id` still matter; the first five are read but no longer published.
|
||||
- `read_card_fields()` reads two MIFARE Classic blocks from the card: block 10 (`CARD_TYPE_BLOCK`, the type as text) and block 12 (`OPERATOR_ID_BLOCK`). Blocks 4-9 once held sku / colour / size / article / remarks and are no longer read; each `RFIDUnit.read(block)` authenticates its own sector, so the reads are independent. The values are written into the card by the desk writer that the UiMetrix "Register Card Type" page drives - the box never consults the database.
|
||||
- `cardtype` selects the behaviour: `Operator` sets and persists `Operator_ID`, `Product` publishes a scan, `Reset` zeroes the counter and operator and publishes a `RESET` message. Anything else is ignored.
|
||||
- The `card` flag edge-detects a tap: set when a card is handled, cleared only once no card is present, so one tap publishes once.
|
||||
- `tap_handled` edge-detects a tap: set when a card is acted on (all three types, Reset included since 2026-09-15), cleared only once no card is present, so one tap publishes once however long the card rests on the reader.
|
||||
- After every publish the device enters `waiting_for_ack` for 10 s: further cards are ignored ("WAITING FOR RESPONSE") until a `response_data` (or `lamp_topic`) message addressed to this serial arrives, or the deadline passes ("NO RESPONSE RECIEVED"). `tag_counter` increments only on a positive ACK from the server, never on the scan itself, so the on-screen count is the server's count.
|
||||
- Quirk: the `Operator` branch publishes before `last_uid_str` is updated, so an operator message carries the previous card's UID (`-` after boot); a `Product` message carries the current UID.
|
||||
|
||||
|
|
@ -56,16 +56,16 @@ Subscribes (each handler first checks the serial in the payload; messages for ot
|
|||
### Connectivity and the light bar
|
||||
|
||||
- Blue on the RGB unit means "not connected" (boot, WiFi down, MQTT down); once MQTT connects the persisted `lamp_color` (default green) is restored. After that, colours come only from `lamp_topic`.
|
||||
- A publish failure calls `reconnect_mqtt()`, which tears the client down and re-runs `init_mqtt()`, throttled to once per 5 s. A `check_msg()` error sets `mqtt_client = None`, which takes the same path on the next loop.
|
||||
- A publish failure calls `reconnect_mqtt()`, which tears the client down and re-runs `init_mqtt()`, throttled to once per 5 s. A `check_msg()` or heartbeat-publish error sets `mqtt_client = None`, which takes the same path on the next loop.
|
||||
- Status icons (WiFi ok/error, MQTT cloud/error, charging) are "updated" by drawing a new `Widgets.Image` over the old one at fixed coordinates.
|
||||
|
||||
### Screen and hardware
|
||||
|
||||
- `init_ui()` draws everything with `M5.Widgets` at absolute 320x240 coordinates and keeps the handles in the `ui_elements` dict; change text through `safe_label_update()`. `label3` and `label5` are referenced but never created, so updates to them are no-ops. The battery bar is an `m5ui.M5Bar`.
|
||||
- `init_ui()` draws everything with `M5.Widgets` at absolute 320x240 coordinates and keeps the handles in the `ui` dict under plain names (`operator`, `count`, `msg`, `uid`, `serial`, `ip`, `battery`, `version`, the icons); change text through `label_set(name, text)` / `set_msg(text)`, which tolerate a widget that has not been created yet (that is what lets `setup()` load NVS before drawing). The battery bar is an `m5ui.M5Bar`.
|
||||
- Images are loaded from `/flash/res/img/` on the device (`Logo`, `emp`, `mach`, `rfid-tag-log`, `bar_c`, `wifi`, `wifi_error`, `MQTT_Cloud`, `mqtt_error`, `charging`, `not_charging`, all `.jpg`); they are not in this directory.
|
||||
- RFID Unit on I2C bus 0 (SCL pin 1, SDA pin 2, 100 kHz); if any RFID call throws, `rfid_re_init` makes the next loop pass rebuild the bus and reader. RGB Unit on pins 8/9 with 10 LEDs.
|
||||
- RFID Unit on I2C bus 0 (SCL pin 1, SDA pin 2, 100 kHz); if any RFID call throws, `rfid_needs_init` makes the next loop pass rebuild the bus and reader (`init_rfid()`). RGB Unit on pins 8/9 with 10 LEDs.
|
||||
|
||||
## Conventions
|
||||
|
||||
- WiFi SSID/password and the MQTT broker/user/password are hardcoded near the top of the file, with previously used networks left commented out; switching networks means commenting/uncommenting those lines. The file already holds live credentials, so don't echo them into other files or chat.
|
||||
- WiFi SSID/password and the MQTT broker/user/password are hardcoded near the top of the file, with one alternative each left commented out (`UI-Matrix`, `scada.utopia.pk`); switching means commenting/uncommenting those lines. Personal hotspots that used to be listed were removed on 2026-09-15 - this is a public repo, do not add them back. The file already holds live credentials, so don't echo them into other files or chat.
|
||||
- The serial console never prints the card UID (only MQTT traffic, NVS saves/loads, connection state and errors), which is why the enrolment tool reads UIDs from MQTT instead of the USB port.
|
||||
|
|
|
|||
Loading…
Reference in New Issue