feat: equipment timer read/write controls (switch, time, select entities)#3
feat: equipment timer read/write controls (switch, time, select entities)#3davidbell81 wants to merge 9 commits into
Conversation
Confirmed write protocol from decompiled app source (TimeConfigCharacteristic3). Fixes parser bugs and adds writable HA entities for timer management. Parser fixes (pychlorinator_cloud/timers.py): - slot_index was reading data[0] (TimerType, always 0) — fixed to data[1] (TimerIndex) - enables was reading 1 byte — fixed to uint16 LE from bytes [4-5] - Updated TIMER_EQUIPMENT_FLAGS to full EnablesValues bitmask from C# source - Added timer_mode / start_mode / stop_mode fields to TimerConfig Library additions (pychlorinator_cloud/websocket_client.py): - Add timer cmd IDs 400-403 to _request_all_data so timers are fetched on connect - Add request_timer_data() for on-demand refresh - Add write_timer_slot() with read-modify-write for unspecified fields - Add TIMER_CONFIG_CMD_ID = 0x0193 HA component additions: - const.py: add "switch" and "time" to PLATFORMS - select.py: HaloTimerSeasonSelect — Summer/Winter profile switcher - switch.py: HaloTimerSlotSwitch — enable/disable each of 4 timer slots - time.py: HaloTimerSlotTime — start and stop time pickers per slot Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntities Plain dataclasses lack translation_key and device_class attributes that HA 2026.4 reads from entity_description when computing entity.translation_key and entity.device_class, causing AttributeError during entity registration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed + heater controls - Split timer_configs into equipment_timer_configs (type=0) and lighting_timer_configs (type=1) so equipment and lighting timer slot 0 no longer overwrite each other - Add debug logging for every timer_config frame received - write_timer_slot now accepts enables param for bitmask control - New HaloTimerSlotHeaterSwitch: toggles ENABLES_HEATER bit per equipment timer slot - New HaloTimerSlotPumpSpeedSelect: Low/Medium/High per equipment timer slot - Update sensor.py timer summary to read from equipment_timer_configs
… configs The protocol requires specifying [timer_type, slot_index, timer_mode] in the read request payload — all-zero payload only returns Winter equipment slot 0. Now request all 16 combinations (pump+lighting × 4 slots × winter+summer). Store all received configs in all_equipment/lighting_timer_configs keyed by (slot_index, timer_mode). equipment/lighting_timer_configs are rebuilt to show only the currently active season's data whenever timer_season is set. write_timer_slot now resolves the active timer_mode from timer_season and reads existing config from all_equipment_timer_configs for that mode.
The cloud API ignores the timer config request payload and always returns the same frame (Winter equipment slot 0). Simplified back to a single request. _rebuild_active_timer_configs now falls back to the other season's data when the active season's config isn't available, so entities stay usable in Summer mode even though the cloud only streams Winter timer configs.
Cloud API only streams Winter timer data regardless of season or request payload. Permanently use Winter mode; remove all_*_timer_configs dicts, _rebuild_active_timer_configs, _is_active_timer_mode, _request_all_timer_slot_configs, and HaloTimerSeasonSelect entity. Timer slot configs now stored directly by slot_index. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hi @davidbell81 , really appreciate the depth of this !! apologies for late response, ive been busy at home/work/life. The I should give you a heads-up: in parallel I've been working on a substantial rewrite for What that means for this PR specifically:
Plan: I'll get Have you tested any of this against firmware 2.7+? Your other PR (#2) suggests yes — useful to know the timer write path actually round-trips on the gated firmware too, since I only have fw 2.3 to test against. |
|
No apology needed, your time is your time. On the fw 2.7+ question: yes, all of this is tested round-trip on fw 2.7. The Caveat: I'm only testing on a single Halo Chlor 25 with a single Viron NXT pump, so multi-pump and multi-zone configurations are untested from my end. On the rewrite: all good. Once preview.1 is tagged I'll diff against this branch and we can figure out which pieces (the |
Summary
This PR adds full read/write support for the Halo controller's equipment timer slots via the cloud WebSocket API.
Library (
pychlorinator_cloud)timers.py— complete rewrite of timer parsing:TimeConfigCharacteristic3struct confirmed from decompiled app source:<BBBBHBBBBBBB(13 bytes, Pack=1)TimerType, TimerIndex, TimerMode, TimerEnabled, Enables(uint16 LE), StartMode, StartHour, StartMin, StopMode, StopHour, StopMin, SpeedCodeEnablesValuesbitmask constants:ENABLES_FILTER_PUMP (0x0002),ENABLES_HEATER (0x0004), plus Outlet1-4, Valve1-4, Relay1-2build_timer_config_payload()for constructing 0x0193 write payloadsTIMER_TYPE_PUMP,TIMER_TYPE_LIGHTING,TIMER_MODE_WINTER,TIMER_MODE_SUMMERconstantswebsocket_client.py— timer data wiring:ChlorinatorLiveDatagainsequipment_timer_configsandlighting_timer_configs(bothdict[int, dict], keyed by slot index) — equipment and lighting timers at the same slot index no longer overwrite each otherwrite_timer_slot()method: read-modify-write for any combination ofenabled,enables,start_hour/minute,stop_hour/minute,speed_coderequest_timer_data()refreshes all four timer characteristics (0x0190–0x0193)Custom component (
custom_components/astralpool_halo_cloud)New platforms registered in
const.py:switch,timeswitch.py(new): 4 × per-slot entities:Timer Slot N Active— enables/disables the timer slotTimer Slot N Heater— enables/disables the heater bit in the slot'senablesbitmasktime.py(new): 4 × 2 = 8 entities:Timer Slot N Start/Timer Slot N Stop— set start and stop times for each slotselect.pyadditions:Timer Slot N Pump Speed— Low / Medium / High per slotsensor.py: updated_active_timer_countand_timer_summary_attributesto use the renamedequipment_timer_configsfield (wastimer_configs)All new timer entities are disabled by default (
entity_registry_enabled_default=False) and only become available once the device has streamed its timer config.Protocol notes
0x0193; writes use cmd0x0193with prefix0x03and a 20-byte padded frame.Test plan
equipment_timer_configsdict populates with slot 0 data from device streamTimer Slot 1 Activeswitch reflects device state and toggles correctlyTimer Slot 1 Start/Timer Slot 1 Stoptime entities show correct times and can be writtenTimer Slot 1 Pump Speedselect reflects device speed code and writes correctlyTimer Slot 1 Heaterswitch reads and toggles theENABLES_HEATERbit without clobbering other bitsv0.3.0and update manifest requirements after merging🤖 Generated with Claude Code