Skip to content

Commit 203f708

Browse files
authored
fix(server): build on chips with no die-temperature sensor (#56)
tigo_server included driver/temperature_sensor.h unconditionally for the Diagnostics die-temp readout. The classic ESP32 has no such peripheral, so IDF compiles esp_driver_tsens out for it: soc/clk_tree_defs.h never defines TEMPERATURE_SENSOR_CLK_SRC_DEFAULT, and the header's own TEMPERATURE_SENSOR_CONFIG_DEFAULT() macro then fails to expand. Reported as an IDF v5 regression (#55), but it is a chip capability gap — the same IDF 5.5.5 builds this fine on the S3 every day. SOC_TEMP_SENSOR_SUPPORTED is defined in soc/esp32s3/soc_caps.h and absent from soc/esp32/soc_caps.h. The peripheral is now compiled out where the SoC lacks it. That unblocks a real class of board: WROVER modules are classic ESP32s that *do* carry the PSRAM this component needs, and until now they could not build it at all. /api/status already reported internal_temp as null when unavailable (#28), so no API or UI shape changes. A user-wired internal_temperature_id keeps working on any chip — it is a plain sensor::Sensor and never touches this driver. Behind that compile error sat a second wall the reporter would have hit next. tigo_server assembles whole HTML pages and JSON responses in memory; with no `psram:` block ESPHome never sets CONFIG_SPIRAM, psram_malloc() resolves to the internal heap, and ~130KB fragments to OOM under dashboard polling — a crash hours later, not a build failure. That is now a validation error naming the sensors-only path, which needs no PSRAM. Verified: classic ESP32 + quad PSRAM + tigo_server compiles clean (64.4% flash) — #55's exact failure, reproduced and fixed. S3 unchanged and still links temperature_sensor_install (nm: present on S3, absent on ESP32). A no-PSRAM config now fails at `esphome config` with the explanation. Docs build with valid links.
1 parent ab8be18 commit 203f708

5 files changed

Lines changed: 81 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- **The config builder can generate wired configs.** A board that declares an on-board Ethernet PHY now emits an `ethernet:` block and no `wifi:`/`captive_portal:` at all, and the Wi-Fi fields disappear from the form. Bluetooth is compiled out on this board to buy back flash, so CCA-over-BLE is unavailable there; HTTP CCA import is unaffected.
1515

1616
### Fixed
17+
- **`tigo_server` builds again on the classic ESP32.** It included `driver/temperature_sensor.h` unconditionally for the Diagnostics die-temperature readout, but the classic ESP32 has no such peripheral, so IDF compiles that driver out and the header's `TEMPERATURE_SENSOR_CONFIG_DEFAULT()` macro fails to expand — `'TEMPERATURE_SENSOR_CLK_SRC_DEFAULT' was not declared in this scope` ([#55](https://github.com/RAR/esphome-tigomonitor/issues/55)). The peripheral is now compiled out on chips that lack it, which unblocks the WROVER modules — classic ESP32s that do have the PSRAM this component needs. `/api/status` already reported the field as null when unavailable, so nothing else changes. A user-wired `internal_temperature_id` still works on any chip; it is a plain sensor and never touches this driver.
18+
- **`tigo_server:` without a `psram:` block is now a config error instead of a device that dies later.** The web server assembles whole HTML pages and JSON responses in memory. With no `psram:` block ESPHome never sets `CONFIG_SPIRAM`, so those allocations fall back to the ~130KB internal heap and fragment it to OOM under dashboard polling — a crash hours in, not a build failure. Validation now says so up front and points at the sensors-only path, which needs no PSRAM.
1719
- **A per-panel sensor entry that lists no measurements is now a config error instead of a silent no-op.** `address` and `name` say which panel and what to call it; the entities come from the sub-keys (`power: {}`, `voltage_in: {}`, ...). An entry with none produced nothing at all, with no warning — which reads as "my panels never appeared in Home Assistant" and sends people looking at heap limits and device counts ([#48](https://github.com/RAR/esphome-tigomonitor/issues/48)). Validation now names the offending entry and lists the available sub-keys.
1820

1921
## [2.0.0] - 2026-08-14

components/tigo_server/__init__.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import esphome.codegen as cg
22
import esphome.config_validation as cv
3+
import esphome.final_validate as fv
34
from esphome.components import tigo_monitor, light, sensor, ble_client, esp32_ble_tracker
45
from esphome.const import CONF_ID, CONF_PORT
56
from pathlib import Path
@@ -43,7 +44,39 @@ def _validate_cca_source(config):
4344
return config
4445

4546

47+
def _require_psram(config):
48+
"""Refuse to build the web server on a board with no PSRAM.
49+
50+
tigo_server builds whole HTML pages and JSON API responses in memory from
51+
the httpd task; PSRAMString and the psram_* containers assume 8MB of
52+
external RAM. Without a `psram:` block ESPHome never sets CONFIG_SPIRAM, so
53+
psram_malloc() resolves to the internal heap — the build succeeds, then
54+
fragments ~130KB of internal RAM to OOM under dashboard polling. That is a
55+
crash hours later on someone's roof, not a compile error, so catch it here.
56+
57+
tigo_monitor itself is unaffected and needs no PSRAM: its device and node
58+
tables are a few KB. A board without PSRAM runs a sensors-only build that
59+
feeds Home Assistant over the native API.
60+
"""
61+
if "psram" not in fv.full_config.get():
62+
raise cv.Invalid(
63+
"tigo_server requires PSRAM, and this configuration has no `psram:` "
64+
"block.\n\n"
65+
"It builds whole HTML pages and JSON responses in memory, so on a "
66+
"board with only internal RAM it compiles and then runs out of heap "
67+
"under load. Either:\n"
68+
" * add a `psram:` block, if your board actually has PSRAM, or\n"
69+
" * remove `tigo_server:` and run sensors-only to Home Assistant "
70+
"over the native API. tigo_monitor needs no PSRAM.\n\n"
71+
"boards/esp32-lilygo-t-can485.yaml is a worked sensors-only example; "
72+
"panel discovery there uses the \"Generate YAML Config\" button "
73+
"instead of the web UI."
74+
)
75+
return config
76+
77+
4678
def _final_validate(config):
79+
_require_psram(config)
4780
# ESPHome disables mbedtls SHA-384/512 on IDF >= 6.0 to save flash. Both of our
4881
# crypto paths need them back:
4982
# * cloud_import: everything above the leaf in Tigo's cert chain is SHA-384-signed

components/tigo_server/tigo_web_server.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include <cmath>
2929
#include <sys/time.h>
3030
#include <mbedtls/base64.h>
31-
#include <driver/temperature_sensor.h>
3231
#include "cJSON.h"
3332

3433
namespace esphome {
@@ -594,6 +593,11 @@ void TigoWebServer::setup() {
594593
if (external_temp_sensor_ != nullptr) {
595594
ESP_LOGI(TAG, "Using configured internal_temperature sensor for die temp");
596595
} else {
596+
#ifndef TIGO_HAS_DIE_TEMP
597+
// No die-temperature peripheral on this chip (classic ESP32). Diagnostics
598+
// reports the field as null; everything else is unaffected.
599+
ESP_LOGI(TAG, "No die temperature sensor on this chip - Diagnostics will omit it");
600+
#else
597601
// The requested range must be fully contained in ONE hardware range — the driver's
598602
// temperature_sensor_choose_best_range() looks for a single table entry with
599603
// range_min <= requested_min && range_max >= requested_max, and fails the whole
@@ -625,6 +629,7 @@ void TigoWebServer::setup() {
625629
"internal_temperature platform, wire it via internal_temperature_id", esp_err_to_name(err));
626630
temp_sensor_handle_ = nullptr;
627631
}
632+
#endif // TIGO_HAS_DIE_TEMP
628633
} // end else (no external sensor wired)
629634

630635
ESP_LOGI(TAG, "All routes registered");
@@ -2799,9 +2804,11 @@ void TigoWebServer::build_esp_status_json(PSRAMString& json) {
27992804
internal_temp = external_temp_sensor_->state;
28002805
internal_temp_ok = true;
28012806
}
2807+
#ifdef TIGO_HAS_DIE_TEMP
28022808
} else if (temp_sensor_handle_ != nullptr &&
28032809
temperature_sensor_get_celsius(temp_sensor_handle_, &internal_temp) == ESP_OK) {
28042810
internal_temp_ok = true;
2811+
#endif
28052812
}
28062813

28072814
// Get network stats
@@ -3791,12 +3798,14 @@ void TigoWebServer::loop() {
37913798
}
37923799

37933800
TigoWebServer::~TigoWebServer() {
3801+
#ifdef TIGO_HAS_DIE_TEMP
37943802
// Clean up temperature sensor if it was initialized
37953803
if (temp_sensor_handle_ != nullptr) {
37963804
temperature_sensor_disable(temp_sensor_handle_);
37973805
temperature_sensor_uninstall(temp_sensor_handle_);
37983806
temp_sensor_handle_ = nullptr;
37993807
}
3808+
#endif
38003809
}
38013810

38023811
} // namespace tigo_server

components/tigo_server/tigo_web_server.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,25 @@
2121
#include <freertos/FreeRTOS.h>
2222
#include <freertos/semphr.h>
2323
#include <esp_heap_caps.h>
24+
#include <soc/soc_caps.h>
25+
26+
// Not every ESP32 has a die-temperature peripheral. The classic ESP32 has none,
27+
// so IDF compiles esp_driver_tsens out for it — soc/clk_tree_defs.h never
28+
// defines TEMPERATURE_SENSOR_CLK_SRC_*, and the header's
29+
// TEMPERATURE_SENSOR_CONFIG_DEFAULT() macro fails to expand ("'TEMPERATURE_
30+
// SENSOR_CLK_SRC_DEFAULT' was not declared in this scope", #55). Including the
31+
// header unconditionally therefore broke the build on every classic ESP32 —
32+
// including the WROVER modules that do have the PSRAM this component needs.
33+
//
34+
// The die temperature is one optional field on the Diagnostics page, and
35+
// /api/status already reports it as null when unavailable (#28), so the whole
36+
// peripheral is compiled out rather than faked. A user-wired
37+
// internal_temperature_id still works on any chip: it is a plain
38+
// sensor::Sensor and never touches this driver.
39+
#if defined(SOC_TEMP_SENSOR_SUPPORTED) && SOC_TEMP_SENSOR_SUPPORTED
40+
#define TIGO_HAS_DIE_TEMP 1
2441
#include <driver/temperature_sensor.h>
42+
#endif
2543
#include <vector>
2644
#include <string>
2745
#include <set>
@@ -127,7 +145,9 @@ class TigoWebServer : public Component
127145
std::string api_token_{""};
128146
std::string web_username_{""};
129147
std::string web_password_{""};
148+
#ifdef TIGO_HAS_DIE_TEMP
130149
temperature_sensor_handle_t temp_sensor_handle_{nullptr};
150+
#endif
131151
sensor::Sensor *external_temp_sensor_{nullptr}; // optional, wins over our own handle
132152
CcaSource cca_source_{CcaSource::HTTP};
133153

site/src/content/docs/guides/troubleshooting.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,24 @@ uart:
180180

181181
### Memory Limits
182182

183-
PSRAM is required.
183+
PSRAM is required — but only by `tigo_server`, not by `tigo_monitor`.
184+
185+
A config with `tigo_server:` and no `psram:` block is now rejected at validation
186+
time rather than compiling into a device that runs out of heap hours later. The
187+
web server builds whole HTML pages and JSON responses in memory; without
188+
`psram:` ESPHome never sets `CONFIG_SPIRAM`, so those allocations land on the
189+
~130KB internal heap and fragment it to OOM under dashboard polling.
190+
191+
If your board has no PSRAM, drop `tigo_server:` and run sensors-only to Home
192+
Assistant over the native API — `boards/esp32-lilygo-t-can485.yaml` is a worked
193+
example. Panel discovery there uses the **Generate YAML Config** button instead
194+
of the web UI.
184195

185196
### ESP32 Internal Temperature Reads Nothing
186197

187198
`/api/status` returns `"internal_temp": null` and the Diagnostics view shows no die temperature.
188199

189-
The ESP32 has a single temperature peripheral, and it installs exactly once. Two things break it:
200+
Most ESP32s have a single temperature peripheral, and it installs exactly once. Three things break it:
190201

191202
1. **Range must fit one hardware range.** `temperature_sensor_install()` needs the requested range to sit inside a *single* entry of the chip's range table — on the ESP32-S3 that is `{50..125, 20..100, -10..80, -30..50, -40..20}`. A request spanning two entries (e.g. `-10..110`) matches none, fails with `ESP_ERR_INVALID_ARG` ("Out of testing range"), and the sensor never installs. Fixed in 2.0.0-beta.4, which requests `-10..80`. The configured range is only a starting hint — the driver follows the hardware onto another range at read time, so a hot die still reads correctly.
192203
2. **Another component owns the peripheral.** If you also run ESPHome's `internal_temperature` platform, its install and ours race and the loser reads nothing. Wire the existing sensor into `tigo_server` instead of letting both try:
@@ -201,7 +212,9 @@ tigo_server:
201212
internal_temperature_id: die_temp
202213
```
203214

204-
Look for `Failed to install temperature sensor: <err>` in the boot log to tell the two apart.
215+
3. **The chip has no such peripheral at all.** The classic ESP32 (including the WROVER modules with PSRAM) has none — only the S2/S3/C-series and P4 do. There is nothing to configure here; the field is simply absent. The boot log says `No die temperature sensor on this chip - Diagnostics will omit it`.
216+
217+
Look for `Failed to install temperature sensor: <err>` in the boot log to tell the first two apart.
205218

206219
### PSRAM Not Detected After Enabling
207220

0 commit comments

Comments
 (0)