Compare commits
30 Commits
81099d9887
...
debug
| Author | SHA1 | Date | |
|---|---|---|---|
| 23834d660c | |||
| a0821fed80 | |||
| 014346c442 | |||
| cf545cb849 | |||
| 9af28acb51 | |||
| af42a1c4de | |||
| ffd807ff45 | |||
| c57e0d8ae2 | |||
| a294ce1fd9 | |||
| 179fb77b95 | |||
| cf36a2c07f | |||
| 70a6719553 | |||
| cba7d907e9 | |||
| ee0d85fa73 | |||
| 0b4f285ddb | |||
| 415310b1f9 | |||
| b1afa2f06c | |||
| 423b9f91be | |||
| ad50fd2ee5 | |||
| ae6d72b292 | |||
| 2ea19b14f8 | |||
| 1f8ba45685 | |||
| 5a98a3c63b | |||
| afe70da24d | |||
| 0d7ae5cedc | |||
| b3e2ef0f81 | |||
| 6068628d13 | |||
| 64b0aa482f | |||
| 2e5e410897 | |||
| 4045da8964 |
62
boot.py
Normal file
62
boot.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
150
gauge.py
150
gauge.py
@@ -151,19 +151,27 @@ ARDUINO_TX_PIN = int(_cfg.get("arduino_tx_pin", 17))
|
|||||||
ARDUINO_RX_PIN = int(_cfg.get("arduino_rx_pin", 16))
|
ARDUINO_RX_PIN = int(_cfg.get("arduino_rx_pin", 16))
|
||||||
ARDUINO_BAUD = int(_cfg.get("arduino_baud", 115200))
|
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):
|
def arduino_send(cmd):
|
||||||
"""Send a newline-terminated command to the Arduino."""
|
"""Send a newline-terminated command to the Arduino."""
|
||||||
_arduino.write((cmd + "\n").encode())
|
_ensure_arduino().write((cmd + "\n").encode())
|
||||||
info(f"Arduino → {cmd}")
|
info(f"Arduino → {cmd}")
|
||||||
|
|
||||||
|
|
||||||
def arduino_recv():
|
def arduino_recv():
|
||||||
"""Print any lines waiting in the Arduino RX buffer."""
|
"""Print any lines waiting in the Arduino RX buffer."""
|
||||||
while _arduino.any():
|
uart = _ensure_arduino()
|
||||||
line = _arduino.readline()
|
while uart.any():
|
||||||
|
line = uart.readline()
|
||||||
if line:
|
if line:
|
||||||
print(f"[{_ts()}] ARDU {line.decode().strip()}")
|
print(f"[{_ts()}] ARDU {line.decode().strip()}")
|
||||||
|
|
||||||
@@ -524,46 +532,60 @@ _DEVICE = {
|
|||||||
_wifi_check_interval_ms = 30000
|
_wifi_check_interval_ms = 30000
|
||||||
_last_wifi_check = 0
|
_last_wifi_check = 0
|
||||||
_wifi_sta = None
|
_wifi_sta = None
|
||||||
|
_WIFI_CONNECT_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
|
def _reset_wifi_interface():
|
||||||
|
global _wifi_sta
|
||||||
|
_wifi_sta = network.WLAN(network.STA_IF)
|
||||||
|
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):
|
def connect_wifi(ssid, password, timeout_s=15, force_reconnect=False):
|
||||||
global _wifi_sta
|
global _wifi_sta
|
||||||
_wifi_sta = network.WLAN(network.STA_IF)
|
_wifi_sta = network.WLAN(network.STA_IF)
|
||||||
|
|
||||||
if force_reconnect:
|
|
||||||
try:
|
|
||||||
_wifi_sta.disconnect()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
_wifi_sta.active(False)
|
|
||||||
utime.sleep_ms(250)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
_wifi_sta.active(True)
|
|
||||||
if _wifi_sta.isconnected() and not force_reconnect:
|
if _wifi_sta.isconnected() and not force_reconnect:
|
||||||
ip, mask, gw, dns = _wifi_sta.ifconfig()
|
ip, mask, gw, dns = _wifi_sta.ifconfig()
|
||||||
info("WiFi already connected")
|
info("WiFi already connected")
|
||||||
info(f" IP:{ip} mask:{mask} gw:{gw} dns:{dns}")
|
info(f" IP:{ip} mask:{mask} gw:{gw} dns:{dns}")
|
||||||
utime.sleep_ms(250)
|
utime.sleep_ms(250)
|
||||||
return ip
|
return ip
|
||||||
info(f"WiFi connecting to '{ssid}' ...")
|
|
||||||
|
last_error = None
|
||||||
|
for attempt in range(_WIFI_CONNECT_ATTEMPTS):
|
||||||
|
info(f"WiFi connecting to '{ssid}' (attempt {attempt + 1}/{_WIFI_CONNECT_ATTEMPTS}) ...")
|
||||||
|
if not _wifi_sta.isconnected():
|
||||||
|
_reset_wifi_interface()
|
||||||
|
try:
|
||||||
_wifi_sta.connect(ssid, password)
|
_wifi_sta.connect(ssid, password)
|
||||||
deadline = utime.time() + timeout_s
|
deadline = utime.time() + timeout_s
|
||||||
while not _wifi_sta.isconnected():
|
while not _wifi_sta.isconnected():
|
||||||
if utime.time() > deadline:
|
if utime.time() > deadline:
|
||||||
log_err(f"WiFi connect timeout after {timeout_s}s")
|
|
||||||
raise OSError("WiFi connect timeout")
|
raise OSError("WiFi connect timeout")
|
||||||
utime.sleep_ms(200)
|
utime.sleep_ms(250)
|
||||||
|
|
||||||
ip, mask, gw, dns = _wifi_sta.ifconfig()
|
ip, mask, gw, dns = _wifi_sta.ifconfig()
|
||||||
mac = ":".join(f"{b:02x}" for b in _wifi_sta.config("mac"))
|
mac = ":".join(f"{b:02x}" for b in _wifi_sta.config("mac"))
|
||||||
info("WiFi connected!")
|
info("WiFi connected!")
|
||||||
info(f" SSID : {ssid}")
|
info(f" SSID : {ssid}")
|
||||||
info(f" MAC : {mac}")
|
info(f" MAC : {mac}")
|
||||||
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}")
|
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}")
|
||||||
utime.sleep_ms(250)
|
utime.sleep_ms(2000)
|
||||||
return ip
|
return ip
|
||||||
|
except Exception as e:
|
||||||
|
last_error = e
|
||||||
|
log_err(f"WiFi connect attempt {attempt + 1} failed: {e}")
|
||||||
|
utime.sleep_ms(1000)
|
||||||
|
|
||||||
|
raise last_error
|
||||||
|
|
||||||
|
|
||||||
def check_wifi():
|
def check_wifi():
|
||||||
@@ -581,15 +603,7 @@ def check_wifi():
|
|||||||
|
|
||||||
log_err("WiFi lost connection — attempting reconnect...")
|
log_err("WiFi lost connection — attempting reconnect...")
|
||||||
try:
|
try:
|
||||||
_wifi_sta.active(True)
|
ip = connect_wifi(WIFI_SSID, WIFI_PASSWORD, timeout_s=15)
|
||||||
_wifi_sta.connect(WIFI_SSID, WIFI_PASSWORD)
|
|
||||||
deadline = utime.time() + 15
|
|
||||||
while not _wifi_sta.isconnected():
|
|
||||||
if utime.time() > deadline:
|
|
||||||
log_err("WiFi reconnect timeout")
|
|
||||||
return
|
|
||||||
utime.sleep_ms(200)
|
|
||||||
ip, mask, gw, dns = _wifi_sta.ifconfig()
|
|
||||||
info(f"WiFi reconnected! IP:{ip}")
|
info(f"WiFi reconnected! IP:{ip}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_err(f"WiFi reconnect failed: {e}")
|
log_err(f"WiFi reconnect failed: {e}")
|
||||||
@@ -874,6 +888,7 @@ def connect_mqtt():
|
|||||||
info(f"Connecting to MQTT broker {MQTT_BROKER}:{MQTT_PORT} ...")
|
info(f"Connecting to MQTT broker {MQTT_BROKER}:{MQTT_PORT} ...")
|
||||||
last_error = None
|
last_error = None
|
||||||
for attempt in range(3):
|
for attempt in range(3):
|
||||||
|
gc.collect()
|
||||||
try:
|
try:
|
||||||
if client_ref is not None:
|
if client_ref is not None:
|
||||||
try:
|
try:
|
||||||
@@ -897,21 +912,14 @@ def connect_mqtt():
|
|||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
log_err(f"MQTT connect attempt {attempt + 1} failed: {e}")
|
log_err(f"MQTT connect attempt {attempt + 1} failed: {type(e).__name__}: {e}")
|
||||||
|
gc.collect()
|
||||||
utime.sleep_ms(1000)
|
utime.sleep_ms(1000)
|
||||||
|
|
||||||
_mqtt_connected = False
|
_mqtt_connected = False
|
||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
|
|
||||||
_mqtt_check_interval_ms = 30000
|
|
||||||
_last_mqtt_check = 0
|
|
||||||
_discovery_queue = []
|
|
||||||
_discovery_idx = 0
|
|
||||||
_last_discovery_ms = 0
|
|
||||||
_DISCOVERY_INTERVAL_MS = 200
|
|
||||||
|
|
||||||
|
|
||||||
def check_mqtt():
|
def check_mqtt():
|
||||||
global client_ref, _mqtt_connected, _last_mqtt_check
|
global client_ref, _mqtt_connected, _last_mqtt_check
|
||||||
now = utime.ticks_ms()
|
now = utime.ticks_ms()
|
||||||
@@ -953,13 +961,23 @@ def check_mqtt():
|
|||||||
return True
|
return True
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
log_err(f"MQTT reconnect attempt {attempt + 1} failed: {e2}")
|
log_err(f"MQTT reconnect attempt {attempt + 1} failed: {e2}")
|
||||||
|
gc.collect()
|
||||||
utime.sleep_ms(2000)
|
utime.sleep_ms(2000)
|
||||||
|
|
||||||
log_err("MQTT reconnection failed after 3 attempts")
|
log_err("MQTT reconnection failed after 3 attempts")
|
||||||
return False
|
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):
|
def _publish_discovery_entity(client, topic, payload, log_msg):
|
||||||
|
gc.collect()
|
||||||
client.publish(topic, ujson.dumps(payload), retain=True)
|
client.publish(topic, ujson.dumps(payload), retain=True)
|
||||||
info(log_msg)
|
info(log_msg)
|
||||||
|
|
||||||
@@ -1233,6 +1251,7 @@ def service_discovery():
|
|||||||
if _last_discovery_ms and utime.ticks_diff(now, _last_discovery_ms) < _DISCOVERY_INTERVAL_MS:
|
if _last_discovery_ms and utime.ticks_diff(now, _last_discovery_ms) < _DISCOVERY_INTERVAL_MS:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
topic, payload, log_msg = _discovery_queue[_discovery_idx]
|
topic, payload, log_msg = _discovery_queue[_discovery_idx]
|
||||||
try:
|
try:
|
||||||
if isinstance(payload, bytes):
|
if isinstance(payload, bytes):
|
||||||
@@ -1243,7 +1262,6 @@ def service_discovery():
|
|||||||
log_err(f"Discovery publish failed for {topic}: {e}")
|
log_err(f"Discovery publish failed for {topic}: {e}")
|
||||||
_discovery_idx += 1
|
_discovery_idx += 1
|
||||||
_last_discovery_ms = utime.ticks_ms()
|
_last_discovery_ms = utime.ticks_ms()
|
||||||
if (_discovery_idx & 3) == 0:
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
|
||||||
@@ -1268,22 +1286,70 @@ def apply_motion_defaults():
|
|||||||
send_vfd_state()
|
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
|
# Main
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def 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("=" * 48)
|
||||||
info("Gauge MQTT controller starting")
|
info("Gauge MQTT controller starting")
|
||||||
|
info(f"Heap free: {gc.mem_free()} bytes")
|
||||||
info("=" * 48)
|
info("=" * 48)
|
||||||
|
|
||||||
connect_wifi(WIFI_SSID, WIFI_PASSWORD, force_reconnect=True)
|
gc.collect()
|
||||||
|
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
|
||||||
|
|
||||||
|
mqtt_attempts = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
connect_mqtt()
|
connect_mqtt()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
mqtt_attempts += 1
|
||||||
|
log_err(f"MQTT connect failed: {e} (attempt {mqtt_attempts})")
|
||||||
|
if mqtt_attempts % 3 == 0:
|
||||||
|
log_err("WiFi may be stale — forcing reconnect...")
|
||||||
|
try:
|
||||||
|
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)
|
_subscribe_all(client_ref)
|
||||||
schedule_discovery()
|
schedule_discovery()
|
||||||
|
|
||||||
|
publish_backlight_states(client_ref)
|
||||||
apply_motion_defaults()
|
apply_motion_defaults()
|
||||||
info("Draining initial retained messages...")
|
info("Draining initial retained messages...")
|
||||||
for _ in range(50):
|
for _ in range(50):
|
||||||
@@ -1296,6 +1362,10 @@ def main():
|
|||||||
gauge_last_rezero[i] = utime.ticks_ms()
|
gauge_last_rezero[i] = utime.ticks_ms()
|
||||||
info("Home command sent")
|
info("Home command sent")
|
||||||
|
|
||||||
|
utime.sleep_ms(100)
|
||||||
|
_restore_led_states()
|
||||||
|
info("LED effects restored")
|
||||||
|
|
||||||
info("Publishing state...")
|
info("Publishing state...")
|
||||||
publish_online(client_ref)
|
publish_online(client_ref)
|
||||||
publish_state(client_ref)
|
publish_state(client_ref)
|
||||||
|
|||||||
Reference in New Issue
Block a user