32 Commits

Author SHA1 Message Date
23834d660c Reverted a lot of cruft 2026-04-21 23:52:52 +02:00
a0821fed80 Reverted a lot of cruft 2026-04-21 23:50:58 +02:00
014346c442 Reverted a lot of cruft 2026-04-21 23:50:39 +02:00
cf545cb849 Reverted a lot of cruft 2026-04-21 23:47:19 +02:00
9af28acb51 Reverted a lot of cruft 2026-04-21 23:43:01 +02:00
af42a1c4de Reverted a lot of cruft 2026-04-21 23:40:26 +02:00
ffd807ff45 Discovery missing effects 2026-04-21 23:34:17 +02:00
c57e0d8ae2 Discovery missing effects 2026-04-21 23:29:28 +02:00
a294ce1fd9 Discovery missing effects 2026-04-21 23:22:50 +02:00
179fb77b95 Discovery missing effects 2026-04-21 23:13:38 +02:00
cf36a2c07f Discovery missing effects 2026-04-21 23:08:16 +02:00
70a6719553 Discovery missing effects 2026-04-21 23:06:32 +02:00
cba7d907e9 OTA cleanup 2026-04-21 22:44:46 +02:00
ee0d85fa73 OpenCode experiments... 2026-04-21 21:27:28 +02:00
0b4f285ddb OpenCode experiments... 2026-04-21 21:24:59 +02:00
415310b1f9 OpenCode experiments... 2026-04-21 21:22:26 +02:00
b1afa2f06c OpenCode experiments... 2026-04-21 21:17:48 +02:00
423b9f91be OpenCode experiments... 2026-04-21 21:15:10 +02:00
ad50fd2ee5 Wifi bugs... 2026-04-21 21:07:42 +02:00
ae6d72b292 Now by hand... 2026-04-21 21:05:29 +02:00
2ea19b14f8 Attempt with tcp probe 2026-04-21 20:26:31 +02:00
1f8ba45685 Attempt with tcp probe 2026-04-21 20:10:38 +02:00
5a98a3c63b No more reconnect in main 2026-04-21 20:04:11 +02:00
afe70da24d MQTT trouble,still 2026-04-21 19:57:22 +02:00
0d7ae5cedc MQTT trouble 2026-04-21 18:52:53 +02:00
b3e2ef0f81 MQTT trouble 2026-04-21 18:41:10 +02:00
6068628d13 MQTT trouble 2026-04-21 18:40:10 +02:00
64b0aa482f MQTT trouble 2026-04-21 18:34:29 +02:00
2e5e410897 More garbage collection 2026-04-21 01:43:57 +02:00
4045da8964 WiFi changes again 2026-04-21 01:39:26 +02:00
81099d9887 Top speed lowered 2026-04-21 01:32:34 +02:00
355faadc31 WiFi made more resilient 2026-04-21 01:32:11 +02:00
3 changed files with 224 additions and 61 deletions

View File

@@ -258,7 +258,7 @@ struct Gauge {
long homingBackoffSteps = 3800; // Deliberately a touch past full reverse travel. long homingBackoffSteps = 3800; // Deliberately a touch past full reverse travel.
float velocity = 0.0f; float velocity = 0.0f;
float maxSpeed = 5000.0f; float maxSpeed = 4000.0f;
float accel = 6000.0f; float accel = 6000.0f;
float homingSpeed = 500.0f; float homingSpeed = 500.0f;

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

221
gauge.py
View File

@@ -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,32 +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 connect_wifi(ssid, password, timeout_s=15): def _reset_wifi_interface():
global _wifi_sta global _wifi_sta
_wifi_sta = network.WLAN(network.STA_IF) _wifi_sta = network.WLAN(network.STA_IF)
_wifi_sta.active(True) if not _wifi_sta.active():
if _wifi_sta.isconnected(): _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):
global _wifi_sta
_wifi_sta = network.WLAN(network.STA_IF)
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)
return ip return ip
info(f"WiFi connecting to '{ssid}' ...")
_wifi_sta.connect(ssid, password) last_error = None
deadline = utime.time() + timeout_s for attempt in range(_WIFI_CONNECT_ATTEMPTS):
while not _wifi_sta.isconnected(): info(f"WiFi connecting to '{ssid}' (attempt {attempt + 1}/{_WIFI_CONNECT_ATTEMPTS}) ...")
if utime.time() > deadline: if not _wifi_sta.isconnected():
log_err(f"WiFi connect timeout after {timeout_s}s") _reset_wifi_interface()
raise OSError("WiFi connect timeout") try:
utime.sleep_ms(200) _wifi_sta.connect(ssid, password)
ip, mask, gw, dns = _wifi_sta.ifconfig() deadline = utime.time() + timeout_s
mac = ":".join(f"{b:02x}" for b in _wifi_sta.config("mac")) while not _wifi_sta.isconnected():
info("WiFi connected!") if utime.time() > deadline:
info(f" SSID : {ssid}") raise OSError("WiFi connect timeout")
info(f" MAC : {mac}") utime.sleep_ms(250)
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}")
return ip ip, mask, gw, dns = _wifi_sta.ifconfig()
mac = ":".join(f"{b:02x}" for b in _wifi_sta.config("mac"))
info("WiFi connected!")
info(f" SSID : {ssid}")
info(f" MAC : {mac}")
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}")
utime.sleep_ms(2000)
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():
@@ -567,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}")
@@ -858,27 +886,38 @@ def _subscribe_all(c):
def connect_mqtt(): def connect_mqtt():
global client_ref, _mqtt_connected global client_ref, _mqtt_connected
info(f"Connecting to MQTT broker {MQTT_BROKER}:{MQTT_PORT} ...") info(f"Connecting to MQTT broker {MQTT_BROKER}:{MQTT_PORT} ...")
client = MQTTClient( last_error = None
client_id=MQTT_CLIENT_ID, for attempt in range(3):
server=MQTT_BROKER, gc.collect()
port=MQTT_PORT, try:
user=MQTT_USER, if client_ref is not None:
password=MQTT_PASSWORD, try:
keepalive=30, client_ref.disconnect()
) except Exception:
client.set_callback(on_message) pass
client.connect()
client_ref = client
_mqtt_connected = True
info(f"MQTT connected client_id={MQTT_CLIENT_ID}")
client = MQTTClient(
client_id=MQTT_CLIENT_ID,
server=MQTT_BROKER,
port=MQTT_PORT,
user=MQTT_USER,
password=MQTT_PASSWORD,
keepalive=30,
)
client.set_callback(on_message)
client.connect()
client_ref = client
_mqtt_connected = True
info(f"MQTT connected client_id={MQTT_CLIENT_ID}")
return
except Exception as e:
last_error = e
log_err(f"MQTT connect attempt {attempt + 1} failed: {type(e).__name__}: {e}")
gc.collect()
utime.sleep_ms(1000)
_mqtt_check_interval_ms = 30000 _mqtt_connected = False
_last_mqtt_check = 0 raise last_error
_discovery_queue = []
_discovery_idx = 0
_last_discovery_ms = 0
_DISCOVERY_INTERVAL_MS = 200
def check_mqtt(): def check_mqtt():
@@ -922,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)
@@ -1124,10 +1173,7 @@ def _append_vfd_discovery(entries, dev_ref):
"cmd_t": vfd_topics["set"], "cmd_t": vfd_topics["set"],
"stat_t": vfd_topics["state"], "stat_t": vfd_topics["state"],
"avty_t": gauge_topics[0]["status"], "avty_t": gauge_topics[0]["status"],
"max": 4,
"mode": "text",
"icon": "mdi:alpha-box", "icon": "mdi:alpha-box",
"pattern": "^[0-9A-Fa-f-]{0,4}$",
"dev": dev_ref, "dev": dev_ref,
}, },
"Discovery: VFD text", "Discovery: VFD text",
@@ -1205,15 +1251,18 @@ 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]
if isinstance(payload, bytes): try:
client_ref.publish(topic, payload, retain=True) if isinstance(payload, bytes):
else: client_ref.publish(topic, payload, retain=True)
_publish_discovery_entity(client_ref, topic, payload, log_msg) else:
_publish_discovery_entity(client_ref, topic, payload, log_msg)
except Exception as 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()
def publish_online(client): def publish_online(client):
@@ -1237,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)
gc.collect()
connect_wifi(WIFI_SSID, WIFI_PASSWORD) connect_wifi(WIFI_SSID, WIFI_PASSWORD)
connect_mqtt() mqtt_attempts = 0
while True:
try:
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):
@@ -1265,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)