Compare commits
6 Commits
debug
...
15257ae6f2
| Author | SHA1 | Date | |
|---|---|---|---|
| 15257ae6f2 | |||
|
795eb0ecf3
|
|||
|
558c5b18c2
|
|||
|
fa66dd70d4
|
|||
|
b14bdf7fc3
|
|||
|
427dde8c72
|
@@ -794,6 +794,24 @@ bool parsePosQuery(const String& line) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Answers `CFG?` with speed and acceleration for every gauge.
|
||||
// Emits one `CFG <id> <maxSpeed> <accel>` line per gauge, then replies `OK`.
|
||||
bool parseCfgQuery(const String& line) {
|
||||
if (line == "CFG?") {
|
||||
for (uint8_t i = 0; i < GAUGE_COUNT; i++) {
|
||||
CMD_PORT.print("CFG ");
|
||||
CMD_PORT.print(i);
|
||||
CMD_PORT.print(' ');
|
||||
CMD_PORT.print((int)gauges[i].maxSpeed);
|
||||
CMD_PORT.print(' ');
|
||||
CMD_PORT.println((int)gauges[i].accel);
|
||||
}
|
||||
sendReply("OK");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Answers the mandatory life question: are you there?
|
||||
// Reply: `PONG`.
|
||||
bool parsePing(const String& line) {
|
||||
@@ -1058,6 +1076,7 @@ void processLine(const String& line) {
|
||||
if (parseHome(line)) return;
|
||||
if (parseSweep(line)) return;
|
||||
if (parsePosQuery(line)) return;
|
||||
if (parseCfgQuery(line)) return;
|
||||
if (parseLedQuery(line)) return;
|
||||
if (parseLed(line)) return;
|
||||
if (parseBlink(line)) return;
|
||||
|
||||
62
boot.py
62
boot.py
@@ -1,62 +0,0 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
2469
esp-home-rewrite.yaml
Normal file
2469
esp-home-rewrite.yaml
Normal file
File diff suppressed because it is too large
Load Diff
116
gauge.py
116
gauge.py
@@ -28,6 +28,16 @@ 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
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -151,27 +161,19 @@ 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 = 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."""
|
||||
_ensure_arduino().write((cmd + "\n").encode())
|
||||
_arduino.write((cmd + "\n").encode())
|
||||
info(f"Arduino → {cmd}")
|
||||
|
||||
|
||||
def arduino_recv():
|
||||
"""Print any lines waiting in the Arduino RX buffer."""
|
||||
uart = _ensure_arduino()
|
||||
while uart.any():
|
||||
line = uart.readline()
|
||||
while _arduino.any():
|
||||
line = _arduino.readline()
|
||||
if line:
|
||||
print(f"[{_ts()}] ARDU {line.decode().strip()}")
|
||||
|
||||
@@ -538,14 +540,11 @@ _WIFI_CONNECT_ATTEMPTS = 3
|
||||
def _reset_wifi_interface():
|
||||
global _wifi_sta
|
||||
_wifi_sta = network.WLAN(network.STA_IF)
|
||||
if not _wifi_sta.active():
|
||||
if _wifi_sta.active():
|
||||
_wifi_sta.active(False)
|
||||
utime.sleep_ms(200)
|
||||
_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):
|
||||
@@ -562,7 +561,6 @@ 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}) ...")
|
||||
if not _wifi_sta.isconnected():
|
||||
_reset_wifi_interface()
|
||||
try:
|
||||
_wifi_sta.connect(ssid, password)
|
||||
@@ -578,7 +576,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(2000)
|
||||
utime.sleep_ms(500)
|
||||
return ip
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
@@ -603,7 +601,7 @@ def check_wifi():
|
||||
|
||||
log_err("WiFi lost connection — attempting reconnect...")
|
||||
try:
|
||||
ip = connect_wifi(WIFI_SSID, WIFI_PASSWORD, timeout_s=15)
|
||||
ip = connect_wifi(WIFI_SSID, WIFI_PASSWORD, timeout_s=15, force_reconnect=True)
|
||||
info(f"WiFi reconnected! IP:{ip}")
|
||||
except Exception as e:
|
||||
log_err(f"WiFi reconnect failed: {e}")
|
||||
@@ -913,6 +911,10 @@ 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)
|
||||
|
||||
@@ -920,6 +922,27 @@ 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()
|
||||
@@ -961,6 +984,10 @@ 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)
|
||||
|
||||
@@ -968,17 +995,9 @@ 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(payload), retain=True)
|
||||
client.publish(topic, ujson.dumps(_compact_discovery_payload(payload)), retain=True)
|
||||
info(log_msg)
|
||||
|
||||
|
||||
@@ -1286,42 +1305,12 @@ 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")
|
||||
@@ -1329,7 +1318,7 @@ def main():
|
||||
info("=" * 48)
|
||||
|
||||
gc.collect()
|
||||
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
|
||||
connect_wifi(WIFI_SSID, WIFI_PASSWORD, force_reconnect=True)
|
||||
|
||||
mqtt_attempts = 0
|
||||
while True:
|
||||
@@ -1342,14 +1331,13 @@ def main():
|
||||
if mqtt_attempts % 3 == 0:
|
||||
log_err("WiFi may be stale — forcing reconnect...")
|
||||
try:
|
||||
connect_wifi(WIFI_SSID, WIFI_PASSWORD)
|
||||
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)
|
||||
schedule_discovery()
|
||||
|
||||
publish_backlight_states(client_ref)
|
||||
apply_motion_defaults()
|
||||
info("Draining initial retained messages...")
|
||||
for _ in range(50):
|
||||
@@ -1362,10 +1350,6 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user