On boards with PSRAM the MQTT client runs in its own task with a 5120
byte stack. Verifying an EC certificate chain against a CA, such as
Let's Encrypt ISRG Root X2, needs about 6 KB, so the task overflowed
and the board boot looped on every connect.
The espMqttClient constructors now take the stack size, and
startClient() passes EMSESP_MQTT_STACKSIZE_CA (8192) when TLS is on
with a CA, keeping the 5120 default for insecure TLS and plain MQTT.
The client is recreated when that changes. The stack usage log
reports against the size the task was created with.
uploadComplete() leaves _is_firmware / _is_filesystem set after a
successful upload. When the client then closes the connection,
handleDisconnect() sees the flags, treats it as an aborted upload and
sets the status back to NORMAL, cancelling the pending restart. The WebUI
gets away with it because its status poll turns the pending restart into
a requested one first; an upload from curl or a script is accepted
(md5_ok) but the device keeps running the old firmware until something
else restarts it.
Clear the flags once the upload has completed.
A restart requested from the WebUI (Restart button, firmware upload,
firmware update from URL, settings or customization upload) is set to
PENDING_RESTART and was only escalated to RESTART_REQUESTED when the
WebUI's SystemMonitor polled /rest/systemStatus. If the tab was closed,
nothing ever escalated it and the generic 5 minute status timeout reset
it back to NORMAL, so the restart was dropped and an uploaded firmware
was never booted.
- System::loop() now escalates a pending restart itself if it hasn't been
picked up within 10 seconds (the SystemMonitor polls every second)
- the generic status reset timeout skips PENDING_RESTART, and its timer is
cleared whenever the status is back to normal, so a start time left over
from an earlier status can't reset a later one (e.g. a pending restart
right after a firmware download) immediately
The DHW thermal disinfection duration (time allowed to reach the
disinfection temperature; on timeout HPC4x0 reports A01(5284)) is
sent in UBAParameterWWPlus 0xEA offset 15 in steps of 10 min, but was
not decoded. Add boiler/dhw/disinfectduration for heat pumps,
readable and writeable, range 120-240 min as on the HPC410 menu.
Tested on a Bosch Compress 3400AW with HPC410: changing the duration
on the controller writes 0xEA offset 15 (150 min -> 0F, 180 -> 12),
and writing 0B 08 EA 0F 11 from EMS-ESP made the HPC410 menu show
170 min without errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tested with a heatpump (0x08) and MP100 pool module (0x11), custom entities
compared against RC300 operation and a power meter on the circulation pump.
- boiler `poolon` (0x48A offset 0), writable, RC300 writes 01/00
- boiler `hppoolon` (0x488 offset 0 bit 6), pool heating actually active
- pool `poolshuntset` (0x5C4 offset 0, heatpump -> MP100), shunt setpoint;
`poolshunt` follows within ~2 min
- pool `poolpump` (0x5BA offset 4), 0 = running, 2 = off. This byte is only
in MP100 -> heatpump telegrams, so the pool module now also handles
telegrams it sends to other devices
- simulator: `test pool`
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YrkDbvxEv3ZMS7SPSKCGd
The heat limit registers on heat pumps using EMS_DEVICE_FLAG_HEATPUMP
(product 172/173) are bytes 2/3/4 of telegram 0x0492, read as a direct
index into enum_maxHeat. That table has only six entries, so any unit
whose auxiliary electric heater goes above 9 kW reports an index the
table cannot resolve.
Nothing bounds-checks the raw byte on the way in, and every render path
guards on `value < options_size`, so an out-of-range index is dropped
silently: no value is published to MQTT, the API omits the field, Home
Assistant shows `unknown`, and nothing appears in the log. Helpers::
value2enum stops at the same boundary, so the value cannot be written
back from EMS-ESP either, leaving the entity permanently stuck.
Measured on a Bosch Compress 7000i AW (CS7000iAW 13 OR-T, product 172)
with a 15 kW auxiliary heater, by setting each limit from the unit's own
display and reading telegram 0x0492 back:
display 12 / 9 / 6 kW -> 0x0492 data: 03 00 06 05 04
raw kW note
0 0 agrees with the existing table
1 2 not offered by this heater
2 3 not offered by this heater
3 4 not offered by this heater
4 6 agrees with the existing table
5 9 agrees with the existing table
6 12 new
7 15 new
Indices 0, 4 and 5 match what EMS-ESP already decodes, which confirms the
existing entries and their ordering; the table was correct, just short.
The 2 kW and 4 kW entries belong to the 4-stufig (2/4/6/9 kW) heater
variant described in the HPC 410 service manual section 4.2.2, so a
3 kW-stepped heater simply skips them and walks 0, 3, 6, 9, 12, 15 kW,
i.e. raw 0, 2, 4, 5, 6, 7. The same sparse pattern is already encoded for
CS6800 devices in Boiler::process_HpHeaterConfig, which maps raw
{0, 2, 4, 5} onto {0, 3, 6, 9} kW.
Only devices carrying EMS_DEVICE_FLAG_HEATPUMP are affected; CS6800
devices use enum_maxHeat1/enum_maxHeat2 and are unchanged. Verified with
the standalone build: `test entity_dump` differs from the committed
docs/dump_entities.csv in exactly the six maxheat rows of products 172
and 173, and nowhere else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>