Compare commits
18 Commits
e525dba0c4
...
debug
| Author | SHA1 | Date | |
|---|---|---|---|
| 23834d660c | |||
| a0821fed80 | |||
| 014346c442 | |||
| cf545cb849 | |||
| 9af28acb51 | |||
| af42a1c4de | |||
| ffd807ff45 | |||
| c57e0d8ae2 | |||
| a294ce1fd9 | |||
| 179fb77b95 | |||
| cf36a2c07f | |||
| 70a6719553 | |||
| cba7d907e9 | |||
| ee0d85fa73 | |||
| 0b4f285ddb | |||
| 415310b1f9 | |||
| b1afa2f06c | |||
| 423b9f91be |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,4 +32,3 @@
|
||||
*.out
|
||||
*.app
|
||||
|
||||
.codex
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -6,8 +6,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
Main firmware lives in `Gaugecontroller/Gaugecontroller.ino`. Requires the **FastLED** library (`arduino-cli lib install FastLED`). Use the Arduino IDE or `arduino-cli`:
|
||||
|
||||
The ESP32 bridge runs ESPHome; the config is in `gaugecontroller.yaml`.
|
||||
|
||||
```bash
|
||||
# Compile (replace board/port as needed)
|
||||
arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
||||
@@ -60,7 +58,7 @@ The sketch controls `GAUGE_COUNT` stepper-motor gauges using a trapezoidal veloc
|
||||
|
||||
### Key data structures
|
||||
|
||||
- `GaugePins` — hardware pin mapping per gauge (dir, step, enable, active-high/low polarity flags, `ledOrder` string). Declared `constexpr` so `TOTAL_LEDS` can be computed from it at compile time. Configured in the `gaugePins[]` array at the top.
|
||||
- `GaugePins` — hardware pin mapping per gauge (dir, step, enable, active-high/low polarity flags, `ledCount`). Declared `constexpr` so `TOTAL_LEDS` can be computed from it at compile time. Configured in the `gaugePins[]` array at the top.
|
||||
- `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode.
|
||||
|
||||
### Motion control (`updateGauge`)
|
||||
@@ -78,7 +76,7 @@ When `sweepEnabled`, `updateSweepTarget` bounces `targetPos` between `minPos` an
|
||||
|
||||
### LED strip
|
||||
|
||||
Two LED strips are driven: main backlight/status LEDs on `LED_DATA_PIN` (currently 22) and dial indicator LEDs on `INDICATOR_LED_DATA_PIN` (currently 36). The serial protocol still exposes one logical per-gauge LED segment: `0-2` backlight, `3-4` indicators, `5-6` status. `gaugePins[i].ledOrder` is a per-LED type string (one char per LED, `'G'` = GRB-ordered, `'R'` = RGB-ordered) and its length defines the logical LED count. `TOTAL_LEDS`, `TOTAL_MAIN_LEDS`, and `TOTAL_INDICATOR_LEDS` are computed at compile time. Per-gauge logical and physical offsets are cached in `setup()`. LED writes dirty only their physical strip, and the loop flushes each FastLED controller independently with `showLeds()`.
|
||||
One shared WS2812B strip is driven from `LED_DATA_PIN` (currently 22). Each gauge owns a contiguous segment of the strip; `gaugePins[i].ledCount` sets the segment length (0 = no LEDs). `TOTAL_LEDS` is computed at compile time via `constexpr sumLedCounts()` — no manual constant to keep in sync. Per-gauge offsets into the flat `leds[]` array are computed once in `setup()` into `gaugeLedOffset[]`. LED commands and effects mark the strip dirty, and `FastLED.show()` is called once per main-loop iteration if anything changed.
|
||||
|
||||
### Serial command protocol
|
||||
|
||||
@@ -106,6 +104,6 @@ All commands reply `OK` or `ERR BAD_ID` / `ERR BAD_CMD` etc.
|
||||
### Adding gauges
|
||||
|
||||
1. Increment `GAUGE_COUNT`.
|
||||
2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including the `ledOrder` string — one char per LED, `'G'` for GRB or `'R'` for RGB).
|
||||
2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including `ledCount`).
|
||||
3. Tune `maxPos` and `homingBackoffSteps` in the corresponding `Gauge` default or at runtime.
|
||||
4. `TOTAL_LEDS`, `gaugeLedOffset[]`, and `gaugeLedCount[]` update automatically — no manual changes needed.
|
||||
4. `TOTAL_LEDS` and `gaugeLedOffset[]` update automatically — no manual changes needed.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
52
README.md
52
README.md
@@ -7,7 +7,7 @@ A dedicated gauge controller for Arduinos.
|
||||
This repository contains:
|
||||
|
||||
- `Gaugecontroller/Gaugecontroller.ino`: the Arduino Mega firmware for the stepper gauges, LEDs, and integrated HV5812-based VFD
|
||||
- `gaugecontroller.yaml`: the ESPHome-based ESP32 firmware that exposes the gauges and VFD to Home Assistant via the native API
|
||||
- `gauge.py`: the ESP32 / MicroPython MQTT bridge that exposes the controller to Home Assistant
|
||||
|
||||
## VFD Support
|
||||
|
||||
@@ -48,19 +48,16 @@ Rules:
|
||||
- shorter values are right-aligned
|
||||
- leading zeroes are preserved if they are part of the input
|
||||
|
||||
## Home Assistant Integration
|
||||
## Home Assistant Entities
|
||||
|
||||
The ESPHome firmware in `gaugecontroller.yaml` exposes entities to Home Assistant via the native API:
|
||||
The MQTT bridge publishes Home Assistant discovery entities for the VFD:
|
||||
|
||||
### Gauge Controls
|
||||
- Number entities for each gauge's target value (with unit conversion)
|
||||
- Number entities for speed and acceleration (diagnostic)
|
||||
- Rezero buttons for each gauge and all gauges
|
||||
|
||||
### VFD Display
|
||||
- `VFD Display`: text entity for the displayed value
|
||||
- `VFD Decimal Point`: switch entity
|
||||
- `VFD Alarm`: switch entity
|
||||
- `VFD Display`
|
||||
text entity for the displayed value
|
||||
- `VFD Decimal Point`
|
||||
switch entity
|
||||
- `VFD Alarm`
|
||||
switch entity
|
||||
|
||||
The display is intentionally exposed as a text entity rather than a numeric entity so that:
|
||||
|
||||
@@ -68,12 +65,27 @@ The display is intentionally exposed as a text entity rather than a numeric enti
|
||||
- hexadecimal values like `DEAD` or `BEEF` work
|
||||
- clearing the display is possible with an empty value
|
||||
|
||||
### LED Controls
|
||||
- RGB light entity for each gauge's backlight with effects (Blink, Breathe, Double Flash)
|
||||
- Binary light entities for each gauge's red/green indicators and status lights
|
||||
## MQTT Topics
|
||||
|
||||
### Diagnostics
|
||||
- WiFi signal sensor
|
||||
- Uptime sensor
|
||||
- IP address and SSID text sensors
|
||||
- Arduino Last Message sensor
|
||||
Using the configured `mqtt_prefix` from `config.json`, the VFD topics are:
|
||||
|
||||
- `<prefix>/vfd/set`
|
||||
- `<prefix>/vfd/state`
|
||||
- `<prefix>/vfd/decimal_point/set`
|
||||
- `<prefix>/vfd/decimal_point/state`
|
||||
- `<prefix>/vfd/alarm/set`
|
||||
- `<prefix>/vfd/alarm/state`
|
||||
|
||||
Example with the default prefix `gauges`:
|
||||
|
||||
- `gauges/vfd/set`
|
||||
- `gauges/vfd/decimal_point/set`
|
||||
- `gauges/vfd/alarm/set`
|
||||
|
||||
Example payloads:
|
||||
|
||||
- publish `0123` to `gauges/vfd/set`
|
||||
- publish `ON` to `gauges/vfd/decimal_point/set`
|
||||
- publish `OFF` to `gauges/vfd/alarm/set`
|
||||
|
||||
The MQTT bridge then converts that into the correct Arduino serial command such as `VFD 0123.`.
|
||||
|
||||
@@ -183,14 +183,13 @@ Then connect the motor side of that driver to:
|
||||
|
||||
according to the driver board you are using.
|
||||
|
||||
## 14. Wire The WS2812 LEDs
|
||||
## 14. Wire The WS2812B LEDs
|
||||
|
||||
Connect:
|
||||
|
||||
- `Mega D22` -> main backlight/status strip `DIN`
|
||||
- `Mega D36` -> indicator strip `DIN`
|
||||
- `5V LED supply` -> both strip `5V` inputs
|
||||
- both strip `GND` inputs -> common ground rail
|
||||
- `Mega D22` -> `WS2812B DIN`
|
||||
- `5V LED supply` -> `WS2812B 5V`
|
||||
- `WS2812B GND` -> common ground rail
|
||||
|
||||
If the LED chain is long or bright:
|
||||
|
||||
|
||||
@@ -205,10 +205,9 @@ If `D8` and `D9` come from separate fly wires to the stripboard, keep them in th
|
||||
|
||||
Route:
|
||||
|
||||
- `D22` -> main backlight/status strip `DIN`
|
||||
- `D36` -> indicator strip `DIN`
|
||||
- `5V` -> both strip `5V` inputs
|
||||
- `GND` -> both strip `GND` inputs
|
||||
- `D22` -> `WS2812 DIN`
|
||||
- `5V` -> `WS2812 5V`
|
||||
- `GND` -> `WS2812 GND`
|
||||
|
||||
Keep the LED connector in the low-voltage area.
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
46
changes.md
46
changes.md
@@ -1,46 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## 2026-04-27 — Arduino firmware refactor (`Gaugecontroller/Gaugecontroller.ino`)
|
||||
|
||||
### Non-blocking VFD multiplexer
|
||||
`vfd::refresh()` previously held each digit for 2000 µs via `delayMicroseconds`,
|
||||
which capped the effective stepper pulse rate at roughly 500 Hz regardless of
|
||||
`maxSpeed`. It now tracks `phaseStartMicros`/`phaseActive` and returns
|
||||
immediately while the digit is still being held; the main loop runs at
|
||||
microsecond cadence again and the configured `maxSpeed = 4000.0f` steps/s is
|
||||
actually achievable.
|
||||
|
||||
### Fixed-buffer command parser (no more `String` heap churn)
|
||||
Replaced `String rxLine` with `char rxBuf[128]` and converted the entire
|
||||
command pipeline to take `const char*`:
|
||||
|
||||
- `processLine`, `sendReply`, `vfd::parseCommand`
|
||||
- All `parse*` functions: `parseSet`, `parseSpeed`, `parseAccel`, `parseEnable`,
|
||||
`parseZero`, `parseHome`, `parseSweep`, `parsePosQuery`, `parseCfgQuery`,
|
||||
`parseLedQuery`, `parseLed`, `parseBlink`, `parseBreathe`, `parseDflash`,
|
||||
`parseVfd`, `parsePing`.
|
||||
|
||||
`parseSpeed` / `parseAccel` / `parseSweep` use `strncmp` + `atof` because the
|
||||
default AVR-libc `sscanf` doesn't support `%f`. No allocations on the command
|
||||
path; the Mega's heap no longer fragments over time.
|
||||
|
||||
### Cached `ledNeedsSwap[TOTAL_LEDS]`
|
||||
Per-LED RGB-vs-GRB swap flag is now precomputed once in `setup()` from
|
||||
`gaugePins[].ledOrder`. `encodeForStrip` is a single array index instead of
|
||||
walking the gauge table on every LED read/write.
|
||||
|
||||
### Cached step direction per gauge
|
||||
Added `Gauge.lastDir`. `setDir()` skips the DIR-pin `digitalWrite` when the
|
||||
direction hasn't flipped (the common case during a step run) and adds a 1 µs
|
||||
DIR-to-STEP setup delay only when it actually flips.
|
||||
|
||||
### Cleanups
|
||||
- Removed the `absf` helper; use `fabsf` consistently.
|
||||
- Removed the `+ 0.0001f` epsilon in the trapezoidal braking-distance divisor.
|
||||
`parseAccel` already rejects `accel <= 0`, so the divisor is always positive.
|
||||
- Fixed the `<r> <ig> <b>` typo to `<r> <g> <b>` in the protocol comment for
|
||||
`DFLASH`.
|
||||
|
||||
### Build verification
|
||||
`arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller`:
|
||||
17758 B flash (6%), 1845 B SRAM (22%).
|
||||
@@ -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)
|
||||
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,6 +562,7 @@ 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)
|
||||
@@ -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)
|
||||
2394
gaugecontroller.yaml
2394
gaugecontroller.yaml
File diff suppressed because it is too large
Load Diff
17
wiring.md
17
wiring.md
@@ -163,22 +163,19 @@ Also connect:
|
||||
|
||||
If your driver boards need separate motor power, supply that from the proper motor supply. Do not power motors from the Mega `5V` pin.
|
||||
|
||||
## WS2812 LED Strips
|
||||
## WS2812B LED Strip
|
||||
|
||||
The current sketch expects two LED data chains. Backlight and status LEDs stay
|
||||
on the main strip; the red/green dial indicator LEDs are on their own strip.
|
||||
The current sketch expects one shared WS2812B chain.
|
||||
|
||||
| Mega Pin | LED Strip |
|
||||
| Mega Pin | WS2812B |
|
||||
|---|---|
|
||||
| `D22` | main backlight/status `DIN` |
|
||||
| `D36` | indicator `DIN` |
|
||||
| `5V` | both strips `5V` |
|
||||
| `GND` | both strips `GND` |
|
||||
| `D22` | `DIN` |
|
||||
| `5V` | `5V` |
|
||||
| `GND` | `GND` |
|
||||
|
||||
Notes:
|
||||
|
||||
- the command protocol still exposes `7 LEDs per gauge`
|
||||
- logical indices `0-2` are backlight, `3-4` are indicators, and `5-6` are status
|
||||
- the code expects `7 LEDs per gauge`, so `21 LEDs total`
|
||||
- use a proper 5V supply sized for the LED current
|
||||
- keep LED ground common with the Mega
|
||||
|
||||
|
||||
Reference in New Issue
Block a user