Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e525dba0c4 | |||
| 1b699352ce | |||
| c32d208854 | |||
| db05bc0864 | |||
| 5f73e75f5b | |||
| 5656986768 | |||
| aa029587a4 | |||
| abbbd16b5c | |||
| e63867ba5e | |||
| 8bdae1da9b | |||
| 361cf52252 | |||
| 2d63ec6006 | |||
| 511ee05712 | |||
| 03ab8604ba | |||
| edb973bb61 | |||
|
bffcf62cae
|
|||
| 27597bceab | |||
| 016de2ccb4 | |||
| 15257ae6f2 | |||
|
795eb0ecf3
|
|||
|
558c5b18c2
|
|||
|
fa66dd70d4
|
|||
|
b14bdf7fc3
|
|||
|
427dde8c72
|
|||
| ad50fd2ee5 | |||
| ae6d72b292 | |||
| 2ea19b14f8 | |||
| 1f8ba45685 | |||
| 5a98a3c63b | |||
| afe70da24d | |||
| 0d7ae5cedc | |||
| b3e2ef0f81 | |||
| 6068628d13 | |||
| 64b0aa482f | |||
| 2e5e410897 | |||
| 4045da8964 | |||
| 81099d9887 | |||
| 355faadc31 | |||
| 109f97caa0 | |||
| 9e7311fb7d | |||
| 9912fe82cc | |||
| 9f5deba0ed |
@@ -32,3 +32,4 @@
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
.codex
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ 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`:
|
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
|
```bash
|
||||||
# Compile (replace board/port as needed)
|
# Compile (replace board/port as needed)
|
||||||
arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
arduino-cli compile --fqbn arduino:avr:mega Gaugecontroller
|
||||||
@@ -58,7 +60,7 @@ The sketch controls `GAUGE_COUNT` stepper-motor gauges using a trapezoidal veloc
|
|||||||
|
|
||||||
### Key data structures
|
### Key data structures
|
||||||
|
|
||||||
- `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.
|
- `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.
|
||||||
- `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode.
|
- `Gauge` — per-gauge runtime state: position, target, velocity, accel, homing state machine, sweep mode.
|
||||||
|
|
||||||
### Motion control (`updateGauge`)
|
### Motion control (`updateGauge`)
|
||||||
@@ -76,7 +78,7 @@ When `sweepEnabled`, `updateSweepTarget` bounces `targetPos` between `minPos` an
|
|||||||
|
|
||||||
### LED strip
|
### LED strip
|
||||||
|
|
||||||
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.
|
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()`.
|
||||||
|
|
||||||
### Serial command protocol
|
### Serial command protocol
|
||||||
|
|
||||||
@@ -104,6 +106,6 @@ All commands reply `OK` or `ERR BAD_ID` / `ERR BAD_CMD` etc.
|
|||||||
### Adding gauges
|
### Adding gauges
|
||||||
|
|
||||||
1. Increment `GAUGE_COUNT`.
|
1. Increment `GAUGE_COUNT`.
|
||||||
2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including `ledCount`).
|
2. Add a `constexpr GaugePins` entry to `gaugePins[]` (including the `ledOrder` string — one char per LED, `'G'` for GRB or `'R'` for RGB).
|
||||||
3. Tune `maxPos` and `homingBackoffSteps` in the corresponding `Gauge` default or at runtime.
|
3. Tune `maxPos` and `homingBackoffSteps` in the corresponding `Gauge` default or at runtime.
|
||||||
4. `TOTAL_LEDS` and `gaugeLedOffset[]` update automatically — no manual changes needed.
|
4. `TOTAL_LEDS`, `gaugeLedOffset[]`, and `gaugeLedCount[]` update automatically — no manual changes needed.
|
||||||
|
|||||||
+249
-648
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
@@ -1,3 +1,79 @@
|
|||||||
# arduino_gauge_controller
|
# arduino_gauge_controller
|
||||||
|
|
||||||
A dedicated gauge controller for Arduinos.
|
A dedicated gauge controller for Arduinos.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
## VFD Support
|
||||||
|
|
||||||
|
The integrated gauge controller now includes a 4-digit VFD with:
|
||||||
|
|
||||||
|
- 4 alphanumeric digits
|
||||||
|
- decimal point indicator
|
||||||
|
- alarm bell indicator
|
||||||
|
|
||||||
|
On the merged Arduino firmware, the HV5812 control pins are:
|
||||||
|
|
||||||
|
- `D46` -> `DATA`
|
||||||
|
- `D47` -> `CLOCK`
|
||||||
|
- `D48` -> `STROBE`
|
||||||
|
- `D49` -> `BLANK/OE`
|
||||||
|
|
||||||
|
The standalone VFD sketch used `D51/D52/D53/D49`, but `51/52/53` conflict with the gauge stepper pins in the integrated controller.
|
||||||
|
|
||||||
|
## Arduino Serial Commands
|
||||||
|
|
||||||
|
The merged Arduino firmware accepts:
|
||||||
|
|
||||||
|
- `VFD`
|
||||||
|
clears the display and turns off decimal point and alarm bell
|
||||||
|
- `VFD 1234`
|
||||||
|
- `VFD 0123`
|
||||||
|
- `VFD DEAD`
|
||||||
|
- `VFD 8888.`
|
||||||
|
- `VFD BEEF!`
|
||||||
|
- `VFD 12AF.!`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- up to 4 characters are displayed
|
||||||
|
- valid characters are `0-9`, `A-F`, and `-`
|
||||||
|
- `.` enables the decimal point
|
||||||
|
- `!` enables the alarm bell
|
||||||
|
- shorter values are right-aligned
|
||||||
|
- leading zeroes are preserved if they are part of the input
|
||||||
|
|
||||||
|
## Home Assistant Integration
|
||||||
|
|
||||||
|
The ESPHome firmware in `gaugecontroller.yaml` exposes entities to Home Assistant via the native API:
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
The display is intentionally exposed as a text entity rather than a numeric entity so that:
|
||||||
|
|
||||||
|
- leading zeroes are preserved
|
||||||
|
- 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
|
||||||
|
|
||||||
|
### Diagnostics
|
||||||
|
- WiFi signal sensor
|
||||||
|
- Uptime sensor
|
||||||
|
- IP address and SSID text sensors
|
||||||
|
- Arduino Last Message sensor
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
# Rewire Checklist
|
||||||
|
|
||||||
|
This is a practical rebuild checklist for the current integrated setup:
|
||||||
|
|
||||||
|
- `Arduino Mega 2560`
|
||||||
|
- `ESP32` running `gauge.py`
|
||||||
|
- `HV5812P`
|
||||||
|
- 4-digit VFD with decimal point and alarm bell
|
||||||
|
- 3 gauge drivers
|
||||||
|
- WS2812B LED chain
|
||||||
|
|
||||||
|
Use this to rebuild the bench wiring from scratch.
|
||||||
|
|
||||||
|
## 1. Power Off Everything
|
||||||
|
|
||||||
|
- disconnect all power supplies
|
||||||
|
- disconnect USB power if it is currently feeding any board
|
||||||
|
- do not move wires while the VFD high-voltage supply is live
|
||||||
|
|
||||||
|
## 2. Place The Main Parts
|
||||||
|
|
||||||
|
- place the `Arduino Mega 2560`
|
||||||
|
- place the `ESP32`
|
||||||
|
- place the `HV5812P`
|
||||||
|
- place the 3 gauge driver boards
|
||||||
|
- place the WS2812B strip connection point
|
||||||
|
- place the VFD tube connection point
|
||||||
|
|
||||||
|
## 3. Establish A Common Ground First
|
||||||
|
|
||||||
|
Before anything else, create one common logic ground network.
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `Mega GND` -> ground rail
|
||||||
|
- `ESP32 GND` -> same ground rail
|
||||||
|
- `HV5812P GND` -> same ground rail
|
||||||
|
- `Gauge driver 0 logic GND` -> same ground rail
|
||||||
|
- `Gauge driver 1 logic GND` -> same ground rail
|
||||||
|
- `Gauge driver 2 logic GND` -> same ground rail
|
||||||
|
- `WS2812B GND` -> same ground rail
|
||||||
|
|
||||||
|
If your VFD high-voltage supply has a ground/reference return:
|
||||||
|
|
||||||
|
- `VFD HV supply return` -> same common ground rail
|
||||||
|
|
||||||
|
Do not continue until this common ground is in place.
|
||||||
|
|
||||||
|
## 4. Wire The Arduino Mega Power
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- regulated `5V logic supply` -> `Mega 5V`
|
||||||
|
- ground rail -> `Mega GND`
|
||||||
|
|
||||||
|
Do not use the Mega to power motors.
|
||||||
|
|
||||||
|
## 5. Wire The ESP32 Power
|
||||||
|
|
||||||
|
Power the ESP32 in the way your board expects.
|
||||||
|
|
||||||
|
Typical options:
|
||||||
|
|
||||||
|
- via board USB
|
||||||
|
- via board `5V/VIN` if your ESP32 board has its own regulator
|
||||||
|
- via regulated `3.3V` if it is a bare module that requires that
|
||||||
|
|
||||||
|
Also connect:
|
||||||
|
|
||||||
|
- `ESP32 GND` -> common ground rail
|
||||||
|
|
||||||
|
Do not feed raw `5V` into a bare `3.3V-only` ESP32 module.
|
||||||
|
|
||||||
|
## 6. Wire The ESP32 UART To The Mega
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `ESP32 GPIO17 (TX)` -> `Mega pin 19 (RX1)`
|
||||||
|
- `ESP32 GPIO16 (RX)` <- `Mega pin 18 (TX1)`
|
||||||
|
- `ESP32 GND` -> `Mega GND`
|
||||||
|
|
||||||
|
This UART link is used by `gauge.py`.
|
||||||
|
|
||||||
|
## 7. Wire The HV5812P Logic Side
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `Mega D46` -> `HV5812P DATA IN / DIN`
|
||||||
|
- `Mega D47` -> `HV5812P CLOCK / CLK`
|
||||||
|
- `Mega D48` -> `HV5812P STROBE / LATCH`
|
||||||
|
- `Mega D49` -> `HV5812P BLANKING / OE`
|
||||||
|
- `Mega 5V` -> `HV5812P VDD`
|
||||||
|
- `Mega GND` -> `HV5812P GND`
|
||||||
|
|
||||||
|
Do not connect:
|
||||||
|
|
||||||
|
- `Mega 5V` -> `HV5812P VPP`
|
||||||
|
|
||||||
|
## 8. Wire The HV5812P High-Voltage Side
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `VFD high-voltage positive supply` -> `HV5812P VPP`
|
||||||
|
- `VFD high-voltage supply return / reference` -> common ground rail
|
||||||
|
|
||||||
|
At this stage:
|
||||||
|
|
||||||
|
- `VDD` must be `5V`
|
||||||
|
- `VPP` must be your VFD high-voltage rail
|
||||||
|
|
||||||
|
## 9. Wire The HV5812P Outputs To The VFD
|
||||||
|
|
||||||
|
Connect these one by one:
|
||||||
|
|
||||||
|
- `HVOut1` -> VFD segment `A`
|
||||||
|
- `HVOut2` -> VFD segment `B`
|
||||||
|
- `HVOut3` -> VFD segment `C`
|
||||||
|
- `HVOut4` -> VFD segment `D`
|
||||||
|
- `HVOut5` -> VFD segment `E`
|
||||||
|
- `HVOut6` -> VFD segment `F`
|
||||||
|
- `HVOut7` -> VFD segment `G`
|
||||||
|
- `HVOut8` -> VFD decimal point segment
|
||||||
|
- `HVOut9` -> VFD alarm bell segment
|
||||||
|
- `HVOut10` -> VFD digit 1 grid
|
||||||
|
- `HVOut11` -> VFD digit 2 grid
|
||||||
|
- `HVOut12` -> VFD digit 3 grid
|
||||||
|
- `HVOut13` -> VFD digit 4 grid
|
||||||
|
- `HVOut14` -> VFD indicator grid between digits 2 and 3
|
||||||
|
|
||||||
|
## 10. Wire The VFD Filament
|
||||||
|
|
||||||
|
Wire the VFD filament/heater exactly as required by your tube.
|
||||||
|
|
||||||
|
This checklist cannot specify the exact filament supply because it depends on the actual tube.
|
||||||
|
|
||||||
|
Required reminder:
|
||||||
|
|
||||||
|
- do not power the filament from an Arduino GPIO
|
||||||
|
- use the correct filament supply for the tube
|
||||||
|
|
||||||
|
## 11. Wire Gauge Driver 0
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `Mega D50` -> `Gauge driver 0 DIR`
|
||||||
|
- `Mega D51` -> `Gauge driver 0 STEP`
|
||||||
|
- `Mega GND` -> `Gauge driver 0 logic GND`
|
||||||
|
|
||||||
|
Then connect the motor side of that driver to:
|
||||||
|
|
||||||
|
- its motor power supply
|
||||||
|
- its gauge motor
|
||||||
|
|
||||||
|
according to the driver board you are using.
|
||||||
|
|
||||||
|
## 12. Wire Gauge Driver 1
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `Mega D8` -> `Gauge driver 1 DIR`
|
||||||
|
- `Mega D9` -> `Gauge driver 1 STEP`
|
||||||
|
- `Mega GND` -> `Gauge driver 1 logic GND`
|
||||||
|
|
||||||
|
Then connect the motor side of that driver to:
|
||||||
|
|
||||||
|
- its motor power supply
|
||||||
|
- its gauge motor
|
||||||
|
|
||||||
|
according to the driver board you are using.
|
||||||
|
|
||||||
|
## 13. Wire Gauge Driver 2
|
||||||
|
|
||||||
|
Connect:
|
||||||
|
|
||||||
|
- `Mega D52` -> `Gauge driver 2 DIR`
|
||||||
|
- `Mega D53` -> `Gauge driver 2 STEP`
|
||||||
|
- `Mega GND` -> `Gauge driver 2 logic GND`
|
||||||
|
|
||||||
|
Then connect the motor side of that driver to:
|
||||||
|
|
||||||
|
- its motor power supply
|
||||||
|
- its gauge motor
|
||||||
|
|
||||||
|
according to the driver board you are using.
|
||||||
|
|
||||||
|
## 14. Wire The WS2812 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
|
||||||
|
|
||||||
|
If the LED chain is long or bright:
|
||||||
|
|
||||||
|
- do not power it from the Mega `5V`
|
||||||
|
- use a proper external `5V` supply
|
||||||
|
|
||||||
|
## 15. Verify The Pins That Changed For The Integrated VFD
|
||||||
|
|
||||||
|
The VFD is no longer on the old standalone pins.
|
||||||
|
|
||||||
|
Old standalone pins:
|
||||||
|
|
||||||
|
- `D51` -> DATA
|
||||||
|
- `D52` -> CLOCK
|
||||||
|
- `D53` -> STROBE
|
||||||
|
- `D49` -> BLANK
|
||||||
|
|
||||||
|
Current integrated pins:
|
||||||
|
|
||||||
|
- `D46` -> DATA
|
||||||
|
- `D47` -> CLOCK
|
||||||
|
- `D48` -> STROBE
|
||||||
|
- `D49` -> BLANK
|
||||||
|
|
||||||
|
So make sure:
|
||||||
|
|
||||||
|
- nothing VFD-related is still on `D51`
|
||||||
|
- nothing VFD-related is still on `D52`
|
||||||
|
- nothing VFD-related is still on `D53`
|
||||||
|
- only `BLANK/OE` remains on `D49`
|
||||||
|
|
||||||
|
## 16. Sanity Check Before Powering Logic
|
||||||
|
|
||||||
|
Check each item physically:
|
||||||
|
|
||||||
|
- `Mega D46` really goes to `HV5812 DATA`
|
||||||
|
- `Mega D47` really goes to `HV5812 CLOCK`
|
||||||
|
- `Mega D48` really goes to `HV5812 STROBE`
|
||||||
|
- `Mega D49` really goes to `HV5812 BLANKING`
|
||||||
|
- `Mega D50/D51` only go to gauge driver 0
|
||||||
|
- `Mega D8/D9` only go to gauge driver 1
|
||||||
|
- `Mega D52/D53` only go to gauge driver 2
|
||||||
|
- `Mega D22` only goes to WS2812B `DIN`
|
||||||
|
- `ESP32 GPIO17` goes to `Mega RX1`
|
||||||
|
- `ESP32 GPIO16` goes to `Mega TX1`
|
||||||
|
- all grounds are common
|
||||||
|
- `HV5812 VDD` is `5V`
|
||||||
|
- `HV5812 VPP` is high voltage, not `5V`
|
||||||
|
|
||||||
|
## 17. Power Logic Only First
|
||||||
|
|
||||||
|
Apply only logic power first:
|
||||||
|
|
||||||
|
- Mega power
|
||||||
|
- ESP32 power
|
||||||
|
- HV5812 `VDD`
|
||||||
|
- WS2812 `5V`
|
||||||
|
|
||||||
|
Leave motor supply and VFD high voltage off for the first check if possible.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
- Mega boots
|
||||||
|
- ESP32 boots
|
||||||
|
- UART communication works
|
||||||
|
|
||||||
|
## 18. Power The VFD High Voltage
|
||||||
|
|
||||||
|
Now apply the VFD high-voltage supply to `HV5812 VPP`.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
- `VDD` remains `5V`
|
||||||
|
- `VPP` is the expected high voltage
|
||||||
|
- no logic wire is heating
|
||||||
|
|
||||||
|
## 19. Power The Gauge Drivers
|
||||||
|
|
||||||
|
Now apply motor power to the gauge drivers.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
- no driver fault LEDs
|
||||||
|
- no motor heating or runaway movement immediately on power-up
|
||||||
|
|
||||||
|
## 20. First Functional Test
|
||||||
|
|
||||||
|
Test in this order:
|
||||||
|
|
||||||
|
1. confirm the ESP32 can talk to the Mega
|
||||||
|
2. send `VFD 8888`
|
||||||
|
3. send `VFD DEAD.!`
|
||||||
|
4. test one gauge movement from Home Assistant or MQTT
|
||||||
|
5. test one LED output
|
||||||
|
|
||||||
|
## 21. If Something Is Wrong
|
||||||
|
|
||||||
|
Use this triage order:
|
||||||
|
|
||||||
|
1. check grounds
|
||||||
|
2. check `VDD` and `VPP`
|
||||||
|
3. check Mega pin number mistakes
|
||||||
|
4. check crossed UART lines
|
||||||
|
5. check that the VFD is still on `46/47/48/49`, not `51/52/53/49`
|
||||||
|
|
||||||
|
## 22. Quick Reference
|
||||||
|
|
||||||
|
### Mega pins in use
|
||||||
|
|
||||||
|
- `D8` -> gauge 1 DIR
|
||||||
|
- `D9` -> gauge 1 STEP
|
||||||
|
- `D22` -> WS2812 DIN
|
||||||
|
- `D46` -> HV5812 DATA
|
||||||
|
- `D47` -> HV5812 CLOCK
|
||||||
|
- `D48` -> HV5812 STROBE
|
||||||
|
- `D49` -> HV5812 BLANKING
|
||||||
|
- `D50` -> gauge 0 DIR
|
||||||
|
- `D51` -> gauge 0 STEP
|
||||||
|
- `D52` -> gauge 2 DIR
|
||||||
|
- `D53` -> gauge 2 STEP
|
||||||
|
- `D18` -> UART TX1 to ESP32 RX
|
||||||
|
- `D19` -> UART RX1 from ESP32 TX
|
||||||
|
|
||||||
|
### VFD outputs
|
||||||
|
|
||||||
|
- `HVOut1..7` -> `A..G`
|
||||||
|
- `HVOut8` -> decimal point
|
||||||
|
- `HVOut9` -> alarm bell
|
||||||
|
- `HVOut10..13` -> digit grids 1..4
|
||||||
|
- `HVOut14` -> indicator grid
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
# Stripboard Layout Suggestion
|
||||||
|
|
||||||
|
This is a practical suggested layout for moving the current bench wiring onto stripboard. It is not a PCB netlist. It is a placement and routing plan intended to reduce wiring chaos while keeping the high-voltage VFD side separated from the low-voltage logic side.
|
||||||
|
|
||||||
|
Use this together with:
|
||||||
|
|
||||||
|
- [wiring.md](/home/adebaumann/development/arduino_gauge_controller/wiring.md:1)
|
||||||
|
- [Rewire_checklist.md](/home/adebaumann/development/arduino_gauge_controller/Rewire_checklist.md:1)
|
||||||
|
|
||||||
|
## Design Goals
|
||||||
|
|
||||||
|
- keep `5V logic` on one side
|
||||||
|
- keep `VFD high voltage` on the opposite side
|
||||||
|
- keep the `HV5812P` between those two domains
|
||||||
|
- bring all off-board wiring to clearly labeled edge connectors
|
||||||
|
- avoid crossing the gauge step/dir wiring through the VFD area
|
||||||
|
- make debugging possible with scope probes and a meter
|
||||||
|
|
||||||
|
## Recommended Board Strategy
|
||||||
|
|
||||||
|
Use one main stripboard for:
|
||||||
|
|
||||||
|
- Arduino Mega interface headers
|
||||||
|
- HV5812P and its support wiring
|
||||||
|
- connectors for the VFD tube
|
||||||
|
- connectors for the three gauge drivers
|
||||||
|
- connector for the WS2812 strip
|
||||||
|
- connector for the ESP32 UART link
|
||||||
|
|
||||||
|
Do not mount the Arduino Mega itself onto stripboard. Use pin headers or screw terminals so the Mega remains removable.
|
||||||
|
|
||||||
|
If possible, also do not mount the ESP32 directly unless you already have a reliable carrier board for it.
|
||||||
|
|
||||||
|
## Suggested Physical Zoning
|
||||||
|
|
||||||
|
Arrange the board in four zones from left to right:
|
||||||
|
|
||||||
|
1. `Mega / ESP32 low-voltage I/O zone`
|
||||||
|
2. `Gauge / LED connector zone`
|
||||||
|
3. `HV5812P driver zone`
|
||||||
|
4. `VFD high-voltage and tube connector zone`
|
||||||
|
|
||||||
|
That gives you a left-to-right flow like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ Mega / ESP32 ] [ Gauge + LED connectors ] [ HV5812P ] [ VFD + HV connectors ]
|
||||||
|
```
|
||||||
|
|
||||||
|
This is better than putting the HV5812 at the edge near the Mega, because the HV5812 is the boundary device between logic and high voltage.
|
||||||
|
|
||||||
|
## Board Orientation
|
||||||
|
|
||||||
|
Assume the stripboard copper tracks run horizontally.
|
||||||
|
|
||||||
|
Recommended use:
|
||||||
|
|
||||||
|
- horizontal tracks for local distribution
|
||||||
|
- vertical jumps made with insulated wire links
|
||||||
|
|
||||||
|
Cut tracks aggressively around the HV5812P so it does not accidentally join unrelated nets through the copper strips.
|
||||||
|
|
||||||
|
## Left Side: Mega / ESP32 Interface
|
||||||
|
|
||||||
|
Place a row of labeled pin headers or screw terminals for the signals coming from the Mega:
|
||||||
|
|
||||||
|
- `5V`
|
||||||
|
- `GND`
|
||||||
|
- `D22`
|
||||||
|
- `D46`
|
||||||
|
- `D47`
|
||||||
|
- `D48`
|
||||||
|
- `D49`
|
||||||
|
- `D50`
|
||||||
|
- `D51`
|
||||||
|
- `D52`
|
||||||
|
- `D53`
|
||||||
|
- `RX1`
|
||||||
|
- `TX1`
|
||||||
|
|
||||||
|
Place a second small header for the ESP32:
|
||||||
|
|
||||||
|
- `ESP32 TX`
|
||||||
|
- `ESP32 RX`
|
||||||
|
- `ESP32 GND`
|
||||||
|
|
||||||
|
Keep these headers near one board edge so you can unplug and rework them easily.
|
||||||
|
|
||||||
|
## Middle-Left: Gauge / LED Connectors
|
||||||
|
|
||||||
|
Place four connector groups near the Mega interface side:
|
||||||
|
|
||||||
|
1. `Gauge 0`
|
||||||
|
- `DIR`
|
||||||
|
- `STEP`
|
||||||
|
- `GND`
|
||||||
|
2. `Gauge 1`
|
||||||
|
- `DIR`
|
||||||
|
- `STEP`
|
||||||
|
- `GND`
|
||||||
|
3. `Gauge 2`
|
||||||
|
- `DIR`
|
||||||
|
- `STEP`
|
||||||
|
- `GND`
|
||||||
|
4. `WS2812`
|
||||||
|
- `DIN`
|
||||||
|
- `5V`
|
||||||
|
- `GND`
|
||||||
|
|
||||||
|
This keeps all low-voltage off-board connections together.
|
||||||
|
|
||||||
|
## Center: HV5812P Zone
|
||||||
|
|
||||||
|
Mount the `HV5812P` roughly in the center-right of the board.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- logic-side control pins can approach from the left
|
||||||
|
- high-voltage outputs can leave to the right toward the VFD connector
|
||||||
|
|
||||||
|
Around the HV5812P:
|
||||||
|
|
||||||
|
- isolate each used pin with track cuts as needed
|
||||||
|
- keep short local links for `DATA`, `CLOCK`, `STROBE`, `BLANKING`
|
||||||
|
- keep `VDD` decoupling physically close to the chip
|
||||||
|
|
||||||
|
Recommended support parts close to the HV5812P:
|
||||||
|
|
||||||
|
- `100 nF` ceramic decoupling capacitor between `VDD` and `GND`
|
||||||
|
- one larger bulk capacitor on the `5V` rail nearby, for example `10 uF` to `47 uF`
|
||||||
|
|
||||||
|
If you already use any datasheet-recommended support parts for the HV side, place them in this same zone.
|
||||||
|
|
||||||
|
## Right Side: VFD Connector Zone
|
||||||
|
|
||||||
|
Put the VFD connectors on the far right side of the board, physically separated from the Mega headers.
|
||||||
|
|
||||||
|
Provide terminals or headers for:
|
||||||
|
|
||||||
|
- `HVOut1` -> `A`
|
||||||
|
- `HVOut2` -> `B`
|
||||||
|
- `HVOut3` -> `C`
|
||||||
|
- `HVOut4` -> `D`
|
||||||
|
- `HVOut5` -> `E`
|
||||||
|
- `HVOut6` -> `F`
|
||||||
|
- `HVOut7` -> `G`
|
||||||
|
- `HVOut8` -> `DP`
|
||||||
|
- `HVOut9` -> `BELL`
|
||||||
|
- `HVOut10` -> `GRID1`
|
||||||
|
- `HVOut11` -> `GRID2`
|
||||||
|
- `HVOut12` -> `GRID3`
|
||||||
|
- `HVOut13` -> `GRID4`
|
||||||
|
- `HVOut14` -> `GRID_IND`
|
||||||
|
|
||||||
|
Also provide separate terminals for:
|
||||||
|
|
||||||
|
- `VPP`
|
||||||
|
- `GND`
|
||||||
|
- filament connections
|
||||||
|
|
||||||
|
Keep the filament connections away from the logic-side headers.
|
||||||
|
|
||||||
|
## Suggested Power Buses
|
||||||
|
|
||||||
|
Use distinct buses and label them clearly:
|
||||||
|
|
||||||
|
- `5V LOGIC`
|
||||||
|
- `GND`
|
||||||
|
- `VPP`
|
||||||
|
|
||||||
|
Recommended physical arrangement:
|
||||||
|
|
||||||
|
- `5V` bus along the top-left area only
|
||||||
|
- `GND` bus available across the board
|
||||||
|
- `VPP` bus only on the far-right HV area
|
||||||
|
|
||||||
|
Do not run a long exposed `VPP` strip through the entire board. Keep the high-voltage distribution short and local to the HV5812 and VFD connector side.
|
||||||
|
|
||||||
|
## Suggested Routing
|
||||||
|
|
||||||
|
### Mega to HV5812
|
||||||
|
|
||||||
|
Route these as short direct runs:
|
||||||
|
|
||||||
|
- `D46` -> `DATA`
|
||||||
|
- `D47` -> `CLOCK`
|
||||||
|
- `D48` -> `STROBE`
|
||||||
|
- `D49` -> `BLANKING`
|
||||||
|
|
||||||
|
These should pass from the left interface zone into the HV5812 zone without crossing the VPP area.
|
||||||
|
|
||||||
|
### Mega to Gauges
|
||||||
|
|
||||||
|
Route these directly to the gauge connector blocks:
|
||||||
|
|
||||||
|
- `D50` -> gauge 0 `DIR`
|
||||||
|
- `D51` -> gauge 0 `STEP`
|
||||||
|
- `D8` and `D9`
|
||||||
|
If you are bringing these through the stripboard too, add them to the Mega header group and route them directly to gauge 1.
|
||||||
|
- `D52` -> gauge 2 `DIR`
|
||||||
|
- `D53` -> gauge 2 `STEP`
|
||||||
|
|
||||||
|
If `D8` and `D9` come from separate fly wires to the stripboard, keep them in the same low-voltage connector area as the rest of the gauge lines.
|
||||||
|
|
||||||
|
### Mega to WS2812
|
||||||
|
|
||||||
|
Route:
|
||||||
|
|
||||||
|
- `D22` -> main backlight/status strip `DIN`
|
||||||
|
- `D36` -> indicator strip `DIN`
|
||||||
|
- `5V` -> both strip `5V` inputs
|
||||||
|
- `GND` -> both strip `GND` inputs
|
||||||
|
|
||||||
|
Keep the LED connector in the low-voltage area.
|
||||||
|
|
||||||
|
### ESP32 to Mega
|
||||||
|
|
||||||
|
If the stripboard is acting as the interconnect backplane:
|
||||||
|
|
||||||
|
- `ESP32 TX` -> `Mega RX1`
|
||||||
|
- `ESP32 RX` -> `Mega TX1`
|
||||||
|
- `ESP32 GND` -> `GND`
|
||||||
|
|
||||||
|
## Track Cuts and Links
|
||||||
|
|
||||||
|
Recommended stripboard discipline:
|
||||||
|
|
||||||
|
- every IC pin should be visually checked for unintended strip continuity
|
||||||
|
- cut under or near pins wherever two adjacent pins must not share the strip
|
||||||
|
- use insulated jumpers for crossings instead of relying on long exposed component leads
|
||||||
|
- use a continuity meter after every 5-10 wires added
|
||||||
|
|
||||||
|
For the HV5812P specifically:
|
||||||
|
|
||||||
|
- assume most adjacent pins must not be left on the same uninterrupted strip
|
||||||
|
- cut first, then add intentional links
|
||||||
|
|
||||||
|
## Physical Separation Rules
|
||||||
|
|
||||||
|
Keep these separations:
|
||||||
|
|
||||||
|
- logic and UART wiring away from `VPP`
|
||||||
|
- VFD output traces away from ESP32 and Mega headers
|
||||||
|
- gauge step/dir traces away from VFD high-voltage outputs where possible
|
||||||
|
|
||||||
|
If you can, leave at least one empty strip gap between low-voltage and high-voltage routing regions, and more where practical.
|
||||||
|
|
||||||
|
## Labeling
|
||||||
|
|
||||||
|
Label the board directly with marker or printed tape.
|
||||||
|
|
||||||
|
At minimum label:
|
||||||
|
|
||||||
|
- `5V`
|
||||||
|
- `GND`
|
||||||
|
- `VPP`
|
||||||
|
- `DIN`
|
||||||
|
- `CLK`
|
||||||
|
- `STR`
|
||||||
|
- `BLK`
|
||||||
|
- `G0 DIR`
|
||||||
|
- `G0 STEP`
|
||||||
|
- `G1 DIR`
|
||||||
|
- `G1 STEP`
|
||||||
|
- `G2 DIR`
|
||||||
|
- `G2 STEP`
|
||||||
|
- `LED DIN`
|
||||||
|
- `RX1`
|
||||||
|
- `TX1`
|
||||||
|
- `A B C D E F G DP BELL G1 G2 G3 G4 GI`
|
||||||
|
|
||||||
|
This matters more than aesthetics. A labeled board is much easier to repair later.
|
||||||
|
|
||||||
|
## Practical Build Order
|
||||||
|
|
||||||
|
1. Place and mark the four physical zones.
|
||||||
|
2. Mount the low-voltage connector headers.
|
||||||
|
3. Mount the HV5812P.
|
||||||
|
4. Cut all required strips around the HV5812P before adding wires.
|
||||||
|
5. Add `GND` and `5V` low-voltage distribution.
|
||||||
|
6. Add Mega-to-HV5812 logic lines.
|
||||||
|
7. Add gauge and LED connector routing.
|
||||||
|
8. Add `VPP` and the VFD output connector routing.
|
||||||
|
9. Add the VFD filament connector.
|
||||||
|
10. Verify continuity and shorts before any power is applied.
|
||||||
|
|
||||||
|
## Recommended First Continuity Checks
|
||||||
|
|
||||||
|
Before power:
|
||||||
|
|
||||||
|
- `5V` is not shorted to `GND`
|
||||||
|
- `VPP` is not shorted to `GND`
|
||||||
|
- `VPP` is not shorted to `5V`
|
||||||
|
- `D46/D47/D48/D49` are only connected to the intended HV5812 pins
|
||||||
|
- `D50/D51/D52/D53/D8/D9` are only connected to the intended gauge connectors
|
||||||
|
- `D22` only goes to the WS2812 connector
|
||||||
|
- `RX1/TX1` are not swapped at the stripboard labels
|
||||||
|
|
||||||
|
## Suggested Board Size
|
||||||
|
|
||||||
|
For comfort rather than minimum size, use a board large enough to avoid crowding:
|
||||||
|
|
||||||
|
- roughly `100 x 160 mm` or larger if you want good service access
|
||||||
|
|
||||||
|
Smaller is possible, but with mixed logic, UART, gauge control, LEDs, and VFD high voltage, cramped stripboard becomes harder to debug than a rat's nest.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
If you want the cleanest result:
|
||||||
|
|
||||||
|
- use the stripboard only as an interconnect backplane
|
||||||
|
- keep the Mega, ESP32, and possibly the gauge drivers off-board on removable connectors
|
||||||
|
- keep the HV5812 and VFD connector area on the stripboard itself
|
||||||
|
|
||||||
|
That gives you most of the neatness benefit without forcing the whole system into one dense board.
|
||||||
|
|
||||||
|
## ASCII Top View
|
||||||
|
|
||||||
|
This is a suggested top-view arrangement, not a strict scale drawing.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Top edge
|
||||||
|
|
||||||
|
+--------------------------------------------------------------------------------------------------+
|
||||||
|
| [Mega Header Block] [Gauge / LED Connectors] [HV5812P Zone] [VFD Zone] |
|
||||||
|
| |
|
||||||
|
| 5V GND D22 D46 D47 D48 D49 D50 D51 D52 D53 RX1 TX1 |
|
||||||
|
| o o o o o o o o o o o o o |
|
||||||
|
| |
|
||||||
|
| [ESP32 Header] |
|
||||||
|
| TX RX GND |
|
||||||
|
| o o o |
|
||||||
|
| |
|
||||||
|
| [Gauge 0] [Gauge 1] [Gauge 2] [WS2812] |
|
||||||
|
| DIR STEP G DIR STEP G DIR STEP G DIN 5V G |
|
||||||
|
| o o o o o o o o o o o o |
|
||||||
|
| |
|
||||||
|
| +----------------------+ |
|
||||||
|
| | HV5812P | |
|
||||||
|
| | | |
|
||||||
|
| 5V LOGIC BUS ============================================>| VDD | |
|
||||||
|
| GND BUS ============================================>| GND |==============|
|
||||||
|
| | DIN CLK STR BLK | |
|
||||||
|
| | ^ ^ ^ ^ | |
|
||||||
|
| +--|----|---|---|------+ |
|
||||||
|
| | | | | |
|
||||||
|
| | | | +---- D49 |
|
||||||
|
| | | +-------- D48 |
|
||||||
|
| | +------------ D47 |
|
||||||
|
| +----------------- D46 |
|
||||||
|
| |
|
||||||
|
| VPP terminal |
|
||||||
|
| o |
|
||||||
|
| | |
|
||||||
|
| HV AREA | |
|
||||||
|
| kept to right side only | |
|
||||||
|
| v |
|
||||||
|
| A B C D E F G DP BELL G1 G2 G3 G4 GI
|
||||||
|
| o o o o o o o o o o o o o o
|
||||||
|
| [VFD output connector block] |
|
||||||
|
| |
|
||||||
|
| FIL_A FIL_B GND/HVRET |
|
||||||
|
| o o o |
|
||||||
|
| [filament / HV return terminals] |
|
||||||
|
| |
|
||||||
|
+--------------------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
Bottom edge
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reading The Sketch
|
||||||
|
|
||||||
|
- left side:
|
||||||
|
all low-voltage headers from the Mega and ESP32
|
||||||
|
- center-left:
|
||||||
|
gauge and LED connector blocks
|
||||||
|
- center-right:
|
||||||
|
HV5812P
|
||||||
|
- far right:
|
||||||
|
VFD outputs, filament, and `VPP`
|
||||||
|
|
||||||
|
This keeps the dangerous and noisy wiring concentrated on one side of the board.
|
||||||
|
|
||||||
|
## Suggested Copper-Strip Use
|
||||||
|
|
||||||
|
If your strips run horizontally:
|
||||||
|
|
||||||
|
- use upper strips for low-voltage headers and distribution
|
||||||
|
- use middle strips for gauge and LED routing
|
||||||
|
- isolate the HV5812P pin rows heavily with track cuts
|
||||||
|
- use lower-right strips only for the VFD output area and `VPP`
|
||||||
|
|
||||||
|
## Suggested Connector Edge Placement
|
||||||
|
|
||||||
|
If you want the board to be easy to service:
|
||||||
|
|
||||||
|
- put Mega and ESP32 headers on the left edge
|
||||||
|
- put gauge and LED connectors on the bottom edge
|
||||||
|
- put VFD and high-voltage terminals on the right edge
|
||||||
|
|
||||||
|
That way:
|
||||||
|
|
||||||
|
- low-voltage control cables enter from the left and bottom
|
||||||
|
- high-voltage VFD wires leave only on the right
|
||||||
|
|
||||||
|
## Minimum Clearance Advice
|
||||||
|
|
||||||
|
On stripboard, do not pack the `VPP` and VFD output terminals tightly against the low-voltage headers.
|
||||||
|
|
||||||
|
Practical suggestion:
|
||||||
|
|
||||||
|
- leave at least several empty holes / one empty strip region between the HV5812 logic-side routing and the `VPP` / VFD connector zone
|
||||||
|
- if you have room, leave more than that
|
||||||
|
|
||||||
|
More separation is better than a dense layout here.
|
||||||
+311
-49
@@ -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
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -293,6 +303,34 @@ _status_red_effect = [None] * num_gauges
|
|||||||
_status_green_effect= [None] * num_gauges
|
_status_green_effect= [None] * num_gauges
|
||||||
_bl_effect = [None] * num_gauges
|
_bl_effect = [None] * num_gauges
|
||||||
|
|
||||||
|
vfd_text = ""
|
||||||
|
vfd_decimal_point = False
|
||||||
|
vfd_alarm = False
|
||||||
|
|
||||||
|
|
||||||
|
def _build_vfd_command():
|
||||||
|
suffix = ""
|
||||||
|
if vfd_decimal_point:
|
||||||
|
suffix += "."
|
||||||
|
if vfd_alarm:
|
||||||
|
suffix += "!"
|
||||||
|
|
||||||
|
if vfd_text:
|
||||||
|
return f"VFD {vfd_text}{suffix}"
|
||||||
|
if suffix:
|
||||||
|
return f"VFD 0{suffix}"
|
||||||
|
return "VFD"
|
||||||
|
|
||||||
|
|
||||||
|
def send_vfd_state():
|
||||||
|
arduino_send(_build_vfd_command())
|
||||||
|
|
||||||
|
|
||||||
|
def publish_vfd_state(client):
|
||||||
|
client.publish(vfd_topics["state"], vfd_text, retain=True)
|
||||||
|
client.publish(vfd_topics["decimal_point_state"], b"ON" if vfd_decimal_point else b"OFF", retain=True)
|
||||||
|
client.publish(vfd_topics["alarm_state"], b"ON" if vfd_alarm else b"OFF", retain=True)
|
||||||
|
|
||||||
|
|
||||||
def _backlight_changed(gauge_idx, new_color, new_on, new_brightness):
|
def _backlight_changed(gauge_idx, new_color, new_on, new_brightness):
|
||||||
return (
|
return (
|
||||||
@@ -356,6 +394,53 @@ def publish_backlight_states(client):
|
|||||||
log_err(f"Backlight state publish failed for gauge {i}: {e}")
|
log_err(f"Backlight state publish failed for gauge {i}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def restore_backlight_state(gauge_idx, payload):
|
||||||
|
"""Restore retained backlight state without republishing it back to MQTT."""
|
||||||
|
global backlight_color, backlight_brightness, backlight_on, _bl_effect
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = ujson.loads(payload)
|
||||||
|
except Exception as e:
|
||||||
|
warn(f"Invalid retained backlight state for gauge {gauge_idx}: '{payload}' ({e})")
|
||||||
|
return
|
||||||
|
|
||||||
|
state_on = data.get("state", "OFF").upper() != "OFF"
|
||||||
|
effect = data.get("effect")
|
||||||
|
if effect not in _EFFECTS:
|
||||||
|
effect = None
|
||||||
|
|
||||||
|
if not state_on:
|
||||||
|
_bl_effect[gauge_idx] = None
|
||||||
|
backlight_on[gauge_idx] = False
|
||||||
|
set_backlight_brightness(gauge_idx, 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
color = data.get("color", {})
|
||||||
|
r = max(0, min(255, int(color.get("r", backlight_color[gauge_idx][0]))))
|
||||||
|
g = max(0, min(255, int(color.get("g", backlight_color[gauge_idx][1]))))
|
||||||
|
b = max(0, min(255, int(color.get("b", backlight_color[gauge_idx][2]))))
|
||||||
|
raw_br = data.get("brightness", None)
|
||||||
|
if raw_br is not None:
|
||||||
|
brightness = max(0, min(100, round(int(raw_br) / 2.55)))
|
||||||
|
elif backlight_brightness[gauge_idx] > 0:
|
||||||
|
brightness = backlight_brightness[gauge_idx]
|
||||||
|
else:
|
||||||
|
brightness = 100
|
||||||
|
|
||||||
|
_bl_effect[gauge_idx] = effect
|
||||||
|
if effect:
|
||||||
|
scale = brightness / 100
|
||||||
|
rs = int(r * scale)
|
||||||
|
gs = int(g * scale)
|
||||||
|
bs_ = int(b * scale)
|
||||||
|
_send_effect(gauge_idx, _LED_BACKLIGHT_RANGE, (rs, gs, bs_), effect)
|
||||||
|
backlight_color[gauge_idx] = (r, g, b)
|
||||||
|
backlight_brightness[gauge_idx] = brightness
|
||||||
|
backlight_on[gauge_idx] = True
|
||||||
|
else:
|
||||||
|
set_backlight_color(gauge_idx, r, g, b, brightness)
|
||||||
|
|
||||||
|
|
||||||
def _flush_backlight_state():
|
def _flush_backlight_state():
|
||||||
global _bl_dirty_since
|
global _bl_dirty_since
|
||||||
if _bl_dirty_since is None:
|
if _bl_dirty_since is None:
|
||||||
@@ -419,6 +504,18 @@ def make_gauge_topics(prefix, gauge_id):
|
|||||||
|
|
||||||
gauge_topics = [make_gauge_topics(MQTT_PREFIX, g["id"]) for g in gauges]
|
gauge_topics = [make_gauge_topics(MQTT_PREFIX, g["id"]) for g in gauges]
|
||||||
|
|
||||||
|
vfd_topics = {
|
||||||
|
"set": f"{MQTT_PREFIX}/vfd/set",
|
||||||
|
"state": f"{MQTT_PREFIX}/vfd/state",
|
||||||
|
"disc": f"homeassistant/text/{MQTT_CLIENT_ID}_vfd/config",
|
||||||
|
"decimal_point": f"{MQTT_PREFIX}/vfd/decimal_point/set",
|
||||||
|
"decimal_point_state": f"{MQTT_PREFIX}/vfd/decimal_point/state",
|
||||||
|
"decimal_point_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_vfd_decimal_point/config",
|
||||||
|
"alarm": f"{MQTT_PREFIX}/vfd/alarm/set",
|
||||||
|
"alarm_state": f"{MQTT_PREFIX}/vfd/alarm/state",
|
||||||
|
"alarm_disc": f"homeassistant/switch/{MQTT_CLIENT_ID}_vfd_alarm/config",
|
||||||
|
}
|
||||||
|
|
||||||
T_SET = f"{MQTT_PREFIX}/set"
|
T_SET = f"{MQTT_PREFIX}/set"
|
||||||
T_ZERO = f"{MQTT_PREFIX}/zero"
|
T_ZERO = f"{MQTT_PREFIX}/zero"
|
||||||
|
|
||||||
@@ -437,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():
|
||||||
@@ -480,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}")
|
||||||
@@ -500,13 +613,41 @@ def check_wifi():
|
|||||||
|
|
||||||
|
|
||||||
def on_message(topic, payload):
|
def on_message(topic, payload):
|
||||||
|
global vfd_text, vfd_decimal_point, vfd_alarm
|
||||||
|
|
||||||
if client_ref is None:
|
if client_ref is None:
|
||||||
return
|
return
|
||||||
topic = topic.decode()
|
topic = topic.decode()
|
||||||
payload = payload.decode().strip()
|
payload = payload.decode().strip()
|
||||||
info(f"MQTT rx {topic} {payload}")
|
info(f"MQTT rx {topic} {payload}")
|
||||||
|
|
||||||
|
if topic == vfd_topics["set"]:
|
||||||
|
vfd_text = payload.upper()
|
||||||
|
send_vfd_state()
|
||||||
|
publish_vfd_state(client_ref)
|
||||||
|
info(f"VFD text -> {vfd_text}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if topic == vfd_topics["decimal_point"]:
|
||||||
|
vfd_decimal_point = payload.upper() == "ON"
|
||||||
|
send_vfd_state()
|
||||||
|
publish_vfd_state(client_ref)
|
||||||
|
info(f"VFD decimal point -> {'ON' if vfd_decimal_point else 'OFF'}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if topic == vfd_topics["alarm"]:
|
||||||
|
vfd_alarm = payload.upper() == "ON"
|
||||||
|
send_vfd_state()
|
||||||
|
publish_vfd_state(client_ref)
|
||||||
|
info(f"VFD alarm -> {'ON' if vfd_alarm else 'OFF'}")
|
||||||
|
return
|
||||||
|
|
||||||
for i, gt in enumerate(gauge_topics):
|
for i, gt in enumerate(gauge_topics):
|
||||||
|
if topic == gt["led_bl_state"]:
|
||||||
|
restore_backlight_state(i, payload)
|
||||||
|
info(f"Gauge {i} backlight state restored")
|
||||||
|
return
|
||||||
|
|
||||||
if topic == gt["zero"]:
|
if topic == gt["zero"]:
|
||||||
info(f"Home command received for gauge {i}")
|
info(f"Home command received for gauge {i}")
|
||||||
gauge_home(i)
|
gauge_home(i)
|
||||||
@@ -723,6 +864,9 @@ def on_message(topic, payload):
|
|||||||
def _subscribe_all(c):
|
def _subscribe_all(c):
|
||||||
c.subscribe(f"{MQTT_PREFIX}/set")
|
c.subscribe(f"{MQTT_PREFIX}/set")
|
||||||
c.subscribe(f"{MQTT_PREFIX}/zero")
|
c.subscribe(f"{MQTT_PREFIX}/zero")
|
||||||
|
c.subscribe(vfd_topics["set"])
|
||||||
|
c.subscribe(vfd_topics["decimal_point"])
|
||||||
|
c.subscribe(vfd_topics["alarm"])
|
||||||
for i in range(num_gauges):
|
for i in range(num_gauges):
|
||||||
prefix = f"{MQTT_PREFIX}/gauge{i}"
|
prefix = f"{MQTT_PREFIX}/gauge{i}"
|
||||||
c.subscribe(f"{prefix}/set")
|
c.subscribe(f"{prefix}/set")
|
||||||
@@ -732,6 +876,7 @@ def _subscribe_all(c):
|
|||||||
c.subscribe(f"{prefix}/led/red/set")
|
c.subscribe(f"{prefix}/led/red/set")
|
||||||
c.subscribe(f"{prefix}/led/green/set")
|
c.subscribe(f"{prefix}/led/green/set")
|
||||||
c.subscribe(f"{prefix}/led/backlight/set")
|
c.subscribe(f"{prefix}/led/backlight/set")
|
||||||
|
c.subscribe(f"{prefix}/led/backlight/state")
|
||||||
c.subscribe(f"{prefix}/status_led/red/set")
|
c.subscribe(f"{prefix}/status_led/red/set")
|
||||||
c.subscribe(f"{prefix}/status_led/green/set")
|
c.subscribe(f"{prefix}/status_led/green/set")
|
||||||
|
|
||||||
@@ -739,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
|
||||||
@@ -759,7 +927,20 @@ _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 _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():
|
def check_mqtt():
|
||||||
@@ -803,6 +984,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")
|
||||||
@@ -810,7 +996,8 @@ def check_mqtt():
|
|||||||
|
|
||||||
|
|
||||||
def _publish_discovery_entity(client, topic, payload, log_msg):
|
def _publish_discovery_entity(client, topic, payload, log_msg):
|
||||||
client.publish(topic, ujson.dumps(payload), retain=True)
|
gc.collect()
|
||||||
|
client.publish(topic, ujson.dumps(_compact_discovery_payload(payload)), retain=True)
|
||||||
info(log_msg)
|
info(log_msg)
|
||||||
|
|
||||||
|
|
||||||
@@ -995,6 +1182,58 @@ def _append_backlight_status_discovery(entries, dev_ref):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _append_vfd_discovery(entries, dev_ref):
|
||||||
|
entries.append(
|
||||||
|
(
|
||||||
|
vfd_topics["disc"],
|
||||||
|
{
|
||||||
|
"name": "VFD Display",
|
||||||
|
"unique_id": f"{MQTT_CLIENT_ID}_vfd",
|
||||||
|
"cmd_t": vfd_topics["set"],
|
||||||
|
"stat_t": vfd_topics["state"],
|
||||||
|
"avty_t": gauge_topics[0]["status"],
|
||||||
|
"icon": "mdi:alpha-box",
|
||||||
|
"dev": dev_ref,
|
||||||
|
},
|
||||||
|
"Discovery: VFD text",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
entries.append(
|
||||||
|
(
|
||||||
|
vfd_topics["decimal_point_disc"],
|
||||||
|
{
|
||||||
|
"name": "VFD Decimal Point",
|
||||||
|
"unique_id": f"{MQTT_CLIENT_ID}_vfd_decimal_point",
|
||||||
|
"cmd_t": vfd_topics["decimal_point"],
|
||||||
|
"stat_t": vfd_topics["decimal_point_state"],
|
||||||
|
"avty_t": gauge_topics[0]["status"],
|
||||||
|
"pl_on": "ON",
|
||||||
|
"pl_off": "OFF",
|
||||||
|
"icon": "mdi:circle-small",
|
||||||
|
"dev": dev_ref,
|
||||||
|
},
|
||||||
|
"Discovery: VFD decimal point",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
entries.append(
|
||||||
|
(
|
||||||
|
vfd_topics["alarm_disc"],
|
||||||
|
{
|
||||||
|
"name": "VFD Alarm",
|
||||||
|
"unique_id": f"{MQTT_CLIENT_ID}_vfd_alarm",
|
||||||
|
"cmd_t": vfd_topics["alarm"],
|
||||||
|
"stat_t": vfd_topics["alarm_state"],
|
||||||
|
"avty_t": gauge_topics[0]["status"],
|
||||||
|
"pl_on": "ON",
|
||||||
|
"pl_off": "OFF",
|
||||||
|
"icon": "mdi:alarm-bell",
|
||||||
|
"dev": dev_ref,
|
||||||
|
},
|
||||||
|
"Discovery: VFD alarm",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def schedule_discovery():
|
def schedule_discovery():
|
||||||
global _discovery_queue, _discovery_idx, _last_discovery_ms
|
global _discovery_queue, _discovery_idx, _last_discovery_ms
|
||||||
_dev_ref = _DEVICE
|
_dev_ref = _DEVICE
|
||||||
@@ -1005,6 +1244,7 @@ def schedule_discovery():
|
|||||||
_append_acceleration_discovery(entries, _dev_ref)
|
_append_acceleration_discovery(entries, _dev_ref)
|
||||||
_append_indicator_led_discovery(entries, _dev_ref)
|
_append_indicator_led_discovery(entries, _dev_ref)
|
||||||
_append_backlight_status_discovery(entries, _dev_ref)
|
_append_backlight_status_discovery(entries, _dev_ref)
|
||||||
|
_append_vfd_discovery(entries, _dev_ref)
|
||||||
_discovery_queue = entries
|
_discovery_queue = entries
|
||||||
_discovery_idx = 0
|
_discovery_idx = 0
|
||||||
_last_discovery_ms = 0
|
_last_discovery_ms = 0
|
||||||
@@ -1030,15 +1270,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):
|
||||||
@@ -1052,12 +1295,14 @@ def publish_state(client):
|
|||||||
client.publish(gt["state"], str(gauge_targets[i]))
|
client.publish(gt["state"], str(gauge_targets[i]))
|
||||||
client.publish(gt["speed_state"], str(gauge_speeds[i]), retain=True)
|
client.publish(gt["speed_state"], str(gauge_speeds[i]), retain=True)
|
||||||
client.publish(gt["acceleration_state"], str(gauge_accelerations[i]), retain=True)
|
client.publish(gt["acceleration_state"], str(gauge_accelerations[i]), retain=True)
|
||||||
|
publish_vfd_state(client)
|
||||||
|
|
||||||
|
|
||||||
def apply_motion_defaults():
|
def apply_motion_defaults():
|
||||||
for i in range(num_gauges):
|
for i in range(num_gauges):
|
||||||
gauge_set_speed(i, gauge_speeds[i])
|
gauge_set_speed(i, gauge_speeds[i])
|
||||||
gauge_set_acceleration(i, gauge_accelerations[i])
|
gauge_set_acceleration(i, gauge_accelerations[i])
|
||||||
|
send_vfd_state()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -1066,13 +1311,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()
|
||||||
|
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# 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%).
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,278 @@
|
|||||||
|
# Wiring
|
||||||
|
|
||||||
|
This document describes the wiring required for the current integrated system:
|
||||||
|
|
||||||
|
- `Arduino Mega 2560`
|
||||||
|
- `HV5812P` VFD driver
|
||||||
|
- 4-digit VFD tube with decimal point and alarm bell
|
||||||
|
- 3 stepper-driven gauges
|
||||||
|
- WS2812B LEDs
|
||||||
|
- ESP32 running `gauge.py` as the MQTT / Home Assistant bridge
|
||||||
|
|
||||||
|
It is intentionally based on the code that is in the repository now:
|
||||||
|
|
||||||
|
- [Gaugecontroller.ino](/home/adebaumann/development/arduino_gauge_controller/Gaugecontroller/Gaugecontroller.ino:1)
|
||||||
|
- [gauge.py](/home/adebaumann/development/arduino_gauge_controller/gauge.py:1)
|
||||||
|
|
||||||
|
## System Power
|
||||||
|
|
||||||
|
You effectively have three power domains:
|
||||||
|
|
||||||
|
1. `5V logic`
|
||||||
|
for the Arduino Mega logic, the HV5812 logic side, and usually the step/dir logic inputs
|
||||||
|
2. `high voltage for the VFD`
|
||||||
|
for `HV5812P VPP` and the VFD segment/grid drive
|
||||||
|
3. `motor / actuator power`
|
||||||
|
for the stepper gauges and their driver hardware
|
||||||
|
|
||||||
|
Minimum common rule:
|
||||||
|
|
||||||
|
- all logic grounds must be common
|
||||||
|
|
||||||
|
That means these must share `GND`:
|
||||||
|
|
||||||
|
- Arduino Mega `GND`
|
||||||
|
- ESP32 `GND`
|
||||||
|
- HV5812P logic `GND`
|
||||||
|
- stepper driver logic `GND`
|
||||||
|
- WS2812B `GND`
|
||||||
|
|
||||||
|
The VFD high-voltage supply still references the same ground, but its high-voltage nodes must never be connected directly to Arduino or ESP32 GPIO pins.
|
||||||
|
|
||||||
|
## Arduino Mega 2560
|
||||||
|
|
||||||
|
Use the Mega as the central logic controller.
|
||||||
|
|
||||||
|
Power:
|
||||||
|
|
||||||
|
- `Mega 5V` <- regulated `5V` logic supply
|
||||||
|
- `Mega GND` <- common logic ground
|
||||||
|
|
||||||
|
Serial bridge to ESP32:
|
||||||
|
|
||||||
|
- `Mega RX1` pin `19` <- `ESP32 TX` GPIO `17`
|
||||||
|
- `Mega TX1` pin `18` -> voltage divider -> `ESP32 RX` GPIO `16`
|
||||||
|
- `Mega GND` <-> `ESP32 GND`
|
||||||
|
|
||||||
|
This matches the current code:
|
||||||
|
|
||||||
|
- Arduino uses `Serial1` in [Gaugecontroller.ino](/home/adebaumann/development/arduino_gauge_controller/Gaugecontroller/Gaugecontroller.ino:12)
|
||||||
|
- ESP32 uses `UART(1, tx=17, rx=16)` in [gauge.py](/home/adebaumann/development/arduino_gauge_controller/gauge.py:149)
|
||||||
|
|
||||||
|
Because the Mega transmits `5V` logic and the ESP32 expects `3.3V` logic on RX, add a resistor divider on the `Mega TX1 -> ESP32 RX` line.
|
||||||
|
|
||||||
|
Suggested simple divider:
|
||||||
|
|
||||||
|
- `Mega TX1` -> `1 kOhm` resistor -> divider node
|
||||||
|
- divider node -> `2 kOhm` resistor -> `GND`
|
||||||
|
- divider node -> `ESP32 GPIO16 (RX)`
|
||||||
|
|
||||||
|
That scales the Mega's `5V` TX signal to roughly `3.3V` for the ESP32 RX input.
|
||||||
|
|
||||||
|
## VFD Control: Mega -> HV5812P
|
||||||
|
|
||||||
|
These are the integrated pin assignments used by the merged controller:
|
||||||
|
|
||||||
|
| Mega Pin | HV5812P Signal | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `D46` | `DATA IN / DIN` | serial data into HV5812P |
|
||||||
|
| `D47` | `CLOCK / CLK` | shift clock |
|
||||||
|
| `D48` | `STROBE / LATCH` | latch transfer |
|
||||||
|
| `D49` | `BLANKING / OE` | output blanking |
|
||||||
|
| `5V` | `VDD` | HV5812 logic supply |
|
||||||
|
| `GND` | `GND` | common reference |
|
||||||
|
| `VFD HV+` | `VPP` | HV5812 high-voltage rail |
|
||||||
|
|
||||||
|
Important:
|
||||||
|
|
||||||
|
- `VDD` is the low-voltage logic rail
|
||||||
|
- `VPP` is the high-voltage output rail
|
||||||
|
- do not connect Arduino `5V` to `VPP`
|
||||||
|
|
||||||
|
## HV5812P -> VFD Tube
|
||||||
|
|
||||||
|
The current output map is:
|
||||||
|
|
||||||
|
| HV5812 Output | Tube Function |
|
||||||
|
|---|---|
|
||||||
|
| `HVOut1` | segment `A` |
|
||||||
|
| `HVOut2` | segment `B` |
|
||||||
|
| `HVOut3` | segment `C` |
|
||||||
|
| `HVOut4` | segment `D` |
|
||||||
|
| `HVOut5` | segment `E` |
|
||||||
|
| `HVOut6` | segment `F` |
|
||||||
|
| `HVOut7` | segment `G` |
|
||||||
|
| `HVOut8` | decimal point segment |
|
||||||
|
| `HVOut9` | alarm bell segment |
|
||||||
|
| `HVOut10` | digit grid 1 |
|
||||||
|
| `HVOut11` | digit grid 2 |
|
||||||
|
| `HVOut12` | digit grid 3 |
|
||||||
|
| `HVOut13` | digit grid 4 |
|
||||||
|
| `HVOut14` | indicator grid between digits 2 and 3 |
|
||||||
|
|
||||||
|
Logical segment layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
---A---
|
||||||
|
| |
|
||||||
|
F B
|
||||||
|
|---G---|
|
||||||
|
E C
|
||||||
|
| |
|
||||||
|
---D---
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional VFD wiring notes:
|
||||||
|
|
||||||
|
- the VFD filament/heater wiring is separate from the HV5812 outputs
|
||||||
|
- the exact filament supply depends on your tube
|
||||||
|
- the HV5812 only drives the segments and grids
|
||||||
|
|
||||||
|
## Gauge Control Pins
|
||||||
|
|
||||||
|
The current sketch drives three gauges.
|
||||||
|
|
||||||
|
Each gauge needs a driver or actuator input that accepts:
|
||||||
|
|
||||||
|
- `DIR`
|
||||||
|
- `STEP`
|
||||||
|
- optionally `ENABLE` if you later add one in code
|
||||||
|
|
||||||
|
Current assignments:
|
||||||
|
|
||||||
|
| Gauge | Mega DIR | Mega STEP |
|
||||||
|
|---|---|---|
|
||||||
|
| `Gauge 0` | `D50` | `D51` |
|
||||||
|
| `Gauge 1` | `D8` | `D9` |
|
||||||
|
| `Gauge 2` | `D52` | `D53` |
|
||||||
|
|
||||||
|
Connect each pair to the matching stepper driver inputs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- `Mega D50` -> Gauge 0 driver `DIR`
|
||||||
|
- `Mega D51` -> Gauge 0 driver `STEP`
|
||||||
|
- `Mega D8` -> Gauge 1 driver `DIR`
|
||||||
|
- `Mega D9` -> Gauge 1 driver `STEP`
|
||||||
|
- `Mega D52` -> Gauge 2 driver `DIR`
|
||||||
|
- `Mega D53` -> Gauge 2 driver `STEP`
|
||||||
|
|
||||||
|
Also connect:
|
||||||
|
|
||||||
|
- `Mega GND` -> each driver logic ground
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
| Mega Pin | LED Strip |
|
||||||
|
|---|---|
|
||||||
|
| `D22` | main backlight/status `DIN` |
|
||||||
|
| `D36` | indicator `DIN` |
|
||||||
|
| `5V` | both strips `5V` |
|
||||||
|
| `GND` | both strips `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
|
||||||
|
- use a proper 5V supply sized for the LED current
|
||||||
|
- keep LED ground common with the Mega
|
||||||
|
|
||||||
|
If the strip is powered from a separate 5V supply:
|
||||||
|
|
||||||
|
- connect external `5V` -> LED `5V`
|
||||||
|
- connect external `GND` -> LED `GND`
|
||||||
|
- connect that same `GND` to `Mega GND`
|
||||||
|
|
||||||
|
## ESP32 Bridge
|
||||||
|
|
||||||
|
The ESP32 runs `gauge.py` and talks to the Mega over UART and to Home Assistant over MQTT/Wi-Fi.
|
||||||
|
|
||||||
|
ESP32 to Mega:
|
||||||
|
|
||||||
|
| ESP32 Pin | Mega Pin | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `GPIO17` | `RX1` pin `19` | ESP32 TX -> Mega RX |
|
||||||
|
| `GPIO16` | `TX1` pin `18` | ESP32 RX <- Mega TX |
|
||||||
|
| `GND` | `GND` | common ground |
|
||||||
|
|
||||||
|
ESP32 power:
|
||||||
|
|
||||||
|
- power the ESP32 from a proper `3.3V` or board-supported USB/5V input, depending on your board
|
||||||
|
- do not feed raw `5V` into a bare `3.3V` ESP32 module unless the board has its own regulator
|
||||||
|
|
||||||
|
## One-Page Wiring Summary
|
||||||
|
|
||||||
|
### Power
|
||||||
|
|
||||||
|
- `5V logic supply` -> Mega `5V`
|
||||||
|
- `5V logic supply` -> HV5812 `VDD`
|
||||||
|
- `5V logic supply` -> WS2812B `5V`
|
||||||
|
- `motor supply` -> gauge driver motor power inputs
|
||||||
|
- `VFD high-voltage supply` -> HV5812 `VPP`
|
||||||
|
- all grounds common
|
||||||
|
|
||||||
|
### Mega to ESP32
|
||||||
|
|
||||||
|
- `Mega 19 (RX1)` <- `ESP32 GPIO17 (TX)`
|
||||||
|
- `Mega 18 (TX1)` -> resistor divider -> `ESP32 GPIO16 (RX)`
|
||||||
|
- `Mega GND` <-> `ESP32 GND`
|
||||||
|
|
||||||
|
Resistor divider on `Mega TX1`:
|
||||||
|
|
||||||
|
- `Mega TX1` -> `1 kOhm` -> divider node
|
||||||
|
- divider node -> `ESP32 GPIO16`
|
||||||
|
- divider node -> `2 kOhm` -> `GND`
|
||||||
|
|
||||||
|
### Mega to HV5812
|
||||||
|
|
||||||
|
- `D46` -> `DIN`
|
||||||
|
- `D47` -> `CLK`
|
||||||
|
- `D48` -> `STROBE`
|
||||||
|
- `D49` -> `BLANKING`
|
||||||
|
- `5V` -> `VDD`
|
||||||
|
- `GND` -> `GND`
|
||||||
|
- `VFD HV+` -> `VPP`
|
||||||
|
|
||||||
|
### HV5812 to Tube
|
||||||
|
|
||||||
|
- `HVOut1..7` -> segments `A..G`
|
||||||
|
- `HVOut8` -> decimal point
|
||||||
|
- `HVOut9` -> alarm bell
|
||||||
|
- `HVOut10..13` -> digit grids `1..4`
|
||||||
|
- `HVOut14` -> indicator grid
|
||||||
|
|
||||||
|
### Mega to Gauges
|
||||||
|
|
||||||
|
- `D50/D51` -> gauge 0 `DIR/STEP`
|
||||||
|
- `D8/D9` -> gauge 1 `DIR/STEP`
|
||||||
|
- `D52/D53` -> gauge 2 `DIR/STEP`
|
||||||
|
|
||||||
|
### Mega to LEDs
|
||||||
|
|
||||||
|
- `D22` -> WS2812B `DIN`
|
||||||
|
- `5V` -> WS2812B `5V`
|
||||||
|
- `GND` -> WS2812B `GND`
|
||||||
|
|
||||||
|
## Sanity Checklist Before Power-On
|
||||||
|
|
||||||
|
- Mega, ESP32, HV5812 logic, LED strip, and driver logic grounds are all common
|
||||||
|
- Mega `D46-D49` go to the HV5812, not to the gauge drivers
|
||||||
|
- Mega `D50-D53` and `D8-D9` go only to the gauge drivers
|
||||||
|
- HV5812 `VDD` is `5V`
|
||||||
|
- HV5812 `VPP` is the VFD high-voltage rail, not `5V`
|
||||||
|
- ESP32 UART is crossed correctly: TX -> RX, RX -> TX
|
||||||
|
- WS2812B has its own adequate 5V supply if current draw is significant
|
||||||
|
- motor power is not coming from the Mega
|
||||||
|
|
||||||
|
## What This Does Not Define
|
||||||
|
|
||||||
|
This document does not define:
|
||||||
|
|
||||||
|
- the exact VFD filament supply voltage/current
|
||||||
|
- the exact motor driver board power pins, because that depends on the driver hardware you are using
|
||||||
|
- the physical PDIP package pin numbers of the HV5812P
|
||||||
Reference in New Issue
Block a user