Skip to content

Commit a03a321

Browse files
committed
feat: add control methods to chlorinators
1 parent b0edd9d commit a03a321

3 files changed

Lines changed: 103 additions & 32 deletions

File tree

pyomnilogic_local/chlorinator.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
from pyomnilogic_local._base import OmniEquipment
2+
from pyomnilogic_local.decorators import dirties_state
23
from pyomnilogic_local.models.mspconfig import MSPChlorinator
34
from pyomnilogic_local.models.telemetry import TelemetryChlorinator
4-
from pyomnilogic_local.omnitypes import ChlorinatorOperatingMode, ChlorinatorStatus
5+
from pyomnilogic_local.omnitypes import (
6+
ChlorinatorCellType,
7+
ChlorinatorOperatingMode,
8+
ChlorinatorStatus,
9+
)
10+
from pyomnilogic_local.util import OmniEquipmentNotInitializedError
511

612

713
class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
@@ -54,7 +60,7 @@ def dispenser_type(self) -> str:
5460
return self.mspconfig.dispenser_type
5561

5662
@property
57-
def cell_type(self) -> str:
63+
def cell_type(self) -> ChlorinatorCellType:
5864
"""Type of T-Cell installed (e.g., T3, T5, T9, T15)."""
5965
return self.mspconfig.cell_type
6066

@@ -321,3 +327,71 @@ def is_ready(self) -> bool:
321327

322328
# Then check chlorinator-specific readiness
323329
return self.is_authenticated and not self.has_error
330+
331+
# Control methods
332+
@dirties_state()
333+
async def turn_on(self) -> None:
334+
"""Turn the chlorinator on (enable it).
335+
336+
Raises:
337+
OmniEquipmentNotInitializedError: If bow_id is None.
338+
"""
339+
if self.bow_id is None:
340+
raise OmniEquipmentNotInitializedError("Cannot turn on chlorinator: bow_id is None")
341+
await self._api.async_set_chlorinator_enable(self.bow_id, True)
342+
343+
@dirties_state()
344+
async def turn_off(self) -> None:
345+
"""Turn the chlorinator off (disable it).
346+
347+
Raises:
348+
OmniEquipmentNotInitializedError: If bow_id is None.
349+
"""
350+
if self.bow_id is None:
351+
raise OmniEquipmentNotInitializedError("Cannot turn off chlorinator: bow_id is None")
352+
await self._api.async_set_chlorinator_enable(self.bow_id, False)
353+
354+
@dirties_state()
355+
async def set_timed_percent(self, percent: int) -> None:
356+
"""Set the timed percent for chlorine generation.
357+
358+
Args:
359+
percent: The chlorine generation percentage (0-100)
360+
361+
Raises:
362+
OmniEquipmentNotInitializedError: If bow_id or system_id is None.
363+
ValueError: If percent is outside the valid range (0-100).
364+
365+
Note:
366+
This method uses the async_set_chlorinator_params API which requires
367+
all chlorinator configuration parameters. The current values from
368+
mspconfig are used for unchanged parameters.
369+
"""
370+
if self.bow_id is None or self.system_id is None:
371+
raise OmniEquipmentNotInitializedError("Cannot set timed percent: bow_id or system_id is None")
372+
373+
if not 0 <= percent <= 100:
374+
raise ValueError(f"Timed percent {percent} is outside valid range [0, 100]")
375+
376+
# Get the parent Bow to determine bow_type
377+
# We need to find our bow in the backyard
378+
if (bow := self._omni.backyard.bow.get(self.bow_id)) is None:
379+
raise OmniEquipmentNotInitializedError(f"Cannot find bow with id {self.bow_id}")
380+
381+
# Map equipment type to numeric bow_type value
382+
# BOW_POOL = 0, BOW_SPA = 1 (based on typical protocol values)
383+
bow_type = 0 if bow.equip_type == "BOW_POOL" else 1
384+
385+
# Get operating mode from telemetry (it's already an int or enum with .value)
386+
op_mode = self.telemetry.operating_mode if isinstance(self.telemetry.operating_mode, int) else self.telemetry.operating_mode.value
387+
388+
await self._api.async_set_chlorinator_params(
389+
pool_id=self.bow_id,
390+
equipment_id=self.system_id,
391+
timed_percent=percent,
392+
cell_type=self.mspconfig.cell_type.value, # ChlorinatorCellType is now IntEnum, use .value
393+
op_mode=op_mode,
394+
sc_timeout=self.mspconfig.superchlor_timeout,
395+
bow_type=bow_type,
396+
orp_timeout=self.mspconfig.orp_timeout,
397+
)

pyomnilogic_local/models/mspconfig.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,21 @@ class MSPChlorinator(OmniBase):
219219
cell_type: ChlorinatorCellType = Field(alias="Cell-Type")
220220
chlorinator_equipment: list[MSPChlorinatorEquip] | None = None
221221

222+
@model_validator(mode="before")
223+
@classmethod
224+
def convert_cell_type(cls, data: Any) -> Any:
225+
"""Convert cell_type string to ChlorinatorCellType enum by name."""
226+
if isinstance(data, dict) and "Cell-Type" in data:
227+
cell_type_str = data["Cell-Type"]
228+
if isinstance(cell_type_str, str):
229+
# Parse by enum member name (e.g., "CELL_TYPE_T15" -> ChlorinatorCellType.CELL_TYPE_T15)
230+
try:
231+
data["Cell-Type"] = ChlorinatorCellType[cell_type_str]
232+
except KeyError:
233+
# If not found, try to parse as int or leave as-is for Pydantic to handle
234+
pass
235+
return data
236+
222237
def __init__(self, **data: Any) -> None:
223238
super().__init__(**data)
224239

pyomnilogic_local/omnitypes.py

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -155,36 +155,18 @@ class ChlorinatorDispenserType(StrEnum, PrettyEnum):
155155
TABLET = "TABLET_DISPENSING"
156156

157157

158-
class ChlorinatorCellType(StrEnum, PrettyEnum):
159-
UNKNOWN = "CELL_TYPE_UNKNOWN"
160-
T3 = "CELL_TYPE_T3"
161-
T5 = "CELL_TYPE_T5"
162-
T9 = "CELL_TYPE_T9"
163-
T15 = "CELL_TYPE_T15"
164-
T15_LS = "CELL_TYPE_T15_LS"
165-
TCELLS315 = "CELL_TYPE_TCELLS315"
166-
TCELLS325 = "CELL_TYPE_TCELLS325"
167-
TCELLS340 = "CELL_TYPE_TCELLS340"
168-
LIQUID = "CELL_TYPE_LIQUID"
169-
TABLET = "CELL_TYPE_TABLET"
170-
171-
# There is probably an easier way to do this
172-
def __int__(self) -> int:
173-
return ChlorinatorCellInt[self.name].value
174-
175-
176-
class ChlorinatorCellInt(IntEnum, PrettyEnum):
177-
UNKNOWN = 0
178-
T3 = 1
179-
T5 = 2
180-
T9 = 3
181-
T15 = 4
182-
T15_LS = 5
183-
TCELLS315 = 6
184-
TCELLS325 = 7
185-
TCELLS340 = 8
186-
LIQUID = 9
187-
TABLET = 10
158+
class ChlorinatorCellType(IntEnum, PrettyEnum):
159+
CELL_TYPE_UNKNOWN = 0
160+
CELL_TYPE_T3 = 1
161+
CELL_TYPE_T5 = 2
162+
CELL_TYPE_T9 = 3
163+
CELL_TYPE_T15 = 4
164+
CELL_TYPE_T15_LS = 5
165+
CELL_TYPE_TCELLS315 = 6
166+
CELL_TYPE_TCELLS325 = 7
167+
CELL_TYPE_TCELLS340 = 8
168+
CELL_TYPE_LIQUID = 9
169+
CELL_TYPE_TABLET = 10
188170

189171

190172
# Lights

0 commit comments

Comments
 (0)