Add M5Stack RFID reader firmware and developer guide

Import the UiMetrix tag-reader firmware (UIFlow 2.0 MicroPython script) as
exported from the device, plus a CLAUDE.md describing the OTA release path,
the MQTT contract with the UiMetrix server and the scan/ACK lifecycle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main
muhammad.faique 2026-09-11 16:00:51 +05:00
commit 7050a7ef51
2 changed files with 842 additions and 0 deletions

69
CLAUDE.md Normal file
View File

@ -0,0 +1,69 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 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).
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 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)
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` to `/flash/main_ota_temp.py`, stores the new version in NVS and resets. A release is therefore: push the new `main.py` and bump `version.txt`. Two things to keep in mind:
- Versions compare as floats, so `1.10` is older than `1.9`.
- Nothing in this file copies `main_ota_temp.py` over `main.py`; that promotion has to happen elsewhere on the device (e.g. its `boot.py`). Confirm it exists before relying on OTA, because the new version is stamped in NVS before the reset either way, after which the device reports "Already latest version".
## 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.
### 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.
### 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.
- `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.
- 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.
### MQTT contract
Broker, port and credentials are constants at the top of the file and must match the broker the UiMetrix server uses. All messages are JSON, QoS 0.
Publishes:
- `send_receive_data``{"card_type", "operator_id", "device_type": "tag_reader", "uid", "device_serial"}`. The server routes on `card_type` / `device_type`.
- `M5devices/<serial>` — an empty object on every idle loop pass (several times a second), a liveness heartbeat.
Subscribes (each handler first checks the serial in the payload; messages for other devices are ignored):
- `response_data``{"device_serial", "ack": 0|1, "output"}`. `ack == 1` increments the counter and shows `output` in the MSG bar; `ack == 0` only shows `output`.
- `lamp_topic``{"serial", "color": "red"|"green"|"yellow"|"purple"|"blue"}` sets the RGB unit and persists the colour to NVS.
- `reset-topic` — payload `reset` clears the counter and UIDs (keeps the operator).
`mqtt_callback` accepts both the 2-argument and 4-argument umqtt callback signatures.
### 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.
- 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`.
- 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.
## 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.
- 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.

View File

@ -0,0 +1,773 @@
import os, sys, io
import M5
from M5 import *
import network
import m5ui
import lvgl as lv
from unit import RFIDUnit
from hardware import I2C, Pin
import time
from umqtt.simple import MQTTClient
import esp32
from esp32 import NVS
import ujson
import utime
#from audio import Player
from machine import unique_id
import ubinascii
from unit import RGBUnit
import requests
import machine
#player = None
serial = ubinascii.hexlify(unique_id()).decode()
mqtt_last_try = 0
MQTT_RECONNECT_INTERVAL = 5000 # 5 seconds
wifi_last_try = 0
WIFI_RECONNECT_INTERVAL = 5000
# Constants and Configuration
#WIFI_SSID = 'UI-Matrix'
#WIFI_PASS = 'Uimatrix01'
#WIFI_SSID = 'EHTISHAM'
#WIFI_PASS = '123456789'
WIFI_SSID = 'Utopia-WiFi'
WIFI_PASS = '@!nt3lGwn#1'
#WIFI_SSID = 'A16'
#WIFI_PASS = '12345678'
#WIFI_SSID = 'okay.'
#WIFI_PASS = '125125125'
#WIFI_SSID = 'StormFiber-2.4G'
#WIFI_PASS = '22733801'
import requests
import machine
nvs = esp32.NVS("storage")
def get_local_version():
try:
return nvs.get_str("fw_version")
except:
return "1.0" # default first install
def set_local_version(version):
try:
nvs.set_str("fw_version", version)
nvs.commit()
print("Version saved:", version)
except Exception as e:
print("NVS save error:", e)
local_version = get_local_version()
def check_for_update():
try:
print("Checking for update...")
r = requests.get(UPDATE_VERSION_URL)
remote_version = r.text.strip()
r.close()
print("Remote:", remote_version)
print("Local :", local_version)
if float(remote_version) > float(local_version):
print("New version found. Updating...")
r = requests.get(UPDATE_FILE_URL)
new_code = r.text
r.close()
with open("/flash/main_ota_temp.py", "w") as f:
f.write(new_code)
print("Update downloaded. Restarting...")
set_local_version(remote_version)
machine.reset()
else:
print("Already latest version")
except Exception as e:
print("Update check failed:", e)
UPDATE_VERSION_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/version.txt"
UPDATE_FILE_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/main.py"
MQTT_CLIENT_ID = serial
MQTT_BROKER = '192.168.87.11'
#MQTT_BROKER = 'scada.utopia.pk'
MQTT_PORT = 1883
MQTT_USER = 'utopia'
MQTT_PASSWORD = 'utopia01'
MQTT_RESET_TOPIC = 'reset-topic'
RESET_UID = "D3:58:24:F8:00:00:00:00:00:00"
NVS_NAMESPACE = "rfid_data"
MAX_RETRIES = 5
MQTT_TOPIC = "send_receive_data"
MQTT_LAMP = "lamp_topic"
DEVICE_TYPE = "tag_reader"
DEVICE_NUMBER = serial
MQTT_RESPONSE_DATA = 'response_data'
# UI Elements
ui_elements = {
'label3': None, # Last UID display
'label4': None, # IP address
'label5': None, # Card present status
'label6': None, # Current UID
'title0': None, # Title
'label7': None, # "PROD COUNT:" label
'label8': None, # Product counter
'label1': None, # "TAG UID:" label
'line0': None, # Divider line
'image1': None,
'label9': None, # Operator ID
'label10': None, #MSG
'label2': None,
'rect0': None,
'rect1': None ,
'rect2': None,
'rect3':None,
'rect4':None,
'image0': None,
'image1': None,
'image2': None,
'image3': None,
'image4': None,
'image5': None,
'image6': None,
'label12': None,
'bettery_per': None,
'charging_icon':None
}
bettery_icon = None
card=0 # card insert=1 card not insert =0
rfid_re_init=0
# System Components
wlan = None
i2c0 = None
rfid_0 = None
rgb_0=None
mqtt_client = None
Operator_ID = "-"
lamp_color = 0
tag_counter = 0
seen_uids = set()
wifi_retry_count = 0
last_uid_str= "-"
# --- NEW GLOBALS FOR ACK WAIT ---
waiting_for_ack = False
ack_deadline = 0
# --------------------------------
def read_all_fields():
lengths = [4, 5, 6, 8, 9, 10, 12]
values = []
for length in lengths:
try:
value = rfid_0.read(length).decode('utf-8').strip('\x00')
except:
value = None
values.append(value)
return tuple(values)
def get_datetime():
"""Get current datetime in ISO 8601 format (YYYY-MM-DDTHH:MM:SS)"""
now = utime.localtime()
return "{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}".format(
now[0], now[1], now[2], now[3], now[4], now[5])
def safe_label_update(label, text):
"""Safely update label text if label exists"""
if label is not None:
label.setText(str(text))
def load_from_nvs():
global tag_counter, seen_uids, Operator_ID,lamp_color
try:
nvs = NVS(NVS_NAMESPACE)
# Load tag counter
try:
tag_counter = nvs.get_i32("count")
except:
tag_counter = 0
nvs.set_i32("count", tag_counter)
nvs.commit()
# Load last UID
try:
lamp_color = nvs.get_i32("color")
print(f" load from nvs {lamp_color}")
except:
lamp_color = 0x008000
nvs.set_i32("color", lamp_color)
nvs.commit()
# Load seen UIDs
try:
uid_count = nvs.get_i32("uid_count")
seen_uids = set()
for i in range(uid_count):
uid_key = f"uid_{i}"
seen_uids.add(nvs.get_str(uid_key))
except:
seen_uids = set()
nvs.set_i32("uid_count", 0)
nvs.commit()
# Load Operator ID
try:
Operator_ID = nvs.get_str("operator_id")
if Operator_ID == "" or Operator_ID == "NA":
Operator_ID = "-"
except:
Operator_ID = "-"
nvs.set_str("operator_id", Operator_ID)
nvs.commit()
# Update UI with loaded values
safe_label_update(ui_elements['label8'], str(tag_counter))
#safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
safe_label_update(ui_elements['label9'], str(Operator_ID))
except Exception as e:
print("NVS Load Error:", e)
tag_counter = 0
lamp_color = 0x008000
seen_uids = set()
Operator_ID = "-"
def save_to_nvs():
global lamp_color
try:
nvs = NVS(NVS_NAMESPACE)
nvs.set_i32("count", tag_counter)
nvs.set_i32("color", lamp_color)
print(f"save to nvs {lamp_color}")
nvs.set_str("operator_id", Operator_ID)
# Save seen UIDs
nvs.set_i32("uid_count", len(seen_uids))
for i, uid in enumerate(seen_uids):
nvs.set_str(f"uid_{i}", uid)
nvs.commit()
except Exception as e:
print("NVS Save Error:", e)
def publish_rfid_data(sku, color, size, article, remarks, uid, count, cardtype, Operator_ID, serial):
"""Publish RFID data as JSON message with datetime timestamp"""
global waiting_for_ack
if waiting_for_ack:
print("Publish blocked (waiting for ACK)")
return
try:
if mqtt_client:
message = {
"card_type": cardtype,
"operator_id": Operator_ID,
#"sku": sku,
#"color": color,
#"size": size,
#"article": article,
#"remarks": remarks,
"device_type": DEVICE_TYPE,
#"device_number": DEVICE_NUMBER,
"uid": uid,
#"count": count,
#"timestamp": get_datetime(),
"device_serial": serial
}
mqtt_client.publish(MQTT_TOPIC, ujson.dumps(message), qos=0)
except Exception as e:
print("MQTT Publish Error:", e)
reconnect_mqtt()
def init_ui():
"""Initialize all UI elements with null checks"""
try:
Widgets.fillScreen(0xffffff)
#Version info
#employee ID
ui_elements['rect0'] = Widgets.Rectangle(4, 101, 152, 55, 0x616161, 0xffffff)
ui_elements['image1'] = Widgets.Image("/flash/res/img/emp.jpg", 5, 102, scale_x=1, scale_y=1)
#Operator ID
ui_elements['label9'] = Widgets.Label(str(Operator_ID), 60, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
#PRoduction Count / Sewing machine
ui_elements['rect1'] = Widgets.Rectangle(160, 101, 155, 55, 0x616161, 0xffffff)
ui_elements['image0'] = Widgets.Image("/flash/res/img/mach.jpg", 165, 102, scale_x=1, scale_y=1)
ui_elements['label8'] = Widgets.Label(str(tag_counter), 220, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
#RESPONSE DATA MESSAGE
ui_elements['rect2'] = Widgets.Rectangle(4, 160, 311, 25, 0x616161, 0xffffff)
ui_elements['label10'] = Widgets.Label("MSG : ", 8, 166, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
#RFID PRESENT TAG UID
ui_elements['rect3'] = Widgets.Rectangle(4, 189, 311, 47, 0x616161, 0xffffff)
ui_elements['image3'] = Widgets.Image("/flash/res/img/rfid-tag-log.jpg", 5, 190, scale_x=1, scale_y=0.9)
ui_elements['label6'] = Widgets.Label("-", 60, 205, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
# Device Serial no
ui_elements['rect4'] = Widgets.Rectangle(4, 47, 311, 50, 0x616161, 0xffffff)
ui_elements['image4'] = Widgets.Image("/flash/res/img/bar_c.jpg", 5, 58, scale_x=1.2, scale_y=1.2)
# WIFI & MQTT LOGO
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi.jpg", 149, 3, scale_x=0.9, scale_y=1)
ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
ui_elements['label4'] = Widgets.Label("IP: Connecting...", 205, 35, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu9)
# Utopia Logo
ui_elements['image6'] = Widgets.Image("/flash/res/img/Logo.jpg", 0, 0)
# Bettery %
ui_elements['bettery_per'] =Widgets.Label("b%", 230, 15,1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
#Version info
ui_elements['label12'] = Widgets.Label(f"V{local_version}", 275, 55, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
except Exception as e:
print("UI Init Error:", e)
def connect_wifi():
global wlan, wifi_retry_count
if not wlan.isconnected():
try:
safe_label_update(ui_elements['label4'], "WiFi: Connecting...")
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
rgb_0.fill_color(0x0000FF)
wlan.connect(WIFI_SSID, WIFI_PASS)
# Wait for connection with timeout
timeout = 10 # 20 seconds timeout
while not wlan.isconnected() and timeout > 0:
time.sleep_ms(500)
timeout -= 0.5
M5.update()
if wlan.isconnected():
ip_address = wlan.ifconfig()[0]
safe_label_update(ui_elements['label4'], "IP:" + ip_address)
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi.jpg", 149, 3, scale_x=0.9, scale_y=1)
rgb_0.fill_color(lamp_color)
else:
safe_label_update(ui_elements['label4'], "WiFi: Timeout")
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
rgb_0.fill_color(0x0000FF)
wifi_retry_count += 1
if wifi_retry_count < MAX_RETRIES:
time.sleep(2)
connect_wifi()
except Exception as e:
safe_label_update(ui_elements['label4'], "WiFi: Error")
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
print("WiFi Error:", e)
rgb_0.fill_color(0x0000FF)
wifi_retry_count += 1
if wifi_retry_count < MAX_RETRIES:
time.sleep(2)
connect_wifi()
def check_wifi():
global wifi_last_try
if wlan.isconnected():
ip = wlan.ifconfig()[0]
safe_label_update(ui_elements['label4'], "IP:" + ip)
return
now = utime.ticks_ms()
if utime.ticks_diff(now, wifi_last_try) < WIFI_RECONNECT_INTERVAL:
return
wifi_last_try = now
print("WiFi reconnecting...")
try:
wlan.disconnect()
except:
pass
wlan.connect(WIFI_SSID, WIFI_PASS)
def init_wifi():
global wlan
try:
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.config(reconnects=MAX_RETRIES)
connect_wifi()
except Exception as e:
print("WiFi Init Error:", e)
safe_label_update(ui_elements['label4'], "WiFi Error")
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
rgb_0.fill_color(0x0000FF)
def mqtt_callback(*args):
"""Universal callback that handles all MQTT library versions"""
global tag_counter, seen_uids, last_uid_str, Operator_ID, output, ack, target_serial, data, lamp_color, waiting_for_ack
try:
# Determine callback signature based on args length
if len(args) == 2: # Standard umqtt.simple (topic, message)
topic, msg = args
elif len(args) == 4: # Some variants (topic, msg, retained, duplicate)
topic, msg, _, _ = args
else:
print("Unsupported callback format")
return
# Ensure proper string decoding
topic = topic.decode('utf-8') if isinstance(topic, bytes) else topic
msg = msg.decode('utf-8') if isinstance(msg, bytes) else msg
print(f"MQTT: {topic} -> {msg}")
if topic.endswith(MQTT_RESET_TOPIC) and msg.lower() == 'reset':
# Reset all counters and data
seen_uids.clear()
tag_counter = 0
last_uid_str = "-"
#Operator_ID = "-"
# Update UI
safe_label_update(ui_elements['label8'], "0")
safe_label_update(ui_elements['label3'], "Last UID: - (Reset)")
#safe_label_update(ui_elements['label9'], "-") #Label for Operator ID
safe_label_update(ui_elements['label6'], "-")
safe_label_update(ui_elements['label10'], "MSG : ")
# Persist state
save_to_nvs()
print("System reset via MQTT")
if topic.endswith(MQTT_RESPONSE_DATA):
try:
data = ujson.loads(msg) # decode JSON payload
target_serial = data.get("device_serial")
ack = data.get("ack")
output=data.get("output")
# Only act if the message is for THIS device
print(f"target sr= {target_serial}")
if target_serial == serial and ack == 1:
# ACK received for this device
waiting_for_ack = False
print(output)
tag_counter += 1
safe_label_update(ui_elements['label8'], tag_counter)
safe_label_update(ui_elements['label10'], f"MSG : {output}")
save_to_nvs()
elif target_serial == serial and ack == 0:
# Negative ACK but still targeted to this device
waiting_for_ack = False
safe_label_update(ui_elements['label10'], f"MSG : {output}")
print(output)
else:
print("Message ignored, condition does not match")
#safe_label_update(ui_elements['label10'], f"MSG : ")
except Exception as e:
print("response data error:", e)
if topic.endswith(MQTT_LAMP):
try:
data = ujson.loads(msg) # decode JSON payload
target_serial = data.get("serial")
color = data.get("color")
# Only act if the message is for THIS device
if target_serial == serial:
# Clear waiting flag if message is specifically for this device
waiting_for_ack = False
if color == "red":
print("Set light RED")
rgb_0.fill_color(0xff0000)
lamp_color = 0xff0000
save_to_nvs()
elif color == "green":
print("Set light GREEN")
rgb_0.fill_color(0x008000)
lamp_color = 0x008000
save_to_nvs()
elif color == "yellow":
print("Set light yellow")
rgb_0.fill_color(0xffff00)
lamp_color = 0xffff00
save_to_nvs()
elif color == "purple":
print("Set light purple")
rgb_0.fill_color(0x800080)
lamp_color = 0x800080
save_to_nvs()
elif color == "blue":
print("Set light blue")
rgb_0.fill_color(0x0000FF)
lamp_color = 0x0000FF
save_to_nvs()
else:
pass
else:
print("Message ignored, serial does not match")
except Exception as e:
print("QC-status topic parse error:", e)
except Exception as e:
print("MQTT Callback Error:", e)
def init_mqtt():
global mqtt_client
try:
if not wlan.isconnected():
print("WiFi not connected")
return
mqtt_client = MQTTClient(
client_id=MQTT_CLIENT_ID,
server=MQTT_BROKER,
port=MQTT_PORT,
user=MQTT_USER,
password=MQTT_PASSWORD,
keepalive=60,
ssl=False
)
mqtt_client.set_callback(mqtt_callback)
mqtt_client.connect()
mqtt_client.subscribe(MQTT_RESET_TOPIC)
mqtt_client.subscribe(MQTT_RESPONSE_DATA)
mqtt_client.subscribe(MQTT_LAMP)
print("MQTT Connected")
ui_elements['image5'] = Widgets.Image("/flash/res/img/MQTT_Cloud.jpg",100,0)
rgb_0.fill_color(lamp_color)
except Exception as e:
print("MQTT init error:", e)
mqtt_client = None
ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
def reconnect_mqtt():
global mqtt_client, mqtt_last_try
now = utime.ticks_ms()
# prevent continuous reconnect spam
if utime.ticks_diff(now, mqtt_last_try) < MQTT_RECONNECT_INTERVAL:
return
mqtt_last_try = now
print("MQTT reconnect attempt")
try:
if mqtt_client:
try:
mqtt_client.disconnect()
except:
pass
mqtt_client = None
if not wlan.isconnected():
print("WiFi not connected, skipping MQTT reconnect")
ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
return
init_mqtt()
except Exception as e:
print("MQTT reconnect error:", e)
ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
mqtt_client = None
def loop():
global tag_counter, seen_uids, last_uid_str, mqtt_client, serial, player, Operator_ID ,battery_per , bettery_icon, card,rfid_re_init,i2c0,rfid_0,lamp_color, waiting_for_ack, ack_deadline
#M5.update()
serial = ubinascii.hexlify(unique_id()).decode()
check_wifi()
time.sleep(0.1)
bettery_icon.set_value(Power.getBatteryLevel(), True)
#bettery_per.set_text(f"{str(Power.getBatteryLevel())}%")
safe_label_update(ui_elements['bettery_per'], str(Power.getBatteryLevel()))
if Power.isCharging():
ui_elements['charging_icon'] = Widgets.Image("/flash/res/img/charging.jpg", 305, 11)
else:
ui_elements['charging_icon'] = Widgets.Image("/flash/res/img/not_charging.jpg", 305,11)
if not wlan.isconnected():
connect_wifi()
if wlan.isconnected():
init_mqtt()
print("MQTT RE-INITIALIZE SUCCESS")
if not wlan.isconnected():
time.sleep(1)
return
# Check for MQTT messages
try:
if mqtt_client:
mqtt_client.check_msg()
except Exception as e:
print("MQTT error:", e)
mqtt_client = None
# --- ACK TIMEOUT HANDLING (non-blocking) ---
if waiting_for_ack:
# utime.ticks_diff returns positive when now >= ack_deadline
if utime.ticks_diff(utime.ticks_ms(), ack_deadline) >= 0:
print("ACK TIMEOUT: No response received")
safe_label_update(ui_elements['label10'], "MSG : NO RESPONSE RECIEVED")
waiting_for_ack = False
# ------------------------------------------------
# Ensure MQTT connection
if wlan.isconnected() and mqtt_client is None:
reconnect_mqtt()
# RFID Processing
try:
if rfid_re_init==1:
i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
rfid_0 = RFIDUnit(i2c0)
rfid_re_init=0
print("RFID RE_INITIALIZED")
else:
pass
card_present = rfid_0.is_new_card_present() if rfid_0 else False
safe_label_update(ui_elements['label5'], card_present)
if waiting_for_ack:
# Show card is ignored during waiting
if card_present:
safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
return
if card_present:
current_uid = rfid_0.read_card_uid() if rfid_0 else None
if current_uid:
# Convert UID bytearray to consistent string format
uid_str = ':'.join(['%02X' % b for b in current_uid])
safe_label_update(ui_elements['label6'], uid_str)
# Read all fields from the card
sku, color, size, article, remarks, cardtype, new_operator_id = read_all_fields()
rfid_0.close()
# Update Operator ID if we have a new valid ID
if new_operator_id and new_operator_id != '0' and cardtype=='Operator' and card==0:
card=1
Operator_ID = new_operator_id
safe_label_update(ui_elements['label9'], str(Operator_ID))
# Save the new Operator ID immediately
save_to_nvs()
publish_rfid_data(sku, color, size, article, remarks, last_uid_str, tag_counter, cardtype, Operator_ID, serial)
# --- START WAIT FOR ACK (seconds) ---
safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
waiting_for_ack = True
ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
# ---------------------------------------
#player.play_tone(2000, 0.04, volume=100, sync=True)
#player.play("file://flash/res/audio/beep.mp3", pos=0, volume=100, sync=True)
#if uid_str == RESET_UID:
if cardtype == "Reset":
# Reset logic
seen_uids.clear()
tag_counter = 0
last_uid_str = "-"
Operator_ID = "0"
safe_label_update(ui_elements['label8'], "0")
safe_label_update(ui_elements['label3'], "Last UID: - (Reset)")
safe_label_update(ui_elements['label9'], "-")
safe_label_update(ui_elements['label10'], "MSG : ")
save_to_nvs()
publish_rfid_data("RESET", "RESET", "RESET", "RESET", "RESET", "RESET", "RESET", "RESET", 0, serial)
# --- START WAIT FOR ACK (seconds) AFTER RESET ---
safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
waiting_for_ack = True
ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
# ---------------------------------------
return
# Update last UID shown
last_uid_str = uid_str
safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
#if uid_str not in seen_uids and cardtype == 'Product':
if cardtype == 'Product' and card==0:
card=1
#seen_uids.add(uid_str)
#if cardtype == 'Product':
# tag_counter += 1
safe_label_update(ui_elements['label8'], str(tag_counter))
save_to_nvs()
publish_rfid_data(sku, color, size, article, remarks, last_uid_str, tag_counter, cardtype, Operator_ID, serial)
# --- START WAIT FOR ACK (seconds) ---
safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
waiting_for_ack = True
ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
# ---------------------------------------
#player.play_tone(2000, 0.04, volume=100, sync=True)
#player.play("file://flash/res/audio/beep.mp3", pos=0, volume=100, sync=True)
else:
safe_label_update(ui_elements['label6'], "-")
safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
card=0
#mqtt_client.publish(f"M5devices/{DEVICE_NUMBER}", ujson.dumps({"online":"True","operator_id":f"{Operator_ID}","serial":f"{serial}"}), qos=0)
if mqtt_client:
mqtt_client.publish(f"M5devices/{DEVICE_NUMBER}", ujson.dumps({}), qos=0)
except Exception as e:
print("RFID Processing Error:", e)
safe_label_update(ui_elements['label6'], "RFID Error")
rfid_re_init=1
M5.update()
time.sleep(0.1)
def setup():
global wlan, i2c0, rfid_0, mqtt_client, label2, player , battery_per , bettery_icon , rfid_re_init,rgb_0,lamp_color
#machine.WDT(timeout=60000)
M5.begin()
m5ui.init()
bettery_icon = m5ui.M5Bar(x=256, y=7, w=49, h=25, min_value=0, max_value=100, value=25, bg_c=0x616161, color=0x21f398)
#player = Player(None)
rgb_0 = RGBUnit((8, 9), 10)
load_from_nvs()
print(lamp_color)
#if lamp_color==16776960:
#rgb_0.fill_color(0xffff00)
#rgb_0.fill_color(lamp_color)
rgb_0.fill_color(0x0000FF)
init_ui()
label2 = Widgets.Label("SR: LOADING...", 70, 70, 1.0,0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
init_wifi()
if wlan.isconnected():
check_for_update() #check update files from git hub
serial = ubinascii.hexlify(unique_id()).decode()
label2.setText("SR: " + serial)
label2.setColor(0x000000, 0xffffff)
# Initialize RFID reader
try:
i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
rfid_0 = RFIDUnit(i2c0)
rfid_re_init=0
except Exception as e:
print("RFID Init Error:", e)
safe_label_update(ui_elements['label6'], "RFID Error")
rfid_re_init=1
# Initialize MQTT
init_mqtt()
#rgb_0.fill_color(0x33ff33)
if __name__ == '__main__':
try:
setup()
while True:
loop()
except Exception as e:
print("Main Error:", e)
try:
from utility import print_error_msg
print_error_msg(e)
except ImportError:
print("Please update firmware")