Skip to content

feat: surface locally-pushed chemistry dose events, water quality, energy (incl. heater gas BTU) and heater egg timer - #8

Draft
kingbear2 wants to merge 13 commits into
PoolsideStan:mainfrom
kingbear2:feat/locally-pushed-dose-wq-energy-timer
Draft

kingbear2 wants to merge 13 commits into
PoolsideStan:mainfrom
kingbear2:feat/locally-pushed-dose-wq-energy-timer

Conversation

@kingbear2

Copy link
Copy Markdown

Motivation

The controller's Device.setStatus local push stream already delivers several status items that the integration receives but currently drops at the entity layer — no polling, no cloud, no new API surface needed. This PR surfaces them as sensors:

  • Chemistry dose events — the controller pushes the current/last chemical dose per dosing system under the body-of-water key. The vendor app shows these; HA users currently can't see dosing at all.
  • Water quality state — the body of water's overall chemistry health.
  • Energy consumption — a per-minute consumption document including natural gas Btu, which gives gas-heated pools a gas-usage sensor (something HA otherwise can't see for a pool heater).
  • Heater "egg timer" — the end time of a heater's timed session.

Real payload sample

A real dose-event payload captured from a controller push (keyed under the body-of-water UUID):

{
  "UUID": "9d5365d1-d267-49f2-afa4-a44a09bd7943",
  "StartDateTime": "2026-08-31T13:58:16.8837444-05:00",
  "EndDateTime": "2026-08-31T14:24:24.8837657-05:00",
  "DoseSeconds": 68,
  "DoseAmount": 5.0,
  "DosingUnit": "OZ",
  "MixMinutes": 25,
  "ChemicalType": "CHLORINE",
  "ChemicalPumpUUID": "Universal-Chemical-Pump",
  "InterruptedAt": null,
  "Before": 597.0,
  "After": null,
  "IgnoreBadEvent": false,
  "PumpMinimumPowerLevelWhileMixing": 25,
  "PumpMinimumPowerLevelWhileDosing": 100,
  "ManuallyInitiatedEvent": false,
  "DosingCutShort": false
}

The item's value arrives as this document JSON-encoded inside the string value (double-encoded, like the InformationFields documents the integration already parses); the item goes null between doses, so the sensors keep the last known dose rather than flapping to unknown.

New entities

Dose events (per body of water, per dosing system that reports; Last acid dose, Last chlorine dose, Last salt chlorinator dose from CurrentAcidMixEvent / CurrentLiquidChlorineMixEvent / CurrentSaltChlorinatorMixEvent):

  • state: DoseAmount — state_class: measurement, unit fl. oz.
  • attributes: duration_seconds (DoseSeconds), started / ended (Start/EndDateTime), mix_minutes (MixMinutes), probe_before (Before), interrupted (InterruptedAt), cut_short (DosingCutShort), manually_initiated (ManuallyInitiatedEvent), unit (DosingUnit); null-valued keys are omitted
  • the last dose is preserved when the item goes null; a new dose document replaces it

Water quality (WaterQualityState): enum sensor (good / ok / low / alert / none options) with a tolerant mapper — unrecognized raw values render as unknown instead of crashing.

Energy (from LastMinuteOfEnergyConsumption, JSON string):

  • Power consumption: TotalWattsUsed — device_class power, unit W, state_class measurement
  • Gas consumption: TotalNaturalGasBtuUsed — unit Btu, state_class measurement, with TotalLiquidPropaneBtuUsed as a liquid_propane_btu attribute (keeping the entity count lean). No device class on the gas sensor: the value is thermal energy (Btu), and HA's gas device class is for metered fuel volume, which this isn't.

Heater egg timer (OnUntil, per temperature control that reports it): timestamp sensor with the ISO end time of the timed session, null-safe when no session runs.

Implementation notes

  • All data arrives via the existing Device.setStatus subscription channel the integration already consumes; each new entity follows the codebase's existing patterns (entity-description dataclasses, _status_keys subscription, controller_uuid + item-UUID + key unique_ids, DeviceInfo via the group entity, availability gated on the client connection).
  • Every entity is added the first time its item is reported (the same dynamic pattern as the optional chemistry probes and the heating/cooling mode selects), so installs without a given chemical system, gas heater, or timer never see the entity. All parsers are null-safe and tolerant of malformed payloads.
  • Unit tests for the JSON-string parsing helpers (dose event, energy document, OnUntil datetime) are in tests/test_sensor_parsing.py — pure functions, 6 passing. pytest.ini sets pythonpath = ["."] for the import. No test framework was previously configured in the repo; happy to drop or restructure these if you'd rather not carry tests here.
  • manifest.json unchanged; no new dependencies, no cloud/API access anywhere.

Happy to adjust entity shapes to maintainer preference — names, units, attributes, or which items deserve their own entity vs. an attribute.

Kevin M added 13 commits October 21, 2026 19:51
…timer entities

The controller's Device.setStatus local push stream already delivers
several status items the integration drops at the entity layer:

- Last chemical dose per dosing system (CurrentAcidMixEvent /
  CurrentLiquidChlorineMixEvent / CurrentSaltChlorinatorMixEvent):
  a measurement of the dose amount with duration, timestamps, mix
  minutes, probe reading before, interruption, and origin as
  attributes. The item's JSON document parses tolerantly and the last
  dose is preserved when the item goes null between doses.
- Water quality (WaterQualityState): enum sensor over the raw
  GOOD/OK/LOW-style values with a tolerant mapper.
- Last-minute energy consumption (LastMinuteOfEnergyConsumption): an
  electric power sensor (TotalWattsUsed) and a gas sensor
  (TotalNaturalGasBtuUsed, with TotalLiquidPropaneBtuUsed as an
  attribute) - giving gas-heated pools a gas-usage sensor.
- Heater timed-session end (OnUntil): a timestamp sensor per
  temperature control that reports the item.

All new entities follow the existing patterns: added on first report
(the equipment behind them is optional), availability gated on the
client connection, and null-safe against missing or malformed items.
Includes unit tests for the JSON-string parsing helpers.
…ns (disabled by default)

- Dose sensors (+ salt-gate kept), energy sensors, and heater egg-timer
  sensors are created at setup instead of waiting for their first push,
  so installs see them immediately as no data.
- New per-chemical 'dosed since last reset' running-total sensors
  (disabled by default, restored across restarts, UUID-deduped).
- New matching reset buttons (disabled by default) on a new button
  platform; pressing zeroes the paired counter for refill tracking.
…er to heater device

- Dose sensing trio attaches to the chemical pump device matched via the
  pump's ChemicalType model, falling back to the body group device when
  a site reports no matching pump.
- Gas consumption attaches to the heater device; watts stays on the
  body group device (whole-body aggregate).
- Heater egg-timer sensor attaches to the heater device when present.
- Icons: totals get flask/tonic icons, reset gets restart-alert.
…pers recipe

- Counters record the reset timestamp (persisted with the total).
- New TANK_TRACKING.md documents the tank-level pattern: number helper +
  counter-triggered decrement automation (positive-delta filter so resets
  are ignored) + low-tank alert; README links to it.
The controller carries LastMinuteOfEnergyConsumption on its site status
item; reading the body uuid left power consumption (and heater gas)
unknown forever on installs that stream it site-side. The lookup now
prefers the body and falls back to the client's site_uuid, and the
sensor subscribes to both keys.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant