18 Commits
main ... debug

2 changed files with 132 additions and 54 deletions

62
boot.py Normal file
View File

@@ -0,0 +1,62 @@
"""
boot.py — runs before main.py on every ESP32 boot
Connects WiFi, runs OTA update, then hands off to main.py.
Keep this file as simple as possible — it is never OTA-updated itself
(it lives outside the repo folder) so bugs here require USB to fix.
"""
#import gauge
import network
import gc
import utime
import sys
import ota
ota.load_config()
WIFI_SSID, WIFI_PASSWORD = ota.WIFI_SSID, ota.WIFI_PASSWORD
def _connect_wifi(timeout_s=20):
sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.config(txpower=15)
if sta.isconnected():
return True
sta.connect(WIFI_SSID, WIFI_PASSWORD)
deadline = utime.time() + timeout_s
while not sta.isconnected():
if utime.time() > deadline:
return False
utime.sleep_ms(300)
return True
if WIFI_SSID is None:
print("[boot] No WiFi credentials — cannot connect, skipping OTA")
elif _connect_wifi():
ip = network.WLAN(network.STA_IF).ifconfig()[0]
print(f"[boot] WiFi connected — {ip}")
try:
ota.update()
except Exception as e:
print(f"[boot] OTA error: {e} — continuing with existing files")
sys.print_exception(e)
utime.sleep_ms(5000)
ota._fetch_commit_sha = None
ota._fetch_manifest = None
ota._fetch_dir = None
ota._api_get = None
ota._download = None
ota.urequests = None
del ota.urequests
del ota
gc.collect()
del sys.modules["ota"]
gc.collect()
else:
print("[boot] WiFi failed — skipping OTA, booting with existing files")
# main.py runs automatically after boot.py

124
gauge.py
View File

@@ -28,16 +28,6 @@ import gc
from umqtt.robust import MQTTClient
from machine import UART
# Activate WiFi driver before any heavy heap allocation so it can claim its
# contiguous DRAM block before the Python heap fragments the address space.
# Only activate if not already running (e.g. boot.py may have started it).
gc.collect()
_early_wlan = network.WLAN(network.STA_IF)
if not _early_wlan.active():
_early_wlan.active(True)
del _early_wlan
gc.collect()
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
@@ -161,19 +151,27 @@ ARDUINO_TX_PIN = int(_cfg.get("arduino_tx_pin", 17))
ARDUINO_RX_PIN = int(_cfg.get("arduino_rx_pin", 16))
ARDUINO_BAUD = int(_cfg.get("arduino_baud", 115200))
_arduino = UART(ARDUINO_UART_ID, baudrate=ARDUINO_BAUD, tx=ARDUINO_TX_PIN, rx=ARDUINO_RX_PIN, timeout=10)
_arduino = None
def _ensure_arduino():
global _arduino
if _arduino is None:
_arduino = UART(ARDUINO_UART_ID, baudrate=ARDUINO_BAUD, tx=ARDUINO_TX_PIN, rx=ARDUINO_RX_PIN, timeout=10)
return _arduino
def arduino_send(cmd):
"""Send a newline-terminated command to the Arduino."""
_arduino.write((cmd + "\n").encode())
_ensure_arduino().write((cmd + "\n").encode())
info(f"Arduino → {cmd}")
def arduino_recv():
"""Print any lines waiting in the Arduino RX buffer."""
while _arduino.any():
line = _arduino.readline()
uart = _ensure_arduino()
while uart.any():
line = uart.readline()
if line:
print(f"[{_ts()}] ARDU {line.decode().strip()}")
@@ -540,11 +538,14 @@ _WIFI_CONNECT_ATTEMPTS = 3
def _reset_wifi_interface():
global _wifi_sta
_wifi_sta = network.WLAN(network.STA_IF)
if _wifi_sta.active():
_wifi_sta.active(False)
utime.sleep_ms(200)
_wifi_sta.active(True)
utime.sleep_ms(500)
if not _wifi_sta.active():
_wifi_sta.active(True)
utime.sleep_ms(500)
try:
_wifi_sta.disconnect()
except Exception:
pass
utime.sleep_ms(1000)
def connect_wifi(ssid, password, timeout_s=15, force_reconnect=False):
@@ -561,7 +562,8 @@ def connect_wifi(ssid, password, timeout_s=15, force_reconnect=False):
last_error = None
for attempt in range(_WIFI_CONNECT_ATTEMPTS):
info(f"WiFi connecting to '{ssid}' (attempt {attempt + 1}/{_WIFI_CONNECT_ATTEMPTS}) ...")
_reset_wifi_interface()
if not _wifi_sta.isconnected():
_reset_wifi_interface()
try:
_wifi_sta.connect(ssid, password)
deadline = utime.time() + timeout_s
@@ -576,7 +578,7 @@ def connect_wifi(ssid, password, timeout_s=15, force_reconnect=False):
info(f" SSID : {ssid}")
info(f" MAC : {mac}")
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}")
utime.sleep_ms(500)
utime.sleep_ms(2000)
return ip
except Exception as e:
last_error = e
@@ -601,7 +603,7 @@ def check_wifi():
log_err("WiFi lost connection — attempting reconnect...")
try:
ip = connect_wifi(WIFI_SSID, WIFI_PASSWORD, timeout_s=15, force_reconnect=True)
ip = connect_wifi(WIFI_SSID, WIFI_PASSWORD, timeout_s=15)
info(f"WiFi reconnected! IP:{ip}")
except Exception as e:
log_err(f"WiFi reconnect failed: {e}")
@@ -911,10 +913,6 @@ def connect_mqtt():
except Exception as e:
last_error = e
log_err(f"MQTT connect attempt {attempt + 1} failed: {type(e).__name__}: {e}")
try:
client.sock.close()
except Exception:
pass
gc.collect()
utime.sleep_ms(1000)
@@ -922,27 +920,6 @@ def connect_mqtt():
raise last_error
_mqtt_check_interval_ms = 30000
_last_mqtt_check = 0
_discovery_queue = []
_discovery_idx = 0
_last_discovery_ms = 0
_DISCOVERY_INTERVAL_MS = 350
def _compact_discovery_payload(payload):
"""Trim optional HA discovery fields when RAM is tight."""
compact = dict(payload)
# Light entities are the largest payloads because they repeat effect metadata.
# Keep core functionality, but omit optional effect declarations to reduce heap use.
if compact.get("schema") == "json":
compact.pop("effect", None)
compact.pop("effect_list", None)
return compact
def check_mqtt():
global client_ref, _mqtt_connected, _last_mqtt_check
now = utime.ticks_ms()
@@ -984,10 +961,6 @@ def check_mqtt():
return True
except Exception as e2:
log_err(f"MQTT reconnect attempt {attempt + 1} failed: {e2}")
try:
client_ref.sock.close()
except Exception:
pass
gc.collect()
utime.sleep_ms(2000)
@@ -995,9 +968,17 @@ def check_mqtt():
return False
_mqtt_check_interval_ms = 30000
_last_mqtt_check = 0
_discovery_queue = []
_discovery_idx = 0
_last_discovery_ms = 0
_DISCOVERY_INTERVAL_MS = 350
def _publish_discovery_entity(client, topic, payload, log_msg):
gc.collect()
client.publish(topic, ujson.dumps(_compact_discovery_payload(payload)), retain=True)
client.publish(topic, ujson.dumps(payload), retain=True)
info(log_msg)
@@ -1305,12 +1286,42 @@ def apply_motion_defaults():
send_vfd_state()
def _restore_led_states():
for i in range(num_gauges):
gt = gauge_topics[i]
info(f" red={_red_effect[i]} green={_green_effect[i]} status_r={_status_red_effect[i]} status_g={_status_green_effect[i]}")
for led_key, led_idx, color, effect_arr, state_topic in [
("red", _LED_RED, gauges[i]["ws2812_red"], _red_effect, gt["led_red_state"]),
("green", _LED_GREEN, gauges[i]["ws2812_green"], _green_effect, gt["led_green_state"]),
("status_red", _LED_STATUS_RED, gauges[i]["ws2812_red"], _status_red_effect, gt["status_red_state"]),
("status_green", _LED_STATUS_GREEN, gauges[i]["ws2812_green"], _status_green_effect, gt["status_green_state"]),
]:
if effect_arr[i]:
pub = {"state": "ON", "effect": effect_arr[i]}
_publish(state_topic, ujson.dumps(pub), retain=True)
if _red_effect[i]:
_apply_blink_or_led(i, _LED_RED, gauges[i]["ws2812_red"], _red_effect[i])
if _green_effect[i]:
_apply_blink_or_led(i, _LED_GREEN, gauges[i]["ws2812_green"], _green_effect[i])
if _status_red_effect[i]:
_apply_blink_or_led(i, _LED_STATUS_RED, gauges[i]["ws2812_red"], _status_red_effect[i])
if _status_green_effect[i]:
_apply_blink_or_led(i, _LED_STATUS_GREEN, gauges[i]["ws2812_green"], _status_green_effect[i])
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
gc.collect()
_w = network.WLAN(network.STA_IF)
if not _w.active():
_w.active(True)
del _w
gc.collect()
_ensure_arduino()
gc.collect()
info("=" * 48)
info("Gauge MQTT controller starting")
@@ -1318,7 +1329,7 @@ def main():
info("=" * 48)
gc.collect()
connect_wifi(WIFI_SSID, WIFI_PASSWORD, force_reconnect=True)
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
mqtt_attempts = 0
while True:
@@ -1331,13 +1342,14 @@ def main():
if mqtt_attempts % 3 == 0:
log_err("WiFi may be stale — forcing reconnect...")
try:
connect_wifi(WIFI_SSID, WIFI_PASSWORD, force_reconnect=True)
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
except Exception as we:
log_err(f"WiFi reconnect failed: {we}")
utime.sleep_ms(5000)
_subscribe_all(client_ref)
schedule_discovery()
publish_backlight_states(client_ref)
apply_motion_defaults()
info("Draining initial retained messages...")
for _ in range(50):
@@ -1350,6 +1362,10 @@ def main():
gauge_last_rezero[i] = utime.ticks_ms()
info("Home command sent")
utime.sleep_ms(100)
_restore_led_states()
info("LED effects restored")
info("Publishing state...")
publish_online(client_ref)
publish_state(client_ref)