16 Commits

3 changed files with 188 additions and 52 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;

64
boot.py Normal file
View File

@@ -0,0 +1,64 @@
"""
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

174
gauge.py
View File

@@ -28,6 +28,16 @@ import gc
from umqtt.robust import MQTTClient from umqtt.robust import MQTTClient
from machine import UART 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 # Logging
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -524,32 +534,56 @@ _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)
if _wifi_sta.active():
_wifi_sta.active(False)
utime.sleep_ms(200)
_wifi_sta.active(True) _wifi_sta.active(True)
if _wifi_sta.isconnected(): utime.sleep_ms(500)
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: _reset_wifi_interface()
log_err(f"WiFi connect timeout after {timeout_s}s") try:
raise OSError("WiFi connect timeout") _wifi_sta.connect(ssid, password)
utime.sleep_ms(200) deadline = utime.time() + timeout_s
ip, mask, gw, dns = _wifi_sta.ifconfig() while not _wifi_sta.isconnected():
mac = ":".join(f"{b:02x}" for b in _wifi_sta.config("mac")) if utime.time() > deadline:
info("WiFi connected!") raise OSError("WiFi connect timeout")
info(f" SSID : {ssid}") utime.sleep_ms(250)
info(f" MAC : {mac}")
info(f" IP : {ip} mask:{mask} gw:{gw} dns:{dns}") ip, mask, gw, dns = _wifi_sta.ifconfig()
return ip 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(500)
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 +601,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, force_reconnect=True)
_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,19 +884,42 @@ 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 client = MQTTClient(
_mqtt_connected = True client_id=MQTT_CLIENT_ID,
info(f"MQTT connected 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}")
try:
client.sock.close()
except Exception:
pass
gc.collect()
utime.sleep_ms(1000)
_mqtt_connected = False
raise last_error
_mqtt_check_interval_ms = 30000 _mqtt_check_interval_ms = 30000
@@ -878,7 +927,7 @@ _last_mqtt_check = 0
_discovery_queue = [] _discovery_queue = []
_discovery_idx = 0 _discovery_idx = 0
_last_discovery_ms = 0 _last_discovery_ms = 0
_DISCOVERY_INTERVAL_MS = 200 _DISCOVERY_INTERVAL_MS = 350
def check_mqtt(): def check_mqtt():
@@ -922,6 +971,11 @@ 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}")
try:
client_ref.sock.close()
except Exception:
pass
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")
@@ -929,6 +983,7 @@ def check_mqtt():
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 +1179,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 +1257,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):
@@ -1243,13 +1298,30 @@ def apply_motion_defaults():
def main(): def main():
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) gc.collect()
connect_wifi(WIFI_SSID, WIFI_PASSWORD, force_reconnect=True)
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, force_reconnect=True)
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()