Fix MQTT 2-gauge issue: publish discovery only on first boot via flag file, add resetdiscovery topic, remove disco, clean up DEBUG prints and duplicate code
This commit is contained in:
@@ -24,7 +24,6 @@ import network
|
||||
import utime
|
||||
import ujson
|
||||
import urandom
|
||||
print("DEBUG: imports done")
|
||||
from umqtt.robust import MQTTClient
|
||||
from machine import Pin
|
||||
from neopixel import NeoPixel
|
||||
@@ -175,8 +174,6 @@ for g in gauges:
|
||||
gauge_targets = [g["min"] for g in gauges] # target value per gauge
|
||||
gauge_last_rezero = [utime.ticks_ms() for _ in gauges]
|
||||
|
||||
print("DEBUG: gauge config parsed, creating topics...")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Topics (per-gauge)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -209,13 +206,12 @@ def make_gauge_topics(prefix, gauge_id):
|
||||
|
||||
gauge_topics = [make_gauge_topics(MQTT_PREFIX, g["id"]) for g in gauges]
|
||||
|
||||
print("DEBUG: topics created successfully")
|
||||
|
||||
T_SET = f"{MQTT_PREFIX}/set"
|
||||
T_STATE = f"{MQTT_PREFIX}/state"
|
||||
T_STATUS = f"{MQTT_PREFIX}/status"
|
||||
T_ZERO = f"{MQTT_PREFIX}/zero"
|
||||
T_DISCO = f"{MQTT_PREFIX}/disco"
|
||||
T_RESET_DISCOVERY = f"{MQTT_PREFIX}/resetdiscovery"
|
||||
|
||||
T_DISC_GAUGE = f"homeassistant/number/{MQTT_CLIENT_ID}/config"
|
||||
T_DISC_RED = f"homeassistant/switch/{MQTT_CLIENT_ID}_red/config"
|
||||
@@ -223,35 +219,6 @@ T_DISC_GREEN = f"homeassistant/switch/{MQTT_CLIENT_ID}_green/config"
|
||||
T_DISC_BL = f"homeassistant/light/{MQTT_CLIENT_ID}_bl/config"
|
||||
|
||||
|
||||
|
||||
def make_gauge_topics(prefix, gauge_id):
|
||||
return {
|
||||
"set": f"{prefix}/gauge{gauge_id}/set",
|
||||
"state": f"{prefix}/gauge{gauge_id}/state",
|
||||
"status": f"{prefix}/gauge{gauge_id}/status",
|
||||
"zero": f"{prefix}/gauge{gauge_id}/zero",
|
||||
"disc": f"homeassistant/number/{MQTT_CLIENT_ID}_g{gauge_id}/config",
|
||||
"led_red": f"{prefix}/gauge{gauge_id}/led/red/set",
|
||||
"led_green": f"{prefix}/gauge{gauge_id}/led/green/set",
|
||||
"led_bl": f"{prefix}/gauge{gauge_id}/led/backlight/set",
|
||||
"led_red_state": f"{prefix}/gauge{gauge_id}/led/red/state",
|
||||
"led_green_state": f"{prefix}/gauge{gauge_id}/led/green/state",
|
||||
"led_bl_state": f"{prefix}/gauge{gauge_id}/led/backlight/state",
|
||||
"led_red_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_g{gauge_id}_red/config",
|
||||
"led_green_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_g{gauge_id}_green/config",
|
||||
"led_bl_disc": f"homeassistant/light/{MQTT_CLIENT_ID}_g{gauge_id}_bl/config",
|
||||
"status_red": f"{prefix}/gauge{gauge_id}/status_led/red/set",
|
||||
"status_green": f"{prefix}/gauge{gauge_id}/status_led/green/set",
|
||||
"status_red_state": f"{prefix}/gauge{gauge_id}/status_led/red/state",
|
||||
"status_green_state": f"{prefix}/gauge{gauge_id}/status_led/green/state",
|
||||
"status_red_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_g{gauge_id}_status_red/config",
|
||||
"status_green_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_g{gauge_id}_status_green/config",
|
||||
}
|
||||
|
||||
|
||||
print("DEBUG: after duplicate function, about to define _DEVICE")
|
||||
|
||||
print("DEBUG: about to define _DEVICE dict")
|
||||
_DEVICE = {
|
||||
"identifiers": [MQTT_CLIENT_ID],
|
||||
"name": DEVICE_NAME,
|
||||
@@ -259,24 +226,16 @@ _DEVICE = {
|
||||
"manufacturer": DEVICE_MFR,
|
||||
"suggested_area": DEVICE_AREA,
|
||||
}
|
||||
print("DEBUG: _DEVICE defined")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WiFi
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
print("DEBUG: about to set wifi globals")
|
||||
print("DEBUG: setting _wifi_reconnect_delay_s...")
|
||||
_wifi_reconnect_delay_s = 5
|
||||
print("DEBUG: setting _wifi_check_interval_ms...")
|
||||
_wifi_check_interval_ms = 30000
|
||||
print("DEBUG: setting _last_wifi_check...")
|
||||
_last_wifi_check = 0
|
||||
print("DEBUG: setting _wifi_sta...")
|
||||
_wifi_sta = None
|
||||
print("DEBUG: wifi globals set")
|
||||
|
||||
print("DEBUG: defining connect_wifi function...")
|
||||
def connect_wifi(ssid, password, timeout_s=15):
|
||||
global _wifi_sta
|
||||
_wifi_sta = network.WLAN(network.STA_IF)
|
||||
@@ -331,32 +290,18 @@ def check_wifi():
|
||||
except Exception as e:
|
||||
log_err(f"WiFi reconnect failed: {e}")
|
||||
|
||||
print("DEBUG: WiFi functions defined")
|
||||
|
||||
print("DEBUG: about to init LED pins")
|
||||
print("DEBUG: computing len(gauges)...")
|
||||
num_gauges = len(gauges)
|
||||
print(f"DEBUG: num_gauges = {num_gauges}")
|
||||
leds_red = []
|
||||
leds_green = []
|
||||
leds_bl = []
|
||||
print("DEBUG: about to loop over gauges for pins")
|
||||
for g in gauges:
|
||||
print(f"DEBUG: initializing pins for gauge: {g['name']}")
|
||||
print(f"DEBUG: red_pin={g['red_pin']}, green_pin={g['green_pin']}")
|
||||
print("DEBUG: creating red Pin...")
|
||||
leds_red.append(Pin(g["red_pin"], Pin.OUT, value=0))
|
||||
print("DEBUG: red Pin created, creating green Pin...")
|
||||
leds_green.append(Pin(g["green_pin"], Pin.OUT, value=0))
|
||||
print("DEBUG: green Pin created")
|
||||
print("DEBUG: LED pins initialized")
|
||||
|
||||
total_backlight_leds = num_gauges * (BACKLIGHT_LEDS_PER_GAUGE + STATUS_LEDS_PER_GAUGE)
|
||||
print(f"DEBUG: initializing NeoPixel on pin {BACKLIGHT_PIN} with {total_backlight_leds} LEDs")
|
||||
leds_bl = NeoPixel(Pin(BACKLIGHT_PIN), total_backlight_leds)
|
||||
print("DEBUG: NeoPixel initialized")
|
||||
|
||||
print("DEBUG: about to init backlight arrays")
|
||||
backlight_color = [(0, 0, 0) for _ in range(num_gauges)]
|
||||
backlight_brightness = [100 for _ in range(num_gauges)]
|
||||
backlight_on = [False for _ in range(num_gauges)]
|
||||
@@ -367,126 +312,8 @@ status_led_green = [False for _ in range(num_gauges)]
|
||||
_bl_dirty_since = None
|
||||
_BL_SAVE_DELAY_MS = 5000
|
||||
|
||||
_disco_saved_brightness = [100] * num_gauges
|
||||
_disco_saved_color = [[0, 0, 0] for _ in range(num_gauges)]
|
||||
|
||||
print("DEBUG: about to define functions")
|
||||
|
||||
|
||||
def _flush_backlight(client):
|
||||
for i in range(num_gauges):
|
||||
gt = gauge_topics[i]
|
||||
payload = {
|
||||
"state": "ON" if backlight_on[i] else "OFF",
|
||||
"color": {
|
||||
"r": backlight_color[i][0],
|
||||
"g": backlight_color[i][1],
|
||||
"b": backlight_color[i][2],
|
||||
},
|
||||
"brightness": int(backlight_brightness[i] * 2.55),
|
||||
}
|
||||
client.publish(gt["led_bl_state"], ujson.dumps(payload), retain=True)
|
||||
info(
|
||||
f"Gauge {i} backlight: {payload['state']} {backlight_color[i]} @ {backlight_brightness[i]}%"
|
||||
)
|
||||
|
||||
|
||||
def _backlight_changed(gauge_idx, new_color, new_on, new_brightness):
|
||||
return (
|
||||
new_color != backlight_color[gauge_idx]
|
||||
or new_on != backlight_on[gauge_idx]
|
||||
or (new_on and new_brightness != backlight_brightness[gauge_idx])
|
||||
)
|
||||
|
||||
|
||||
def _mark_bl_dirty():
|
||||
global _bl_dirty_since
|
||||
_bl_dirty_since = utime.ticks_ms()
|
||||
|
||||
|
||||
def set_backlight_color(gauge_idx, r, g, b, brightness=None):
|
||||
global backlight_color, backlight_brightness, backlight_on
|
||||
if brightness is None:
|
||||
brightness = backlight_brightness[gauge_idx]
|
||||
new_on = brightness > 0
|
||||
if not _backlight_changed(gauge_idx, (r, g, b), new_on, brightness):
|
||||
return
|
||||
backlight_color[gauge_idx] = (r, g, b)
|
||||
if brightness > 0:
|
||||
backlight_brightness[gauge_idx] = brightness
|
||||
backlight_on[gauge_idx] = new_on
|
||||
|
||||
scale = brightness / 100
|
||||
leds_per_gauge = BACKLIGHT_LEDS_PER_GAUGE + STATUS_LEDS_PER_GAUGE
|
||||
base_idx = gauge_idx * leds_per_gauge
|
||||
for j in range(BACKLIGHT_LEDS_PER_GAUGE):
|
||||
leds_bl[base_idx + j] = (int(g * scale), int(r * scale), int(b * scale))
|
||||
_update_status_leds(gauge_idx)
|
||||
leds_bl.write()
|
||||
_mark_bl_dirty()
|
||||
|
||||
|
||||
def set_backlight_brightness(gauge_idx, brightness):
|
||||
global backlight_brightness, backlight_on
|
||||
clamped = max(0, min(100, brightness))
|
||||
new_on = clamped > 0
|
||||
if not _backlight_changed(gauge_idx, backlight_color[gauge_idx], new_on, clamped):
|
||||
return
|
||||
if clamped > 0:
|
||||
backlight_brightness[gauge_idx] = clamped
|
||||
backlight_on[gauge_idx] = new_on
|
||||
r, g, b = backlight_color[gauge_idx]
|
||||
scale = clamped / 100
|
||||
leds_per_gauge = BACKLIGHT_LEDS_PER_GAUGE + STATUS_LEDS_PER_GAUGE
|
||||
base_idx = gauge_idx * leds_per_gauge
|
||||
for j in range(BACKLIGHT_LEDS_PER_GAUGE):
|
||||
leds_bl[base_idx + j] = (int(g * scale), int(r * scale), int(b * scale))
|
||||
_update_status_leds(gauge_idx)
|
||||
leds_bl.write()
|
||||
_mark_bl_dirty()
|
||||
|
||||
|
||||
def _update_status_leds(gauge_idx):
|
||||
leds_per_gauge = BACKLIGHT_LEDS_PER_GAUGE + STATUS_LEDS_PER_GAUGE
|
||||
base_idx = gauge_idx * leds_per_gauge + BACKLIGHT_LEDS_PER_GAUGE
|
||||
|
||||
g_cfg = gauges[gauge_idx]
|
||||
red_color = g_cfg["ws2812_red"]
|
||||
green_color = g_cfg["ws2812_green"]
|
||||
|
||||
if status_led_red[gauge_idx]:
|
||||
leds_bl[base_idx] = (red_color[1], red_color[0], red_color[2])
|
||||
else:
|
||||
leds_bl[base_idx] = (0, 0, 0)
|
||||
|
||||
if status_led_green[gauge_idx]:
|
||||
leds_bl[base_idx + 1] = (green_color[1], green_color[0], green_color[2])
|
||||
else:
|
||||
leds_bl[base_idx + 1] = (0, 0, 0)
|
||||
|
||||
|
||||
def set_status_led(gauge_idx, led_type, state):
|
||||
global status_led_red, status_led_green
|
||||
if led_type == "red":
|
||||
status_led_red[gauge_idx] = state
|
||||
elif led_type == "green":
|
||||
status_led_green[gauge_idx] = state
|
||||
_update_status_leds(gauge_idx)
|
||||
leds_bl.write()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# State
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_last_rezero_ms = None # set to ticks_ms() in main()
|
||||
client_ref = None
|
||||
_mqtt_connected = False
|
||||
_last_mqtt_check = 0
|
||||
|
||||
_disco_end_time = 0
|
||||
|
||||
print("DEBUG: about to define _publish")
|
||||
|
||||
|
||||
def _publish(topic, payload, retain=False):
|
||||
@@ -507,7 +334,6 @@ def _publish(topic, payload, retain=False):
|
||||
|
||||
|
||||
def on_message(topic, payload):
|
||||
global _disco_end_time, _disco_saved_brightness, _disco_saved_color
|
||||
global backlight_brightness, backlight_color
|
||||
if client_ref is None:
|
||||
return
|
||||
@@ -540,13 +366,16 @@ def on_message(topic, payload):
|
||||
info("All gauges zeroed")
|
||||
return
|
||||
|
||||
if topic == T_DISCO:
|
||||
_disco_end_time = utime.ticks_ms() + 5000
|
||||
for i in range(num_gauges):
|
||||
_disco_saved_brightness[i] = backlight_brightness[i]
|
||||
_disco_saved_color[i] = backlight_color[i]
|
||||
info("Disco mode started")
|
||||
return
|
||||
if topic == T_RESET_DISCOVERY:
|
||||
info("Reset discovery triggered")
|
||||
try:
|
||||
import uos
|
||||
uos.remove(".discovery_ok")
|
||||
info("Discovery flag file removed")
|
||||
except:
|
||||
pass
|
||||
import machine
|
||||
machine.reset()
|
||||
|
||||
for i, gt in enumerate(gauge_topics):
|
||||
if topic == gt["led_red"]:
|
||||
@@ -627,32 +456,28 @@ def connect_mqtt():
|
||||
port=MQTT_PORT,
|
||||
user=MQTT_USER,
|
||||
password=MQTT_PASSWORD,
|
||||
keepalive=60,
|
||||
keepalive=30,
|
||||
)
|
||||
client.set_last_will(T_STATUS, b"offline", retain=True, qos=0)
|
||||
# Don't set last will - it might be causing issues
|
||||
# client.set_last_will(T_STATUS, b"offline", retain=True, qos=0)
|
||||
client.set_callback(on_message)
|
||||
client.connect()
|
||||
client_ref = client
|
||||
|
||||
# Explicit subscriptions
|
||||
client.subscribe(T_SET)
|
||||
client.subscribe(T_ZERO)
|
||||
client.subscribe(T_DISCO)
|
||||
client.subscribe(T_RESET_DISCOVERY) # Reset discovery flag
|
||||
for gt in gauge_topics:
|
||||
client.subscribe(gt["set"])
|
||||
client.subscribe(gt["zero"])
|
||||
client.subscribe(gt["led_red"])
|
||||
client.subscribe(gt["led_green"])
|
||||
client.subscribe(gt["led_bl"])
|
||||
client.subscribe(gt["status_red"])
|
||||
client.subscribe(gt["status_green"])
|
||||
|
||||
_mqtt_connected = True
|
||||
info(f"MQTT connected client_id={MQTT_CLIENT_ID}")
|
||||
return client
|
||||
|
||||
|
||||
_mqtt_check_interval_ms = 30000
|
||||
_last_mqtt_check = 0
|
||||
|
||||
print("DEBUG: about to define check_mqtt")
|
||||
|
||||
|
||||
def check_mqtt():
|
||||
@@ -690,7 +515,7 @@ def check_mqtt():
|
||||
client_ref.connect()
|
||||
client_ref.subscribe(T_SET)
|
||||
client_ref.subscribe(T_ZERO)
|
||||
client_ref.subscribe(T_DISCO)
|
||||
client_ref.subscribe(T_RESET_DISCOVERY)
|
||||
for gt in gauge_topics:
|
||||
client_ref.subscribe(gt["set"])
|
||||
client_ref.subscribe(gt["zero"])
|
||||
@@ -711,7 +536,6 @@ def check_mqtt():
|
||||
log_err("MQTT reconnection failed after 3 attempts")
|
||||
return False
|
||||
|
||||
print("DEBUG: about to define publish_discovery")
|
||||
|
||||
|
||||
def publish_discovery(client):
|
||||
@@ -748,6 +572,10 @@ def publish_discovery(client):
|
||||
)
|
||||
info(f"Discovery: gauge {i} ({g['name']})")
|
||||
|
||||
# Process MQTT messages between publishes
|
||||
client.check_msg()
|
||||
utime.sleep_ms(30)
|
||||
|
||||
client.publish(
|
||||
gt["led_red_disc"],
|
||||
ujson.dumps(
|
||||
@@ -786,6 +614,30 @@ def publish_discovery(client):
|
||||
)
|
||||
info(f"Discovery: gauge {i} green LED")
|
||||
|
||||
# Process MQTT messages
|
||||
for _ in range(5):
|
||||
client.check_msg()
|
||||
utime.sleep_ms(10)
|
||||
|
||||
client.publish(
|
||||
gt["led_green_disc"],
|
||||
ujson.dumps(
|
||||
{
|
||||
"name": f"{g['name']} Green LED",
|
||||
"uniq_id": f"{MQTT_CLIENT_ID}_g{i}_green",
|
||||
"cmd_t": gt["led_green"],
|
||||
"stat_t": gt["led_green_state"],
|
||||
"pl_on": "ON",
|
||||
"pl_off": "OFF",
|
||||
"icon": "mdi:led-on",
|
||||
"dev": _dev_ref,
|
||||
"ret": True,
|
||||
}
|
||||
),
|
||||
retain=True,
|
||||
)
|
||||
info(f"Discovery: gauge {i} green LED")
|
||||
|
||||
client.publish(
|
||||
gt["led_bl_disc"],
|
||||
ujson.dumps(
|
||||
@@ -859,61 +711,74 @@ def publish_state(client):
|
||||
|
||||
|
||||
def main():
|
||||
print("DEBUG: main() entered")
|
||||
info("=" * 48)
|
||||
info("Gauge MQTT controller starting")
|
||||
info("=" * 48)
|
||||
|
||||
print("DEBUG: about to connect wifi")
|
||||
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
|
||||
print("DEBUG: wifi connected")
|
||||
|
||||
# Check if we need to publish discovery
|
||||
try:
|
||||
with open(".discovery_ok", "r") as f:
|
||||
discovery_published = f.read()
|
||||
info("Discovery flag file exists, skipping discovery")
|
||||
discovery_published = True
|
||||
except:
|
||||
info("No discovery flag file - publishing discovery")
|
||||
discovery_published = False
|
||||
|
||||
# Connect MQTT first (needed for discovery)
|
||||
connect_mqtt()
|
||||
|
||||
# Give MQTT time to process subscriptions
|
||||
info("Processing initial MQTT messages...")
|
||||
for i in range(50):
|
||||
client_ref.check_msg()
|
||||
utime.sleep_ms(20)
|
||||
info("Done with initial processing")
|
||||
|
||||
# Publish discovery if needed (before gauge initialization)
|
||||
if not discovery_published:
|
||||
info("Publishing discovery...")
|
||||
publish_discovery(client_ref)
|
||||
|
||||
# Write flag file
|
||||
try:
|
||||
with open(".discovery_ok", "w") as f:
|
||||
f.write("ok")
|
||||
info("Discovery flag file created")
|
||||
except Exception as e:
|
||||
warn(f"Could not write discovery flag: {e}")
|
||||
|
||||
info("Discovery published, resetting...")
|
||||
import machine
|
||||
machine.reset()
|
||||
|
||||
# Now initialize gauges
|
||||
info("Zeroing gauges on startup ...")
|
||||
for i, g in enumerate(gauge_objects):
|
||||
g.zero()
|
||||
info(f"Zeroed gauge {i}")
|
||||
info("Zero complete")
|
||||
|
||||
connect_mqtt()
|
||||
publish_discovery(client_ref)
|
||||
info("Publishing state...")
|
||||
publish_state(client_ref)
|
||||
utime.sleep_ms(50)
|
||||
for _ in range(5):
|
||||
client_ref.check_msg()
|
||||
utime.sleep_ms(20)
|
||||
|
||||
info("Entering main loop")
|
||||
info("-" * 48)
|
||||
|
||||
try:
|
||||
import ota
|
||||
|
||||
ota.mark_ok()
|
||||
info("OTA OK flag set")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
global _bl_dirty_since
|
||||
global _disco_end_time, _disco_saved_brightness, _disco_saved_color
|
||||
# Initialize variables for main loop
|
||||
last_heartbeat = utime.ticks_ms()
|
||||
now = 0
|
||||
|
||||
while True:
|
||||
try:
|
||||
now = utime.ticks_ms()
|
||||
|
||||
if _disco_end_time > 0 and utime.ticks_diff(_disco_end_time, now) <= 0:
|
||||
_disco_end_time = 0
|
||||
for i in range(num_gauges):
|
||||
set_backlight_color(i, *_disco_saved_color[i], _disco_saved_brightness[i])
|
||||
info("Disco mode ended")
|
||||
elif _disco_end_time > 0:
|
||||
for led_idx in range(total_backlight_leds):
|
||||
r = urandom.getrandbits(8)
|
||||
g = urandom.getrandbits(8)
|
||||
b = urandom.getrandbits(8)
|
||||
r = max(r, 128)
|
||||
g = max(g, 128)
|
||||
b = max(b, 128)
|
||||
leds_bl[led_idx] = (g, r, b)
|
||||
leds_bl.write()
|
||||
utime.sleep_ms(200)
|
||||
|
||||
check_wifi()
|
||||
|
||||
if not check_mqtt():
|
||||
@@ -936,33 +801,20 @@ def main():
|
||||
if moved_any:
|
||||
delay_us = 1_000_000 // MICROSTEPS_PER_SECOND
|
||||
utime.sleep_us(delay_us)
|
||||
else:
|
||||
utime.sleep_ms(10)
|
||||
|
||||
if (
|
||||
REZERO_INTERVAL_MS > 0
|
||||
and utime.ticks_diff(now, gauge_last_rezero[0]) >= REZERO_INTERVAL_MS
|
||||
):
|
||||
for i, g in enumerate(gauge_objects):
|
||||
info(f"Auto-rezero gauge {i}")
|
||||
saved = gauge_targets[i]
|
||||
g.zero()
|
||||
if saved > gauges[i]["min"]:
|
||||
g.set(saved)
|
||||
gauge_last_rezero[i] = now
|
||||
publish_state(client_ref)
|
||||
info("Auto-rezero complete")
|
||||
|
||||
if (
|
||||
_bl_dirty_since is not None
|
||||
and utime.ticks_diff(now, _bl_dirty_since) >= _BL_SAVE_DELAY_MS
|
||||
):
|
||||
_flush_backlight(client_ref)
|
||||
_bl_dirty_since = None
|
||||
if utime.ticks_diff(utime.ticks_ms(), last_heartbeat) > 10000:
|
||||
info(f"Heartbeat: {gauge_targets}")
|
||||
last_heartbeat = utime.ticks_ms()
|
||||
|
||||
if utime.ticks_diff(now, last_heartbeat) >= HEARTBEAT_MS:
|
||||
publish_state(client_ref)
|
||||
last_heartbeat = now
|
||||
|
||||
except Exception as e:
|
||||
import sys
|
||||
sys.print_exception(e)
|
||||
log_err(f"Main loop error: {e} — continuing")
|
||||
utime.sleep_ms(100)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user