Skip to content

Releases: marklynch/pool-controller-code

v1.13.0 - Power, Energy and consistent Entity Id's

Choose a tag to compare

@github-actions github-actions released this 02 Aug 10:28
Immutable release. Only release title and notes can be modified.

[1.13.0] - 2026-08-02

Added

  • Added configurable power estimates and on-device energy accumulation, exposed to Home Assistant as editable power numbers and read-only energy sensors for the Energy dashboard's Individual devices view — per channel, plus a system baseline covering the idle draw that belongs to no channel (the controller itself, chlorinator standby, and so on) which accumulates continuously rather than being gated on a channel's active state; the power and energy sensors are only created once a channel or the baseline has watts configured.
  • Added a "Reset entities" button — on the firmware update page beside Reboot, and as a Home Assistant button — that clears every retained discovery config belonging to this device and restarts, so Home Assistant deletes the entities and re-creates them from the configs published on the next connect. Entity IDs are only assigned when an entity is first registered, so this is what applies the naming change below to an existing install; it also clears entities left behind by earlier firmware, which HA otherwise keeps resurrecting from the broker's retained configs

Changed

  • Breaking: Home Assistant entity IDs are now consistent, all of the form <domain>.pool_control_<mac>_<role> (light.pool_control_fe01cc_light_1, button.pool_control_fe01cc_channel_5_toggle, number.pool_control_fe01cc_channel_5_configured_power). Previously each entity's ID depended on when Home Assistant first saw it, so an install accumulated several unrelated shapes: some carried the device name twice (button.pool_pool_controller_fe01cc_reboot), some were derived from a channel or zone name that can change (button.pool_pool_controller_fe01cc_jets), and numbering was spelt inconsistently between entity types (ch5, light1, heater_0). Unique IDs are unchanged, so nothing is orphaned; existing entities keep their old IDs until the new "Reset entities" button is used, at which point dashboards, automations and history referencing the old IDs need updating
  • Renamed the device-management entities in Home Assistant's config section so they group together: "System: Check Updates", "System: Update Firmware", "System: Reboot" and "System: Reset HA Entities"

Fixed

  • Fixed the pH and pH Setpoint sensors losing their ph device class in Home Assistant, so they showed as plain numbers: the device class was dropped in favour of unit_of_measurement: "pH", but HA's ph device class accepts no unit (DEVICE_CLASS_UNITS maps it to {None}) — pH is dimensionless, so a blank unit is the correct display, not a missing one. Both sensors now carry device_class: ph and no unit
  • Fixed Home Assistant ignoring the entity ID the firmware asks for: discovery published it as object_id, which HA 2026.4 replaced with default_entity_id, so on current HA the request was silently dropped and entity IDs were generated from the area, device and entity names instead. Both keys are now published, naming the same entity ID on either HA version
  • Fixed Home Assistant showing Pump Speed and Pump Power entities stuck at 0 on installs with no telemetry-reporting pump: the Filter-channel-inactive workaround added in 1.12.0 zeroes the pump's readings because a variable-speed pump loses power with the channel and cannot broadcast its own 0, but it marked both readings valid unconditionally — so on a plain single-speed pump, which never sends CMD 0x3B at all, the first inactive Filter broadcast invented readings from nothing and published the entities. The zeroing is now gated on a genuine pump telemetry message having been decoded (pump_telemetry_seen). Devices that already published the entities keep the retained discovery, so those need clearing in Home Assistant once

What's Changed

Full Changelog: v1.12.0...v1.13.0

v1.12.0 - Pump power and Web based firmware flashing

Choose a tag to compare

@github-actions github-actions released this 02 Aug 00:46
Immutable release. Only release title and notes can be modified.

Huge thanks to @lawther for the work on multi-speed pumps and power consumption

[1.12.0] - 2026-08-02

Added

Changed

  • Refactored channel types to use a strongly typed channel_type_t enum across decoder and state (fixes #84).

Fixed

  • Set pump speed to 0 when the Filter channel is not active (fixes #82).

What's Changed

  • Add ESP Web Tools browser installer published to GitHub Pages by @marklynch in #75
  • Fix web installer: move source out of docs/, add deploy triggers and preview by @marklynch in #76
  • Fix web installer deploy and offer the last 5 releases by @marklynch in #77
  • Vendor ESP Web Tools into the installer instead of loading it from a CDN by @marklynch in #78
  • refactor: create channel_type_t enum by @lawther in #86
  • fix: set pump speed to zero when filter channel is inactive by @lawther in #83
  • Feature: decode Cmd 0x3B v2 for pump power by @lawther in #85

Full Changelog: v1.11.1...v1.12.0

v1.11.1

Choose a tag to compare

@github-actions github-actions released this 25 Jul 23:59
Immutable release. Only release title and notes can be modified.

Full Changelog: v1.11.0...v1.11.1

v1.11.0 - Add Reboot controller, and resync multicolor lights

Choose a tag to compare

@github-actions github-actions released this 21 Jul 01:44
Immutable release. Only release title and notes can be modified.

[1.11.0] - 2026-07-21

Added

  • Added a device reboot control: a Reboot button on the firmware-update web page (with confirmation and auto-reload countdown) backed by a new /reboot endpoint, and a matching Home Assistant Reboot button via MQTT (pool/<device_id>/reboot)
  • Decoded the solar protocol messages, previously logged as unhandled: register 0x3A (slot 0x01) is the Solar Setpoint in °C (confirmed by changing the setpoint in the UI and watching the rebroadcast; polled by the Internet Gateway every cycle), CMD 0x2D is the Solar Setpoint Broadcast the touchscreen fires when the setpoint is changed (1-byte °C), and CMD 0x2C is the Solar Status Broadcast carrying the solar mode (Off/Auto/On), a config bitmask (Winter/Summer season, Flush daily, Filter pump required for solar), the temperature differential, and two bytes read as the pool-water/roof temperature readings (byte order unconfirmed) — all three handled log-only and documented in PROTOCOL.md, with the remaining 0x2C unknowns (a byte tracking differential − 3, an always-set bit) flagged for further captures
  • Decoded register 0x30 (slot 0x01) as the Current Water Temperature mirror, previously logged as unhandled: the touchscreen mirrors the controller's CMD 0x16 water-temperature reading into this register (same 2-byte {temp1, temp2} payload) for the Internet Gateway to poll each cycle, confirmed by the value tracking the controller's own sensor across captures — including through a temperature change, and staying with the controller's reading when the heat pump's differed — and by installs without a controller temp sensor never answering the poll; handled log-only since CMD 0x16 remains the authoritative state source, and documented in PROTOCOL.md along with a still-unknown register 0x30 slot 0x03 (1-byte, only 0x00 observed) left unhandled so novel values keep surfacing
  • Added a per-zone Resync button to Home Assistant for multicolor light zones: pressing it broadcasts the Light Resync Command (CMD 0x3C) for that zone, impersonating the Touchscreen (the only observed sender); the button is created alongside the zone's color effect list — so only multicolor-capable zones on installs with a known light type get one — and is removed if the zone stops being multicolor

Changed

  • Renamed CMD 0x3C from "Light Refresh Command" to "Light Resync Command" in PROTOCOL.md and the decoder to better describe its effect of resynchronizing a zone's light

What's Changed

  • Decoded the solar protocol messages, previously logged as unhandled by @marklynch in #73

Full Changelog: v1.10.0...v1.11.0

v1.10.0 - Multicolor light support for SLX and Delta types

Choose a tag to compare

@github-actions github-actions released this 19 Jul 02:55
Immutable release. Only release title and notes can be modified.

[1.10.0] - 2026-07-19

Added

  • Decoded CMD 0x3C as the Light Refresh Command, previously logged as unhandled: a touchscreen broadcast carrying a light zone index that refreshes/resyncs that zone's light, observed during light configuration sessions and color operations — handled log-only with out-of-range zone indexes recorded as "undocumented" entries on the Unknown Messages page; documented in PROTOCOL.md with the exact effect at the light hardware flagged unconfirmed
  • Added light color control to the Home Assistant light entities for multicolor zones: each zone whose light is multicolor-capable (and whose system light type is known via register 0xF0) now exposes the model's color list — the SLX or Delta subset of the shared color table — as the light's effect list, with the current color shown as the active effect and color picks sent to the controller as a Gateway-sourced register write; light entity discovery re-publishes automatically if the light type or a zone's multicolor capability changes, and the MQTT light state now uses the canonical color names (e.g. "User 1" instead of "User1") so the reported color matches the effect list
  • Decoded register 0xF0 (slot 0x01) as the system-wide Multicolor Light Type selection, previously an unknown register: the light model chosen in the touchscreen's light setup (0x00 = SLX, 0x01 = Delta, 0xFF = no multicolor light), confirmed by switching the type in the UI and watching the register rebroadcast. Surfaced as a "Multicolor Light Type" row on the status page and a multicolor_light_type field in the /status JSON ("None"/"SLX"/"Delta"); an unmapped model index is shown as "Unknown (0xXX)" and recorded as an "undocumented" entry on the Unknown Messages page — documented in PROTOCOL.md, where the selected model also determines which subset of the shared light color code table applies — the full SLX and Delta color code subsets were mapped by cycling every color on each type, and a Gateway-sourced 0x3A color write was verified working (sets the light and updates the touchscreen)
  • Decoded CMD 0x07 as the Lighting Zone Color Broadcast, previously logged as unhandled every ~10 s on installs with a multicolor light: the color companion to the CMD 0x06 light config broadcast ({zone_idx, color} payload), emitted only for multicolor-capable zones, with the color byte mirroring the zone's Light Zone Color register (0xD0+zone, slot 0x01) — handled log-only since the register broadcast remains the state source, with out-of-range zone indexes recorded as "undocumented" entries on the Unknown Messages page; documented in PROTOCOL.md with the color enumeration flagged install-specific
  • Decoded the Light Zone Enabled registers (0x90–0x93, slot 0x01), previously logged as unhandled: a 1-byte per-zone flag reporting whether each light zone is configured in the controller (0x01 = configured, 0x00 = not), completing the slot-0x01 per-zone register family alongside multicolor/name/state/color/active. Configured zones are rebroadcast regularly while unconfigured ones only appear at startup or after a configuration change; the flag now drives the zone's configured state (a newly seen zone wakes the register requester to poll its name/color, and an unconfigured zone stops publishing to Home Assistant) — documented in PROTOCOL.md

What's Changed

Full Changelog: v1.9.0...v1.10.0

v1.9.0 - Service mode handling and reduced unknown messages

Choose a tag to compare

@github-actions github-actions released this 17 Jul 04:33
Immutable release. Only release title and notes can be modified.

[1.9.0] - 2026-07-17

Added

  • Decoded the CMD 0x2B controller heartbeat — a ~60 s unicast from the Connect 10 controller (0x0062) directly to the Touchscreen (0x0050), payload 02 00 in every capture, previously logged as an unhandled frame each cycle. Now handled log-only by handle_controller_heartbeat, which records any deviation from 02 00 as an "undocumented" entry on the Unknown Messages page. As one of only two message types the controller sends straight to the touchscreen, it is a prime candidate carrier for a controller-originated state signal such as service mode (documented in PROTOCOL.md)
  • Decoded Service Mode: byte 11 of the Connect 10 controller's CMD 0x12 status is a bitfield (bit 0 = heater on/off, bit 1 = service mode), confirmed by a service-mode capture — documented in PROTOCOL.md and surfaced as a new Home Assistant "Service Mode" binary sensor (published on first status frame) and a service_mode field in the /status JSON
  • Decoded the Genus Heater's (Active i25 Evo heat pump, 0x0070) CMD 0x12 device status, previously logged as unhandled: same 4-byte payload shape as the gas heaters with matching heater-on and water-flow bits, but a heat-pump-specific upper-bit set (0x13 observed immediately after a Gateway heater-on command, so bit 4 is read as actively heating rather than the gas heaters' lockout); heater on/off feeds the Heater 1 state in Home Assistant, and any status value outside the observed set — or a non-zero padding byte — is recorded as an "undocumented" entry on the Unknown Messages page — documented in PROTOCOL.md with the tentative bits flagged unconfirmed

Changed

  • Extended undocumented-payload flagging (record_undocumented()) across more decoders as protocol-research groundwork toward service-mode detection, so a novel byte value is surfaced on the Unknown Messages page instead of being silently absorbed: CMD 0x26 Configuration (reserved bits 5–7 set, or the documented always-1 bit 0 clear), the Connect 10 controller's own CMD 0x12 status (padding byte 10, state byte 11, and the unknown byte 12 — a likely service/interlock carrier), CMD 0x05 touchscreen ack, CMD 0x37 gateway comms status (codes absent from the known table), the VX 11S v3 chlorine-output byte 12 (to catch a LOW SALT / NO FLOW warning capture), CMD 0x06 light config (out-of-range zone index / undocumented status byte), the CMD 0x27 short-form valve data byte, CMD 0x38 timer day-bitmask bit 7, and the favourite-enable register — documented in PROTOCOL.md

Fixed

  • Fixed register writes made from the Viron Chlorinator's app (light zone on/off and color picks) being logged as unhandled: the chlorinator broadcasts the same CMD 0x3A Register Write the Gateway uses, so the command is now dispatched on its CMD byte regardless of source (handler renamed to handle_register_write_request), with the newly observed light-zone-color write target (0xD0–0xD7/slot 0x01) and its app color codes documented in PROTOCOL.md — flagged as an install-specific enumeration since the codes conflict with the reference system's color values
  • Fixed channel toggles made at the Connect 10 controller's physical buttons being logged as unhandled: the controller broadcasts the same CMD 0x10 Channel Toggle Command the Gateway uses for remote toggles (same 1-byte channel-index payload), so the command is now dispatched on its CMD byte regardless of source — controller-sourced examples (channel indexes 0x04–0x07) documented in PROTOCOL.md
  • Fixed favourite activations made at the Connect 10 controller itself (e.g. All Auto) being logged as unhandled: the controller unicasts the same CMD 0x2A Favourite Control Command to the Touchscreen as the Gateway does for remote activations, so the command is now dispatched on its CMD byte regardless of source and updates the active favourite in Home Assistant — both sources documented in PROTOCOL.md
  • Fixed CMD 0x26 Configuration broadcasts being counted as unknown/unhandled on every cycle: the handler returned false unconditionally, routing each normal config frame into the Unknown Messages buffer. It now decodes the frame, returns decoded, and records only genuine anomalies (see above), so the unknown-message counter and page reflect real unknowns
  • Fixed the inbuilt heater wrongly reporting On while the controller is in service mode with the heater off: the CMD 0x12 status byte was treated as a boolean (any non-zero value = On), so the service-mode value 0x02 read as heater on; the heater state now comes from bit 0 only

What's Changed

  • Decode Connect 10 service mode (CMD 0x12) and flag undocumented bytes across decoders by @marklynch in #67
  • Fixed channel toggles made at the Connect 10 controller's physical buttons by @marklynch in #69
  • Decoded the Genus Heater's (Active i25 Evo heat pump, 0x0070) CMD 0x12 by @marklynch in #70

Full Changelog: v1.8.2...v1.9.0

v1.8.2 - Firmware update polish and Mode Set command

Choose a tag to compare

@github-actions github-actions released this 16 Jul 02:59
Immutable release. Only release title and notes can be modified.

[1.8.2] - 2026-07-16

Changed

  • Decoded the newly discovered CMD 0x15 Mode Set Command: broadcast from the Touchscreen address (0x0050), switches Spa/Pool mode using the same encoding as the 0x14 status (Spa=0x00, Pool=0x01, unlike the inverted 0x2A values), confirmed by injection testing — documented in PROTOCOL.md and decoded into pool state, so mode switches commanded by other senders update Home Assistant immediately
  • The Home Assistant Spa/Pool mode select (mode/set) now switches mode with the dedicated CMD 0x15 Mode Set Command instead of activating the Pool/Spa built-in favourites via CMD 0x2A; mode values throughout the code now use the new MODE_SPA/MODE_POOL constants
  • Renamed CMD 0x2A from "Mode/Favourite Control Command" to "Favourite Control Command" throughout PROTOCOL.md and the code (handler, pattern constant, comments and logs), since its value space is the favourite slots; the Spa/Pool MQTT mode select is unaffected

Fixed

  • Fixed the Home Assistant "Firmware" update entity reverting to "Update available" (with a live Install button) during the post-flash reboot: the rebooting window now reports as still installing with an indeterminate progress bar, flipping to "Up-to-date" once the new firmware boots and reports its version

What's Changed

Full Changelog: v1.8.1...v1.8.2

v1.8.1 - Cleanup phantom channel entities

Choose a tag to compare

@github-actions github-actions released this 16 Jul 02:40
Immutable release. Only release title and notes can be modified.

[1.8.1] - 2026-07-16

Fixed

  • Added a logo to the Home Assistant "Firmware" update entity (shown on the Updates page and in the update dialog) by pointing its entity_picture at the project favicon hosted on GitHub
  • Fixed phantom "Unused" / "Unused Active" Home Assistant entities appearing for unconfigured channels: a channel-state register (0x8C–0x93), which the controller broadcasts for unused channels too, wrongly marked the channel as configured and published discovery for it under the fallback type name "Unused"; a channel is now only marked in use once its type is known

Full Changelog: v1.8.0...v1.8.1

v1.8.0 - Firmware update from github

Choose a tag to compare

@github-actions github-actions released this 16 Jul 01:50
Immutable release. Only release title and notes can be modified.

[1.8.0] - 2026-07-16

Added

  • GitHub firmware auto-update: the device now periodically checks the project's recent GitHub releases (every 12 h, plus shortly after boot) — the latest plus the previous 4 versions, read from the streamed releases.atom feed — and can install any of them over-the-air by pulling that release's pool-controller-update-<tag>.bin asset directly over HTTPS (esp_https_ota), no local file handling needed. The /update page gained an "Update from GitHub" section showing whether a newer release is available (with a release-notes link) and a version dropdown to install or roll back to a specific release, with live download progress. A new Home Assistant update entity ("Firmware") mirrors the check and installs the latest via the HA Install button, reporting installed/latest versions and download progress. Repo, number of versions tracked, check interval, and timeouts are configurable via FW_UPDATE_* in config.h. (New firmware_update module; see OTA_UPDATE.md.)
  • WiFi mesh roaming support: on connect the device now scans all channels and joins the strongest AP broadcasting the SSID (instead of the first one found), and advertises 802.11k/v so mesh networks like eero can steer it to a better node while connected
  • Decoded the Channel Category registers (0xF5–0xFC, slot 0x01, one per channel: Pool equipment / Light / Controlled Heater Power; only broadcast for in-use channels) — documented in PROTOCOL.md Appendix A and now decoded into pool state instead of being logged as unhandled registers
  • Decoded the Active Favourite register (0x20, slot 0x03): reports the currently active favourite/mode using the CMD 0x2A values, with 0xFF = none active (All Off reports as Pool and All Auto as none, since the controller treats them as momentary actions rather than states) — documented in PROTOCOL.md Appendix A and fed into the Home Assistant favourite select, which gains a status-only "No Favourite" option; favourites activated outside HA now update the select (immediately for gateway/MQTT commands, at the next ~8-minute register dump for touchscreen activations)

What's Changed

  • Added - Decoded the Channel Category registers by @marklynch in #61
  • Added - Decoded the Active Favourite register (0x20, slot 0x03) by @marklynch in #63
  • Add GitHub firmware auto-update with Home Assistant update entity by @marklynch in #66

Full Changelog: v1.7.0...v1.8.0

v1.7.0 - Improve message reliability and make unknown messages visible

Choose a tag to compare

@github-actions github-actions released this 14 Jul 00:29
Immutable release. Only release title and notes can be modified.

Lots of improvements from @lawther and collaboration from @zagnuts

[1.7.0] - 2026-07-14

Added

  • Saving of unknown bus messages, added web UI to view them
  • Periodic heap-stats logging (free, minimum-free watermark, largest free block) every 5 minutes via a new low-priority heap_monitor task, so a slow memory leak or growing fragmentation is visible as a trend in the console/TCP log history before it can exhaust the heap. The home page System table now also shows a Memory row (free / min-free), using the memory fields already present in the /status JSON.

Changed

  • Note about CMD 0x05 observed with payload 0x00. Added to PROTOCOL.md and new sample trace.
  • Note about CMD 0x12 observed with payload 0x01 0x00. Added to PROTOCOL.md and new sample trace.
  • Parsing for CMD 0x12 updated so 0x01 0x00 no longer reported as unexpected.
  • Full decoding of CMD 0x12 status byte for Gas Heaters (HiNRG 0x0072 and ICI (0x0074))
  • Rewrote frame level parser to use a sliding window for better resync on errors
  • Track frame resync events per failure type (no-start, header/data checksum, control, length, end-byte, overflow) and show the breakdown on the status page, separate from message-level decode errors
  • Unknown Messages page now tags each captured frame with its specific reason (no start, overflow, header/data checksum, length, bad framing) instead of a generic "error" chip
  • Handlers can now flag recognised frames carrying an undocumented field value via the new UNKNOWN_REASON_UNDOCUMENTED_PAYLOAD reason (record_undocumented()): the known fields are still decoded/published and the frame is counted as decoded, while the raw frame is surfaced on the Unknown Messages page as a non-error "undocumented" chip (amber, distinct from red framing errors). Applied across the decoders wherever a field value falls outside its documented set: heater 1/2 state (0xE6/0xE9), gas-heater status (0x12), Spa/Pool mode (0x14), touchscreen status (0x12), chlorinator mode (0x12) and pump-mode (0x0F), VX 11S status (0x12), pump speed command (0x18) and buttons (0x1B), mode/favourite command (0x2A), gateway register writes (0x3A), and the register-based channel type/state, light zone state/colour/name, valve state, and channel-status broadcasts (0x0B/0x27/0x38).
  • Breaking (HTTP): /status JSON's message_counts.errors (per-type protocol error counters) and message_counts.error_detail (introduced in 1.6.0-rc1) are removed, replaced by the new per-type resyncs object (total, no_start, header_checksum, bad_control, bad_length, bad_end, data_checksum, buffer_overflow) emitted by the sliding-window frame parser. Any external consumer reading the old fields needs to switch to resyncs.
  • Frame parser and decoder now also accept "discovery" packets (control bytes 0x00 0x00): a shorter 11-byte frame shape with no payload and no data-checksum byte, alongside the existing 0x80 0x00 data packets

Fixed

  • Fixed the home page Temperature row showing nothing useful since the per-source temperature refactor: the page's script still read the removed /status field temperature.current; it now renders one row per temperature-reporting device (e.g. "Genus Heater Temperature", "Connect 8/10 Temperature 1/2") from the per-device temperature1/temperature2 fields
  • TCP bridge no longer hangs when a connected client stalls or dies silently. The client socket is now non-blocking with a keep/drop policy on every send (full send keeps the client; a full send buffer drops the message but keeps the connection; a partial write or hard error drops the client), so a stuck peer can never wedge the bridge task — which is also the only task reading the UART, meaning such a wedge previously froze the whole device with no logs and no recovery. Added TCP keepalive (idle 30s, interval 5s, count 3) so a silently dead peer is detected and the single client slot freed.
  • MQTT client is no longer stopped from inside the WiFi event handler on disconnect. esp_mqtt_client_stop() blocks waiting for the MQTT task, and calling it from the system event-loop task could stall all further WiFi/IP event processing if the MQTT task was itself stuck on a dead socket during the same outage. The client is now started once on first connectivity and left to esp-mqtt's built-in reconnection, which handles WiFi drops/restores on its own. mqtt_client_start() is now idempotent so repeated reconnects are no-ops.

Security

  • Fixed a DOM-based XSS on the home page: channel/zone/valve names read from the bus were injected into the pool status table via innerHTML. They are now set with textContent so bus-derived strings can never be parsed as HTML/script.

What's Changed

  • feat: document unknown touchscreen messages by @lawther in #26
  • Add full CMD 0x12 gas heater status decoding (HiNRG 0x0072 / ICI 0x0074) by @lawther in #42
  • feat: VX 11S v3 chlorinator status handler by @lawther in #29
  • feat: Add capture buffer for unknown/error bus frames with web UI page by @lawther in #44
  • feat: sliding-window frame parser with per-type resync tracking by @lawther in #47
  • Support discovery packets (00 00 control bytes) in frame parsing by @lawther in #48
  • Fix DOM-based XSS on home page pool table by @marklynch in #51
  • Addresses PR #46 review comments: by @lawther in #50
  • Add decoder for CMD 0x0F chlorinator pump mode unicast (issue #23) by @lawther in #54
  • Record undocumented payloads in unknown buffer (issue #55) by @lawther in #56
  • Fix TCP bridge hang when a client stalls or dies silently by @marklynch in #49
  • Fixed the home page Temperature row showing nothing useful by @marklynch in #59

Full Changelog: v1.6.0...v1.7.0