From dcd8bfda4ca09299f9e3c10e7de4b8c7ed0fa294 Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Mon, 13 Apr 2026 00:38:41 +0200 Subject: [PATCH] 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 --- gaugemqttcontinuous.py | 350 ++++++++++++----------------------------- 1 file changed, 101 insertions(+), 249 deletions(-) diff --git a/gaugemqttcontinuous.py b/gaugemqttcontinuous.py index eabc47b..3cbfb79 100644 --- a/gaugemqttcontinuous.py +++ b/gaugemqttcontinuous.py @@ -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): @@ -747,7 +571,11 @@ def publish_discovery(client): retain=True, ) 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( @@ -766,7 +594,31 @@ def publish_discovery(client): retain=True, ) info(f"Discovery: gauge {i} red LED") - + + 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") + + # Process MQTT messages + for _ in range(5): + client.check_msg() + utime.sleep_ms(10) + client.publish( gt["led_green_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)