Boxes were updating from a dead GitHub repo and talking to the old broker

The program pointed its boot-time update check at a personal GitHub repo
(husbananjum/m5core-s3-UIMETRIX-Z87) that now answers 404 for both
version.txt and main.py, so every box on that build has been failing its
check on every boot - harmlessly, but no release could ever reach them. It
also had the broker at 192.168.87.11, while the boxes on the floor (and the
UiMetrix server) are on 192.168.2.174.

Updates now come from this repo on the company Gitea, through its anonymous
raw-file URLs. For that the program is committed as main.py - the name the
device downloads and runs - instead of "M5-Stack Code for Uimetrix.txt", and
version.txt is added. The old broker stays as a commented line, the way the
earlier networks are kept.

- A push to main that also bumps version.txt IS a release now; every box that
  follows Gitea installs it at its next boot. Leave version.txt alone until
  main.py is ready for the floor.
- No box follows Gitea yet: the ones with OTA code still point at the dead
  GitHub URL, and the ones on the floor run a build without OTA at all. Each
  needs one USB copy of this main.py; after that they update by themselves.
- version.txt starts at 2.0. Nobody knows the highest number ever published
  on GitHub, and a box's NVS may hold a number it never actually ran; if any
  box shows a higher V... on its screen, bump version.txt above it.
- A box freshly copied over USB still has its old NVS version (or the default
  1.0), so on its first boot it downloads the identical main.py once and
  resets. Expected, not a loop.
- The repo stores LF; the CRLF seen in a Windows checkout is core.autocrlf.
  Gitea serves the stored LF bytes, which is what the device compiles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
m.faisal 2026-09-15 16:15:03 +05:00
parent 4d487d3c58
commit 0d7c5781b0
3 changed files with 10 additions and 6 deletions

View File

@ -4,18 +4,20 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this repository is ## What this repository is
Firmware for the UiMetrix shop-floor RFID tag-reader stations: an M5Stack CoreS3 with an RFID Unit and an RGB light bar, running MicroPython on the UIFlow 2.0 firmware (`M5`, `m5ui`, `unit`, `hardware`, `umqtt.simple`, `esp32.NVS` APIs). The whole program is the single file `M5-Stack Code for Uimetrix.txt`; on the device it is `/flash/main.py` (the `.txt` extension is just how it was exported; CRLF line endings). Firmware for the UiMetrix shop-floor RFID tag-reader stations: an M5Stack CoreS3 with an RFID Unit and an RGB light bar, running MicroPython on the UIFlow 2.0 firmware (`M5`, `m5ui`, `unit`, `hardware`, `umqtt.simple`, `esp32.NVS` APIs). The whole program is the single file `main.py`, served to the devices exactly as committed (the repo stores LF; the Windows checkout shows CRLF through `core.autocrlf`). It was `M5-Stack Code for Uimetrix.txt` until 2026-09-15; `git log --follow main.py` crosses the rename.
The repository is hosted on the company Gitea at `https://git.utopiadeals.com/UIND/UiMetrix-M5-Firmware` (branch `main`); pushing there does not reach any device, since devices pull from the GitHub repo described under "Releasing to devices". There is no PC-side toolchain: nothing to build, lint or run here, and there are no tests. Changes can only be verified on a device; debug output is `print()` over the USB serial port. The repository is hosted on the company Gitea at `https://git.utopiadeals.com/UIND/UiMetrix-M5-Firmware` (branch `main`), and **that is what the devices update from** (see "Releasing to devices"): a push to `main` that also bumps `version.txt` goes live on every box at its next boot, so keep unfinished work off `main` or leave `version.txt` alone until it is ready. There is no PC-side toolchain: nothing to build, lint or run here, and there are no tests. Changes can only be verified on a device; debug output is `print()` over the USB serial port.
The server that talks to this device is the UiMetrix ASP.NET Core app in `D:\Projects\UiMetrix` (`Services/MqttService.cs`; its own `CLAUDE.md` documents the server side). `D:\Projects\MqttWorkerService` is an older standalone worker that speaks the same protocol, and its `tools/rfid_enrol.py` numbers cards from this device's MQTT messages. The server that talks to this device is the UiMetrix ASP.NET Core app in `D:\Projects\UiMetrix` (`Services/MqttService.cs`; its own `CLAUDE.md` documents the server side). `D:\Projects\MqttWorkerService` is an older standalone worker that speaks the same protocol, and its `tools/rfid_enrol.py` numbers cards from this device's MQTT messages.
## Releasing to devices (OTA) ## Releasing to devices (OTA)
Devices update themselves at boot from the GitHub repo `husbananjum/m5core-s3-UIMETRIX-Z87` (`main` branch): `check_for_update()` fetches `version.txt`, compares it as a float against the `fw_version` string in NVS namespace `storage` (default `"1.0"`), and if newer downloads that repo's `main.py` and hands it to `install_update()`. The download must be HTTP 200, at least 1000 bytes, contain `def setup` and `def loop`, and compile; otherwise it is rejected. Installation writes it to `/flash/main_ota_temp.py`, renames the running program to `/flash/main_prev.py` (a rollback copy for manual recovery over USB), renames the download to `/flash/main.py`, and only then stores the new version in NVS and resets. A rejected download or a failed swap leaves the old program and the old version in place, so the next boot retries. A release is therefore: push the new `main.py` and bump `version.txt`. Things to keep in mind: Devices update themselves at boot from this repo's `main` branch on Gitea, over its anonymous raw-file URLs (`.../raw/branch/main/version.txt` and `.../raw/branch/main/main.py`; the host resolves to a public address, so any box with internet reaches it). `check_for_update()` fetches `version.txt`, compares it as a float against the `fw_version` string in NVS namespace `storage` (default `"1.0"`), and if newer downloads `main.py` and hands it to `install_update()`. The download must be HTTP 200, at least 1000 bytes, contain `def setup` and `def loop`, and compile; otherwise it is rejected. Installation writes it to `/flash/main_ota_temp.py`, renames the running program to `/flash/main_prev.py` (a rollback copy for manual recovery over USB), renames the download to `/flash/main.py`, and only then stores the new version in NVS and resets. A rejected download or a failed swap leaves the old program and the old version in place, so the next boot retries. A release is therefore: push the new `main.py` and bump `version.txt`. Things to keep in mind:
- Versions compare as floats, so `1.10` is older than `1.9`. - Versions compare as floats, so `1.10` is older than `1.9`.
- Builds before 2026-09-11 downloaded the file but never installed it, while still stamping the new version into NVS. Devices running such a build never pick up an OTA release: they need one manual USB copy of the fixed `main.py` first, and because their NVS may already claim a version they never ran, the next `version.txt` must be higher than any number published so far. - Builds before 2026-09-11 downloaded the file but never installed it, while still stamping the new version into NVS. Devices running such a build never pick up an OTA release: they need one manual USB copy of the fixed `main.py` first, and because their NVS may already claim a version they never ran, the next `version.txt` must be higher than any number published so far.
- Until 2026-09-15 the update location was the personal GitHub repo `husbananjum/m5core-s3-UIMETRIX-Z87`, which now returns 404 for both files, so every box on that build has been failing its boot-time check (harmlessly) since. Those boxes, and any box running a build without OTA code at all (the ones with the broker at `192.168.2.174` hardcoded and no `V…` label on screen), need one manual USB copy of the current `main.py` to start following Gitea. `version.txt` was reset to `2.0` at the move; nobody knows the highest number ever published on GitHub, so if a box shows a higher `V…` on its screen, bump `version.txt` above it.
- A box freshly copied over USB still carries its old NVS version (or the default `1.0`), so on its first boot it downloads and installs the identical `main.py` from Gitea once and resets; that is expected, not a loop.
## How the program works ## How the program works

View File

@ -149,15 +149,16 @@ def check_for_update():
print("Update check failed:", e) print("Update check failed:", e)
UPDATE_VERSION_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/version.txt" UPDATE_VERSION_URL = "https://git.utopiadeals.com/UIND/UiMetrix-M5-Firmware/raw/branch/main/version.txt"
UPDATE_FILE_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/main.py" UPDATE_FILE_URL = "https://git.utopiadeals.com/UIND/UiMetrix-M5-Firmware/raw/branch/main/main.py"
OTA_MAIN_PATH = "/flash/main.py" # the program the device runs at boot OTA_MAIN_PATH = "/flash/main.py" # the program the device runs at boot
OTA_TEMP_PATH = "/flash/main_ota_temp.py" # a download lands here first OTA_TEMP_PATH = "/flash/main_ota_temp.py" # a download lands here first
OTA_BACKUP_PATH = "/flash/main_prev.py" # the previous program, kept for manual rollback OTA_BACKUP_PATH = "/flash/main_prev.py" # the previous program, kept for manual rollback
MQTT_CLIENT_ID = serial MQTT_CLIENT_ID = serial
MQTT_BROKER = '192.168.87.11' MQTT_BROKER = '192.168.2.174'
#MQTT_BROKER = '192.168.87.11'
#MQTT_BROKER = 'scada.utopia.pk' #MQTT_BROKER = 'scada.utopia.pk'
MQTT_PORT = 1883 MQTT_PORT = 1883
MQTT_USER = 'utopia' MQTT_USER = 'utopia'

1
version.txt Normal file
View File

@ -0,0 +1 @@
2.0