Skip to content

Commit efd3932

Browse files
committed
Merge branch 'main' into fix/history-10min-cadence
Brings in #42 (execute_from_psram), #44 (LilyGO T-CAN485), the reset-reason diagnostics and the config-builder work. Every source file auto-merged. Two things needed hand resolution: CHANGELOG.md — both sides appended to Fixed; kept both. While here, corrected two statements main now contradicts and I had left stale: the LilyGO entry claimed the Config Builder was still closed to PSRAM-less boards, and the Removed entry claimed a test blocked them outright. Both were true when written and are not now — the builder offers the T-CAN485, and the test enforces "PSRAM and the web server imply each other" instead of a blanket ban. Also added the two entries my own commits never wrote: the reset reason, and the `ref: next` fix. docs/images/*.png — generated artifacts, so neither side was right to keep. Regenerated from the merged app.html. Verified on the merged tree: 46/46 site tests, and both board tiers compile — test-local (PSRAM, with history) and the T-CAN485 example (no PSRAM, no history), which is the interaction this merge creates, since this branch adds a history_interval option to a schema the no-history board also uses.
2 parents 361c2e4 + 295596d commit efd3932

29 files changed

Lines changed: 857 additions & 63 deletions

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- **LilyGO T-CAN485 support, for Home-Assistant-only installs.** A board with no PSRAM can now run a sensors-only build — `tigo_monitor` without `tigo_server` — and feed every value to Home Assistant over the native API. PSRAM was never a requirement of the monitor itself: the device and node tables run about 600 bytes per optimizer, so twenty of them cost ~12 KB of the ~180-200 KB an ESP32 has free once WiFi is up. It is the web server that needs PSRAM, and it stays out of these configs. `boards/esp32-lilygo-t-can485.yaml` sets up the board's built-in isolated RS485 transceiver (GPIO21/22, plus the three enable lines LilyGO's own example drives high at boot), and `boards/example-t-can485.yaml` is ready to flash. The Config Builder offers it too, restricted to exactly this shape: selecting it hides the CCA, cloud and display options, because every one of them is a web-server feature, and clears them if you had them set on another board. What you give up: the dashboard, the REST API, on-flash history, CCA/cloud import, and BLE.
1112
- **Docs screenshots generate themselves.** The guides now show the real device UI — dashboard, history, topology, node table, diagnostics — rendered during the docs build by driving the actual `app.html` against synthetic API responses. No firmware, no hardware, and nothing committed: the images are rebuilt from the current UI every time, so they cannot drift from it. All data is invented, with IPs and MACs from the IETF documentation ranges and a reserved fake-serial prefix, enforced by a test so a real serial, SSID or address can't reach a published page.
1213

1314
### Changed
@@ -17,10 +18,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1718
- **New brand mark.** The green-to-blue tile is replaced by an optimizer emitting a telemetry frame, in silicon indigo and amber. It's drawn at three sizes rather than scaled — the device favicon, the device UI sidebar, and the docs — so it stays legible in a browser tab. The docs site had no logo or favicon configured before and now carries the same mark the device serves.
1819

1920
### Removed
20-
- **Boards without PSRAM are no longer offered.** The Config Builder listed the M5Stack AtomS3/AtomS3 Lite and a generic ESP32 DevKit, both of which lack PSRAM — so it could hand you a ready-to-flash YAML for a board the project doesn't support. Both are gone from the builder, along with their `boards/*.yaml` reference configs, and a test now blocks a PSRAM-less board from being added back.
21+
- **Boards without PSRAM are no longer offered.** The Config Builder listed the M5Stack AtomS3/AtomS3 Lite and a generic ESP32 DevKit, both of which lack PSRAM — so it could hand you a ready-to-flash YAML for a board the project doesn't support. Both are gone from the builder, along with their `boards/*.yaml` reference configs. A test still guards this, but it now enforces the real rule rather than a blanket ban: PSRAM and the web server go together, so a board may be offered without PSRAM only if it is also offered without `tigo_server` — which is what makes the T-CAN485 above possible.
2122

2223
### Fixed
24+
- **The device now tells you why it last restarted.** An unexplained reboot used to leave nothing behind: the reset cause the chip records only reaches the serial console at boot, which nothing is listening to, so an unattended restart was indistinguishable from any other. Diagnostics now shows it under Uptime — "since power-on", "after a crash", "after brownout" — and it is in `/api/status` for the whole time the device stays up, so you can still ask hours later. Abnormal causes are highlighted; a routine reboot or an update is not. This is the difference between "it restarted at some point" and knowing whether to look at your firmware or your power supply: on the reference rig it identified a storm-time restart as a power loss rather than a fault, in a case that had been unexplained for days.
25+
- **Configs from the Config Builder pulled the wrong branch.** Every generated config fetched the components from `next`, a branch development moved off some time ago, so anyone following the docs got older code than the docs describe — including everything added since. It now pins `main`. Rebuild an existing config to pick this up; nothing on your device changes until you do.
2326
- **Updating the firmware could crash the device.** The history writer is meant to stand down while an update is being written, since both are writing the same flash chip — but it only checked for an update at the moment it started a snapshot, and a snapshot takes ten to twenty seconds to write. An update beginning during one was ignored until it was too late, and the two collided. Starting an update now waits for any snapshot in progress to finish before transferring, so the collision can't happen. If you update at the wrong moment you'll see a short pause and a "waited N ms" message before the transfer begins; that's the wait working. An update is refused outright if a snapshot somehow doesn't finish within 45 seconds, rather than proceeding into a known crash.
27+
- **A config without a `text_sensor:` section failed to compile.** `tigo_monitor.h` includes `sensor.h`, `text_sensor.h` and `binary_sensor.h` unconditionally, but ESPHome only copies a component's sources into the build when that component appears in the config — so any YAML that didn't happen to declare all three died on `fatal error: esphome/components/text_sensor/text_sensor.h: No such file or directory`. The documented fix was an empty `text_sensor:` stanza, which you had to already know about to write. `AUTO_LOAD` now pulls the three in whenever `tigo_monitor` is used, so hand-written configs work without the incantation. Existing configs that declare the stanzas are unaffected.
28+
- **The "Generate YAML Config" button printed a platform name that doesn't exist.** Every generated sensor block said `platform: tigo_server`; the sensor platform is `platform: tigo_monitor`, so pasting the output verbatim failed validation. Harmless on boards with the web UI, where the node table is a page you can read — but on a board without one, that button *is* the panel-discovery workflow, and its output has to paste in clean.
29+
- **PSRAM allocation no longer probes a heap that isn't there.** `psram_malloc()` tried `heap_caps_malloc(MALLOC_CAP_SPIRAM)` on every allocation and fell back to the internal heap when it failed. On a board with no PSRAM that attempt can never succeed, so it ran — and logged a "PSRAM unavailable" warning for anything over 1 KB — on every single allocation, all day. It is now compiled out when the IDF's `CONFIG_SPIRAM` is unset. No change on boards that have PSRAM.
30+
- **The flash-write crash appears to be fixed, not just made rarer.** The device now executes from PSRAM (`execute_from_psram`), and firmware configures this itself — you don't have to know it exists. Every history commit takes an ESP-IDF lock that disables the instruction cache across both cores; with code running from flash, that stall raced the WiFi/BLE radio ISRs and faulted. ESP-IDF skips the cache-disable entirely when instructions and read-only data live in PSRAM instead, which removes the race rather than shrinking its window. Previous builds survived 13.5, 42.0 and 14.0 hours before faulting; the current one has run **142 hours and counting** with no movement in any memory watermark, and with the per-panel databases already at full size — so it has been running at the worst-case flash cost the whole time, not easing into it. Costs about 1.7 MB of the 8 MB PSRAM, which moves out of the heap to hold the relocated code. Applies to ESP32-S3 boards with PSRAM configured; the reference config and the Config Builder set the flag explicitly as well.
2431

2532
- **History pages no longer fail when a snapshot is being written.** Requests gave up after 15 seconds, which is less than a snapshot takes, so any chart loaded at the wrong moment returned an error. They now wait up to 30 seconds and get their data.
2633

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ PSRAM is required for 15+ devices. Custom STL-compatible allocators (`PSRAMAlloc
6262

6363
Always use PSRAM containers for large data. Internal RAM is <200KB; PSRAM is 8MB. Use `#ifdef USE_ESP_IDF` guards for PSRAM types.
6464

65+
**Boards without PSRAM** (e.g. `boards/esp32-lilygo-t-can485.yaml`) are supported for *sensors-only* builds — `tigo_monitor` without `tigo_server`. The `psram_*` aliases still compile there; `psram_malloc()` keys off the IDF's `CONFIG_SPIRAM` and resolves straight to the internal heap when it is unset, so no call site changes. Keep it that way: gate on `CONFIG_SPIRAM` inside the allocator rather than sprinkling new `#ifdef`s at usage sites, and never assume `USE_ESP_IDF` implies PSRAM exists. The web server genuinely does require PSRAM and must stay out of those configs.
66+
6567
### Web Server
6668

6769
5 HTML pages (`/`, `/nodes`, `/status`, `/yaml`, `/cca`) + JSON API endpoints (`/api/devices`, `/api/overview`, `/api/strings`, `/api/status`, `/api/health`, `/api/inverters`, `/api/energy-history`). Auth: `api_token` for API, HTTP Basic for HTML pages. All responses built in PSRAM via `PSRAMString`.
@@ -77,7 +79,7 @@ Fuzzy barcode matching (`match_barcode()`): compares last 6 chars of UART-discov
7779
- **JSON field naming**: `snake_case` in JSON, `kebab-case` in HTML IDs, `camelCase` in JavaScript
7880
- **When renaming methods** in `tigo_monitor.h`, update all call sites: header → member variable → web server → Python config → JavaScript
7981
- **When changing any `/api/*` JSON shape** (rename a field, restructure a response), update `site/screenshots/fixtures.mjs` in the same commit. The docs screenshots are rendered from the real `app.html` against those fixtures at build time — a stale fixture doesn't fail the build, it publishes screenshots of a UI rendering `undefined`. Check with `cd site && npm run screenshots` — which also refreshes the committed README images in `docs/images/`, so commit those too
80-
- **Sensor/text_sensor/binary_sensor sections** must be declared in YAML (even if empty) or compilation fails with missing header errors
82+
- **Sensor/text_sensor/binary_sensor** are pulled in by `AUTO_LOAD` in `tigo_monitor/__init__.py`, because `tigo_monitor.h` includes all three unconditionally. YAML no longer needs to declare empty stanzas for them. If you add an unconditional `#include` of another ESPHome component to that header, add it to `AUTO_LOAD` too — otherwise configs that don't happen to use it fail with "No such file or directory". Prefer a `USE_*` guard (as `time`/`button` already do) when the dependency is genuinely optional
8183
- `CONFIG_UART_ISR_IN_IRAM: "y"` in sdkconfig is required for reduced frame loss
8284

8385
## Git Workflow

boards/README.md

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
This directory contains optimized ESPHome configurations for different ESP32 boards used with the Tigo Monitor system.
44

5-
**PSRAM is required.** Only boards that have it are configured here.
5+
**PSRAM is required for the web server (`tigo_server`)**, which builds whole HTML
6+
pages and JSON API responses in memory. It is *not* required by `tigo_monitor`
7+
the device and node tables are a few KB and sit happily on the internal heap. A
8+
board without PSRAM can therefore run a sensors-only build that feeds Home
9+
Assistant over the native API; it just cannot serve the dashboard.
610

711
## Available Configurations
812

@@ -26,6 +30,42 @@ This directory contains optimized ESPHome configurations for different ESP32 boa
2630
- **UART Buffers**: 16384 RX / 1024 TX (listen-only, no transmission)
2731
- **Special optimizations**: High-frequency FreeRTOS tick, tickless idle disabled
2832

33+
### ESP32 Boards (no PSRAM — sensors only)
34+
35+
#### `esp32-lilygo-t-can485.yaml` - LilyGO T-CAN485 (no PSRAM)
36+
- **Board**: LilyGO T-CAN485 (`esp32dev`), 4MB flash
37+
- **PSRAM**: none — the board wires GPIO16/17 to its RS485 front end, which is
38+
where a WROVER's PSRAM would live, so it cannot have any
39+
- **CPU**: 240MHz (classic ESP32 defaults to 160MHz under ESP-IDF; raised here)
40+
- **RS485**: built-in isolated transceiver on GPIO21 (RX) / GPIO22 (TX), with
41+
three enable lines (GPIO16 5V boost, GPIO17 `/RE`, GPIO19 `/SHDN`) that the
42+
board file drives high at boot
43+
- **Recommended for**: Home-Assistant-only installations. Tested at 18 devices;
44+
40 should fit — each optimizer costs roughly 600 bytes across the device and
45+
node tables, so 40 is ~24KB of the ~130KB left free once WiFi is up. Those are
46+
many small heap allocations rather than one block, so on a board with no PSRAM
47+
the practical ceiling is fragmentation, not total bytes
48+
- **First flash must be over USB**: the board file enables `sram1_as_iram`, which
49+
needs an ESP-IDF v5.1+ bootloader. A USB flash updates the bootloader; an OTA
50+
does not, so OTA-ing an existing device onto this config leaves it unable to
51+
boot
52+
- **Excludes**: `tigo_server` (needs PSRAM), on-flash history (the smallest tsdb
53+
layout wants ~7MB — dual OTA slots plus a 3MB LittleFS partition — which 4MB
54+
cannot hold; Home Assistant keeps long-term history anyway), CCA/cloud import, BLE
55+
- **Ready-to-flash example**: `example-t-can485.yaml`
56+
- **⚠ Flash over USB the first time.** The config sets `sram1_as_iram` (+40KB of
57+
IRAM, which is what pays for `CONFIG_UART_ISR_IN_IRAM`), and that needs an
58+
ESP-IDF v5.1+ bootloader. A USB flash updates the bootloader automatically; an
59+
OTA does not, so an OTA-first device will fail to boot.
60+
- **⚠ Assumes chip revision 3.1.** `minimum_chip_revision: '3.1'` drops the IDF's
61+
workarounds for older silicon. The bootloader hard-checks it — an older chip
62+
halts at boot. Your boot log prints `chip revision: v3.1`; lower the value in
63+
the board file if yours reports less.
64+
65+
Because there is no web UI on this board, panel discovery happens through the
66+
**"Generate YAML Config"** button, which prints a paste-ready `sensor:` block to
67+
the logs. `example-t-can485.yaml` documents that workflow.
68+
2969
## Usage
3070

3171
To use a board configuration, include it in your main YAML file:
@@ -47,18 +87,20 @@ Or copy the relevant sections directly into your main configuration file.
4787

4888
## GPIO Pin Compatibility
4989

50-
All configurations assume the standard Tigo UART connection:
90+
Most configurations assume the standard Tigo UART connection:
5191
- **TX Pin**: GPIO1
5292
- **RX Pin**: GPIO3
5393
- **Baud Rate**: 38400
5494

55-
Adjust these in your main configuration if your hardware differs.
95+
Adjust these in your main configuration if your hardware differs. Boards with a
96+
built-in transceiver already set their own pins — the AtomS3R uses GPIO1/GPIO2,
97+
and the T-CAN485 uses GPIO22/GPIO21 plus three transceiver enable lines.
5698

5799
## Performance Notes
58100

59101
- **ESP32-P4** offers the best performance with massive PSRAM and higher CPU frequency
60102
- **ESP32-S3 with PSRAM** is the sweet spot for most installations
61-
- Boards without PSRAM are not supported — the web server and device tables have nowhere to live and the firmware goes unstable
103+
- **Boards without PSRAM** work for sensors-only builds that talk to Home Assistant over the native API. Do not add `tigo_server:` to one — it compiles, then fragments the internal heap to OOM under dashboard polling
62104

63105
## Customization
64106

0 commit comments

Comments
 (0)