675 lines
22 KiB
Python
675 lines
22 KiB
Python
import os
|
|
import gc
|
|
import M5
|
|
from M5 import *
|
|
import network
|
|
import m5ui
|
|
from unit import RFIDUnit, RGBUnit
|
|
from hardware import I2C, Pin
|
|
import time
|
|
import utime
|
|
from umqtt.simple import MQTTClient
|
|
from esp32 import NVS
|
|
import ujson
|
|
from machine import unique_id
|
|
import ubinascii
|
|
import requests
|
|
import machine
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration
|
|
# ---------------------------------------------------------------------------
|
|
WIFI_SSID = 'Utopia-WiFi'
|
|
WIFI_PASS = '@!nt3lGwn#1'
|
|
#WIFI_SSID = 'UI-Matrix'
|
|
#WIFI_PASS = 'Uimatrix01'
|
|
|
|
MQTT_BROKER = '192.168.2.174'
|
|
#MQTT_BROKER = 'scada.utopia.pk'
|
|
MQTT_PORT = 1883
|
|
MQTT_USER = 'utopia'
|
|
MQTT_PASSWORD = 'utopia01'
|
|
|
|
MQTT_SCAN_TOPIC = "send_receive_data" # published: one message per card tap
|
|
MQTT_RESPONSE_TOPIC = "response_data" # subscribed: the server's answer to a scan
|
|
MQTT_LAMP_TOPIC = "lamp_topic" # subscribed: light bar colour
|
|
MQTT_RESET_TOPIC = "reset-topic" # subscribed: payload "reset" clears the counter
|
|
DEVICE_TYPE = "tag_reader"
|
|
|
|
UPDATE_VERSION_URL = "https://git.utopiadeals.com/UIND/UiMetrix-M5-Firmware/raw/branch/main/version.txt"
|
|
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_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
|
|
|
|
NVS_NAMESPACE = "rfid_data"
|
|
MAX_RETRIES = 5
|
|
ACK_TIMEOUT_MS = 10000 # how long a tap blocks the reader while the server answers
|
|
MQTT_RECONNECT_INTERVAL = 5000
|
|
WIFI_TIMEOUT_S = 10
|
|
|
|
# Card layout (MIFARE Classic blocks; 7 and 11 are sector trailers). Blocks 4-9 once held
|
|
# sku / colour / size / article / remarks, which are no longer published, so only these two
|
|
# are read now.
|
|
CARD_TYPE_BLOCK = 10 # "Product", "Operator" or "Reset"
|
|
OPERATOR_ID_BLOCK = 12 # employee id, on Operator cards
|
|
|
|
LAMP_COLOURS = {
|
|
"red": 0xff0000,
|
|
"green": 0x008000,
|
|
"yellow": 0xffff00,
|
|
"purple": 0x800080,
|
|
"blue": 0x0000FF,
|
|
}
|
|
DEFAULT_LAMP_COLOUR = 0x008000
|
|
DISCONNECTED_COLOUR = 0x0000FF # blue on the bar means "not connected"
|
|
|
|
IMG = "/flash/res/img/"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Identity and firmware version
|
|
# ---------------------------------------------------------------------------
|
|
serial = ubinascii.hexlify(unique_id()).decode()
|
|
|
|
_version_nvs = NVS("storage")
|
|
|
|
def get_local_version():
|
|
try:
|
|
return _version_nvs.get_str("fw_version")
|
|
except Exception:
|
|
return "1.0" # default first install
|
|
|
|
def set_local_version(version):
|
|
try:
|
|
_version_nvs.set_str("fw_version", version)
|
|
_version_nvs.commit()
|
|
print("Version saved:", version)
|
|
except Exception as e:
|
|
print("NVS save error:", e)
|
|
|
|
local_version = get_local_version()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Runtime state (module-level; functions declare `global` for what they change)
|
|
# ---------------------------------------------------------------------------
|
|
ui = {} # screen widgets by name; label_set() tolerates ones not created yet
|
|
wlan = None
|
|
i2c0 = None
|
|
rfid_0 = None
|
|
rgb_0 = None
|
|
battery_bar = None
|
|
mqtt_client = None
|
|
mqtt_last_try = 0
|
|
wifi_retry_count = 0
|
|
|
|
Operator_ID = "-"
|
|
lamp_color = DEFAULT_LAMP_COLOUR
|
|
tag_counter = 0 # the server's count: only a positive ACK increments it
|
|
last_uid_str = "-"
|
|
tap_handled = False # set once the card on the reader has been acted on, cleared when it leaves
|
|
rfid_needs_init = False
|
|
waiting_for_ack = False
|
|
ack_deadline = 0
|
|
pending_scan_type = None # card type of the scan the server has not answered yet
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OTA update (runs once at boot, when WiFi is up)
|
|
# ---------------------------------------------------------------------------
|
|
def _is_valid_program(code):
|
|
"""Reject error pages, truncated downloads and anything that does not compile."""
|
|
if not code or len(code) < 1000:
|
|
return False
|
|
if "def setup" not in code or "def loop" not in code:
|
|
return False
|
|
try:
|
|
compile(code, "main.py", "exec")
|
|
except SyntaxError:
|
|
return False
|
|
except MemoryError:
|
|
print("Not enough memory to verify the update")
|
|
return False
|
|
return True
|
|
|
|
def install_update(new_code, remote_version):
|
|
"""Write the new program to flash, swap it in as main.py, then record the version."""
|
|
with open(OTA_TEMP_PATH, "w") as f:
|
|
f.write(new_code)
|
|
# Keep the current program as a rollback copy (restore it over USB if the new one misbehaves).
|
|
try:
|
|
os.remove(OTA_BACKUP_PATH)
|
|
except OSError:
|
|
pass
|
|
try:
|
|
os.rename(OTA_MAIN_PATH, OTA_BACKUP_PATH)
|
|
except OSError:
|
|
pass # no main.py yet
|
|
try:
|
|
os.rename(OTA_TEMP_PATH, OTA_MAIN_PATH)
|
|
except OSError:
|
|
# Put the old program back so the device still boots. The old version stays
|
|
# in NVS, so the update is retried on the next boot.
|
|
try:
|
|
os.rename(OTA_BACKUP_PATH, OTA_MAIN_PATH)
|
|
except OSError:
|
|
pass
|
|
raise
|
|
set_local_version(remote_version)
|
|
|
|
def check_for_update():
|
|
try:
|
|
print("Checking for update...")
|
|
|
|
r = requests.get(UPDATE_VERSION_URL)
|
|
try:
|
|
status = getattr(r, "status_code", 200)
|
|
if status != 200:
|
|
print("Version check failed: HTTP", status)
|
|
return
|
|
remote_version = r.text.strip()
|
|
finally:
|
|
r.close()
|
|
|
|
print("Remote:", remote_version)
|
|
print("Local :", local_version)
|
|
|
|
# Compared as floats, so "1.10" is OLDER than "1.9".
|
|
if float(remote_version) <= float(local_version):
|
|
print("Already latest version")
|
|
return
|
|
|
|
print("New version found. Downloading...")
|
|
gc.collect()
|
|
r = requests.get(UPDATE_FILE_URL)
|
|
try:
|
|
status = getattr(r, "status_code", 200)
|
|
if status != 200:
|
|
print("Download failed: HTTP", status)
|
|
return
|
|
new_code = r.text
|
|
finally:
|
|
r.close()
|
|
|
|
if not _is_valid_program(new_code):
|
|
print("Update rejected: downloaded file is not a valid program")
|
|
return
|
|
|
|
install_update(new_code, remote_version)
|
|
print("Update installed. Restarting...")
|
|
machine.reset()
|
|
|
|
except Exception as e:
|
|
print("Update check failed:", e)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Persistence (NVS namespace rfid_data: count, color, operator_id)
|
|
# ---------------------------------------------------------------------------
|
|
def load_from_nvs():
|
|
global tag_counter, Operator_ID, lamp_color
|
|
try:
|
|
nvs = NVS(NVS_NAMESPACE)
|
|
try:
|
|
tag_counter = nvs.get_i32("count")
|
|
except Exception:
|
|
tag_counter = 0
|
|
nvs.set_i32("count", tag_counter)
|
|
nvs.commit()
|
|
try:
|
|
lamp_color = nvs.get_i32("color")
|
|
except Exception:
|
|
lamp_color = DEFAULT_LAMP_COLOUR
|
|
nvs.set_i32("color", lamp_color)
|
|
nvs.commit()
|
|
try:
|
|
Operator_ID = nvs.get_str("operator_id")
|
|
if Operator_ID == "" or Operator_ID == "NA":
|
|
Operator_ID = "-"
|
|
except Exception:
|
|
Operator_ID = "-"
|
|
nvs.set_str("operator_id", Operator_ID)
|
|
nvs.commit()
|
|
print("Loaded from NVS: count", tag_counter, "colour", lamp_color, "operator", Operator_ID)
|
|
except Exception as e:
|
|
print("NVS Load Error:", e)
|
|
tag_counter = 0
|
|
lamp_color = DEFAULT_LAMP_COLOUR
|
|
Operator_ID = "-"
|
|
|
|
def save_to_nvs():
|
|
try:
|
|
nvs = NVS(NVS_NAMESPACE)
|
|
nvs.set_i32("count", tag_counter)
|
|
nvs.set_i32("color", lamp_color)
|
|
nvs.set_str("operator_id", Operator_ID)
|
|
nvs.commit()
|
|
print("Saved to NVS")
|
|
except Exception as e:
|
|
print("NVS Save Error:", e)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Screen
|
|
# ---------------------------------------------------------------------------
|
|
def label_set(name, text):
|
|
label = ui.get(name)
|
|
if label is not None:
|
|
label.setText(str(text))
|
|
|
|
def set_msg(text):
|
|
label_set('msg', "MSG : " + str(text))
|
|
|
|
def show_wifi_icon(ok):
|
|
# Drawn over the previous icon at fixed coordinates; the two files differ slightly in size.
|
|
if ok:
|
|
ui['wifi_icon'] = Widgets.Image(IMG + "wifi.jpg", 149, 3, scale_x=0.9, scale_y=1)
|
|
else:
|
|
ui['wifi_icon'] = Widgets.Image(IMG + "wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
|
|
|
|
def show_mqtt_icon(ok):
|
|
if ok:
|
|
ui['mqtt_icon'] = Widgets.Image(IMG + "MQTT_Cloud.jpg", 100, 0)
|
|
else:
|
|
ui['mqtt_icon'] = Widgets.Image(IMG + "mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
|
|
|
|
def show_battery():
|
|
level = Power.getBatteryLevel()
|
|
battery_bar.set_value(level, True)
|
|
label_set('battery', level)
|
|
icon = "charging.jpg" if Power.isCharging() else "not_charging.jpg"
|
|
ui['charging_icon'] = Widgets.Image(IMG + icon, 305, 11)
|
|
|
|
def init_ui():
|
|
try:
|
|
Widgets.fillScreen(0xffffff)
|
|
# Operator
|
|
ui['operator_box'] = Widgets.Rectangle(4, 101, 152, 55, 0x616161, 0xffffff)
|
|
ui['operator_img'] = Widgets.Image(IMG + "emp.jpg", 5, 102, scale_x=1, scale_y=1)
|
|
ui['operator'] = Widgets.Label(str(Operator_ID), 60, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
|
|
# Production count
|
|
ui['count_box'] = Widgets.Rectangle(160, 101, 155, 55, 0x616161, 0xffffff)
|
|
ui['count_img'] = Widgets.Image(IMG + "mach.jpg", 165, 102, scale_x=1, scale_y=1)
|
|
ui['count'] = Widgets.Label(str(tag_counter), 220, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
|
|
# Server message bar
|
|
ui['msg_box'] = Widgets.Rectangle(4, 160, 311, 25, 0x616161, 0xffffff)
|
|
ui['msg'] = Widgets.Label("MSG : ", 8, 166, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
|
|
# Tag UID
|
|
ui['uid_box'] = Widgets.Rectangle(4, 189, 311, 47, 0x616161, 0xffffff)
|
|
ui['uid_img'] = Widgets.Image(IMG + "rfid-tag-log.jpg", 5, 190, scale_x=1, scale_y=0.9)
|
|
ui['uid'] = Widgets.Label("-", 60, 205, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
|
|
# Device serial
|
|
ui['serial_box'] = Widgets.Rectangle(4, 47, 311, 50, 0x616161, 0xffffff)
|
|
ui['serial_img'] = Widgets.Image(IMG + "bar_c.jpg", 5, 58, scale_x=1.2, scale_y=1.2)
|
|
ui['serial'] = Widgets.Label("SR: " + serial, 70, 70, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
|
|
# Status strip
|
|
show_wifi_icon(True)
|
|
show_mqtt_icon(False)
|
|
ui['ip'] = Widgets.Label("IP: Connecting...", 205, 35, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu9)
|
|
ui['logo'] = Widgets.Image(IMG + "Logo.jpg", 0, 0)
|
|
ui['battery'] = Widgets.Label("b%", 230, 15, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
|
|
ui['version'] = Widgets.Label("V" + str(local_version), 275, 55, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
|
|
except Exception as e:
|
|
print("UI Init Error:", e)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# WiFi
|
|
# ---------------------------------------------------------------------------
|
|
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)
|
|
label_set('ip', "WiFi Error")
|
|
show_wifi_icon(False)
|
|
rgb_0.fill_color(DISCONNECTED_COLOUR)
|
|
|
|
def connect_wifi():
|
|
"""Blocking connect. Retries after a 2 s pause, MAX_RETRIES times over the device's life."""
|
|
global wifi_retry_count
|
|
if wlan.isconnected():
|
|
return
|
|
try:
|
|
label_set('ip', "WiFi: Connecting...")
|
|
show_wifi_icon(False)
|
|
rgb_0.fill_color(DISCONNECTED_COLOUR)
|
|
wlan.connect(WIFI_SSID, WIFI_PASS)
|
|
|
|
timeout = WIFI_TIMEOUT_S
|
|
while not wlan.isconnected() and timeout > 0:
|
|
time.sleep_ms(500)
|
|
timeout -= 0.5
|
|
M5.update()
|
|
|
|
if wlan.isconnected():
|
|
label_set('ip', "IP:" + wlan.ifconfig()[0])
|
|
show_wifi_icon(True)
|
|
rgb_0.fill_color(lamp_color)
|
|
return
|
|
label_set('ip', "WiFi: Timeout")
|
|
except Exception as e:
|
|
print("WiFi Error:", e)
|
|
label_set('ip', "WiFi: Error")
|
|
show_wifi_icon(False)
|
|
rgb_0.fill_color(DISCONNECTED_COLOUR)
|
|
|
|
wifi_retry_count += 1
|
|
if wifi_retry_count < MAX_RETRIES:
|
|
time.sleep(2)
|
|
connect_wifi()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# MQTT
|
|
# ---------------------------------------------------------------------------
|
|
def publish_scan(uid, cardtype, operator_id):
|
|
"""One message per card tap; the server routes on card_type / device_type."""
|
|
if not mqtt_client:
|
|
return
|
|
message = {
|
|
"card_type": cardtype,
|
|
"operator_id": operator_id,
|
|
"device_type": DEVICE_TYPE,
|
|
"uid": uid,
|
|
"device_serial": serial,
|
|
}
|
|
try:
|
|
mqtt_client.publish(MQTT_SCAN_TOPIC, ujson.dumps(message), qos=0)
|
|
except Exception as e:
|
|
print("MQTT Publish Error:", e)
|
|
reconnect_mqtt()
|
|
|
|
def start_ack_wait(cardtype):
|
|
"""Block the reader until the server answers this tap (or ACK_TIMEOUT_MS passes)."""
|
|
global waiting_for_ack, ack_deadline, pending_scan_type
|
|
set_msg("WAITING FOR RESPONSE")
|
|
waiting_for_ack = True
|
|
pending_scan_type = cardtype
|
|
ack_deadline = utime.ticks_add(utime.ticks_ms(), ACK_TIMEOUT_MS)
|
|
|
|
def reset_counter():
|
|
global tag_counter, last_uid_str
|
|
tag_counter = 0
|
|
last_uid_str = "-"
|
|
label_set('count', "0")
|
|
label_set('uid', "-")
|
|
set_msg("")
|
|
|
|
def handle_response(msg):
|
|
global waiting_for_ack, tag_counter
|
|
data = ujson.loads(msg)
|
|
if data.get("device_serial") != serial:
|
|
print("Message ignored, not for this device")
|
|
return
|
|
ack = data.get("ack")
|
|
output = data.get("output")
|
|
if ack == 1:
|
|
waiting_for_ack = False
|
|
# Only a Product scan is production: an accepted Operator or Reset scan is not a piece.
|
|
if pending_scan_type == "Product":
|
|
tag_counter += 1
|
|
label_set('count', tag_counter)
|
|
save_to_nvs()
|
|
set_msg(output)
|
|
elif ack == 0:
|
|
waiting_for_ack = False
|
|
set_msg(output)
|
|
else:
|
|
print("Message ignored, no ack field")
|
|
return
|
|
print(output)
|
|
|
|
def handle_lamp(msg):
|
|
global waiting_for_ack, lamp_color
|
|
data = ujson.loads(msg)
|
|
if data.get("serial") != serial:
|
|
print("Message ignored, serial does not match")
|
|
return
|
|
waiting_for_ack = False # a lamp message for this device also releases a pending tap
|
|
colour = LAMP_COLOURS.get(data.get("color"))
|
|
if colour is None:
|
|
return
|
|
print("Set light", data.get("color"))
|
|
rgb_0.fill_color(colour)
|
|
lamp_color = colour
|
|
save_to_nvs()
|
|
|
|
def mqtt_callback(*args):
|
|
"""Accepts both the 2-argument and 4-argument umqtt callback signatures."""
|
|
if len(args) == 2:
|
|
topic, msg = args
|
|
elif len(args) == 4:
|
|
topic, msg, _, _ = args
|
|
else:
|
|
print("Unsupported callback format")
|
|
return
|
|
try:
|
|
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_counter() # keeps the operator
|
|
save_to_nvs()
|
|
print("System reset via MQTT")
|
|
elif topic.endswith(MQTT_RESPONSE_TOPIC):
|
|
handle_response(msg)
|
|
elif topic.endswith(MQTT_LAMP_TOPIC):
|
|
handle_lamp(msg)
|
|
except Exception as e:
|
|
print("MQTT Callback Error:", e)
|
|
|
|
def init_mqtt():
|
|
global mqtt_client
|
|
if not wlan.isconnected():
|
|
print("WiFi not connected")
|
|
return
|
|
try:
|
|
client = MQTTClient(
|
|
client_id=serial,
|
|
server=MQTT_BROKER,
|
|
port=MQTT_PORT,
|
|
user=MQTT_USER,
|
|
password=MQTT_PASSWORD,
|
|
keepalive=60,
|
|
ssl=False
|
|
)
|
|
client.set_callback(mqtt_callback)
|
|
client.connect()
|
|
for topic in (MQTT_RESET_TOPIC, MQTT_RESPONSE_TOPIC, MQTT_LAMP_TOPIC):
|
|
client.subscribe(topic)
|
|
mqtt_client = client
|
|
print("MQTT Connected")
|
|
show_mqtt_icon(True)
|
|
rgb_0.fill_color(lamp_color)
|
|
except Exception as e:
|
|
print("MQTT init error:", e)
|
|
mqtt_client = None
|
|
show_mqtt_icon(False)
|
|
|
|
def reconnect_mqtt():
|
|
"""Tear the client down and rebuild it, at most once per MQTT_RECONNECT_INTERVAL."""
|
|
global mqtt_client, mqtt_last_try
|
|
now = utime.ticks_ms()
|
|
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 Exception:
|
|
pass
|
|
mqtt_client = None
|
|
if not wlan.isconnected():
|
|
print("WiFi not connected, skipping MQTT reconnect")
|
|
show_wifi_icon(False)
|
|
return
|
|
init_mqtt()
|
|
except Exception as e:
|
|
print("MQTT reconnect error:", e)
|
|
show_mqtt_icon(False)
|
|
mqtt_client = None
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# RFID
|
|
# ---------------------------------------------------------------------------
|
|
def init_rfid():
|
|
global i2c0, rfid_0, rfid_needs_init
|
|
i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
|
|
rfid_0 = RFIDUnit(i2c0)
|
|
rfid_needs_init = False
|
|
|
|
def read_card_fields():
|
|
"""(card type, operator id) as written on the card; None for a block that cannot be read."""
|
|
values = []
|
|
for block in (CARD_TYPE_BLOCK, OPERATOR_ID_BLOCK):
|
|
try:
|
|
values.append(rfid_0.read(block).decode('utf-8').strip('\x00'))
|
|
except Exception:
|
|
values.append(None)
|
|
return values[0], values[1]
|
|
|
|
def handle_card():
|
|
"""Act on the card on the reader: once per tap, by the type written on the card."""
|
|
global Operator_ID, last_uid_str, tap_handled
|
|
uid = rfid_0.read_card_uid()
|
|
if not uid:
|
|
return
|
|
uid_str = ':'.join('%02X' % b for b in uid)
|
|
label_set('uid', uid_str)
|
|
cardtype, card_operator_id = read_card_fields()
|
|
rfid_0.close()
|
|
|
|
if tap_handled:
|
|
return # the same card is still resting on the reader
|
|
|
|
if cardtype == "Reset":
|
|
tap_handled = True
|
|
reset_counter()
|
|
Operator_ID = "0"
|
|
label_set('operator', "-")
|
|
save_to_nvs()
|
|
publish_scan("RESET", "RESET", 0)
|
|
start_ack_wait("Reset")
|
|
return
|
|
|
|
last_uid_str = uid_str
|
|
if cardtype == "Operator" and card_operator_id and card_operator_id != '0':
|
|
tap_handled = True
|
|
Operator_ID = card_operator_id
|
|
label_set('operator', Operator_ID)
|
|
save_to_nvs()
|
|
# The operator card's OWN uid. Until 2026-09-15 this sent the previous card's uid
|
|
# ("-" after a reboot), so the server checked and logged the wrong card.
|
|
publish_scan(uid_str, cardtype, Operator_ID)
|
|
start_ack_wait(cardtype)
|
|
return
|
|
|
|
if cardtype == "Product":
|
|
tap_handled = True
|
|
publish_scan(uid_str, cardtype, Operator_ID)
|
|
start_ack_wait(cardtype)
|
|
# Any other type (or an unwritten card) is shown on screen and not sent.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main loop
|
|
# ---------------------------------------------------------------------------
|
|
def loop():
|
|
global mqtt_client, tap_handled, rfid_needs_init, waiting_for_ack
|
|
|
|
if wlan.isconnected():
|
|
label_set('ip', "IP:" + wlan.ifconfig()[0])
|
|
time.sleep(0.1)
|
|
show_battery()
|
|
|
|
if not wlan.isconnected():
|
|
connect_wifi()
|
|
if wlan.isconnected():
|
|
init_mqtt()
|
|
else:
|
|
time.sleep(1)
|
|
return
|
|
|
|
try:
|
|
if mqtt_client:
|
|
mqtt_client.check_msg()
|
|
except Exception as e:
|
|
print("MQTT error:", e)
|
|
mqtt_client = None
|
|
|
|
if waiting_for_ack and utime.ticks_diff(utime.ticks_ms(), ack_deadline) >= 0:
|
|
print("ACK TIMEOUT: No response received")
|
|
set_msg("NO RESPONSE RECEIVED")
|
|
waiting_for_ack = False
|
|
|
|
if wlan.isconnected() and mqtt_client is None:
|
|
reconnect_mqtt()
|
|
|
|
try:
|
|
if rfid_needs_init:
|
|
init_rfid()
|
|
print("RFID RE_INITIALIZED")
|
|
card_present = rfid_0.is_new_card_present() if rfid_0 else False
|
|
if waiting_for_ack:
|
|
# Further cards are ignored until the server answers or the deadline passes.
|
|
if card_present:
|
|
set_msg("WAITING FOR RESPONSE")
|
|
return
|
|
if card_present:
|
|
handle_card()
|
|
else:
|
|
label_set('uid', "-")
|
|
tap_handled = False
|
|
except Exception as e:
|
|
print("RFID Processing Error:", e)
|
|
label_set('uid', "RFID Error")
|
|
rfid_needs_init = True # rebuild the bus and reader on the next pass
|
|
|
|
# Liveness heartbeat, several times a second while idle.
|
|
if mqtt_client:
|
|
try:
|
|
mqtt_client.publish("M5devices/" + serial, "{}", qos=0)
|
|
except Exception as e:
|
|
print("MQTT heartbeat error:", e)
|
|
mqtt_client = None
|
|
|
|
M5.update()
|
|
time.sleep(0.1)
|
|
|
|
def setup():
|
|
global rgb_0, battery_bar, rfid_needs_init
|
|
|
|
M5.begin()
|
|
m5ui.init()
|
|
battery_bar = m5ui.M5Bar(x=256, y=7, w=49, h=25, min_value=0, max_value=100, value=25, bg_c=0x616161, color=0x21f398)
|
|
rgb_0 = RGBUnit((8, 9), 10)
|
|
|
|
load_from_nvs()
|
|
rgb_0.fill_color(DISCONNECTED_COLOUR)
|
|
init_ui()
|
|
init_wifi()
|
|
if wlan.isconnected():
|
|
check_for_update()
|
|
|
|
try:
|
|
init_rfid()
|
|
except Exception as e:
|
|
print("RFID Init Error:", e)
|
|
label_set('uid', "RFID Error")
|
|
rfid_needs_init = True
|
|
|
|
init_mqtt()
|
|
|
|
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")
|