Skip to content

Commit eaf4a60

Browse files
committed
Implement Chlorinator and CSAD equipment properties with comprehensive status decoding
- Add telemetry properties and bitmask decoding for Chlorinator status, alerts, and errors - Implement CSAD properties for pH/ORP monitoring and chemical dispensing control - Add computed properties (is_on, is_generating, is_ready, has_alert, etc.) with detailed docstrings - Clarify ORP sensor usage: primary control for chlorinator, monitoring only for CSAD
1 parent 818f88f commit eaf4a60

2 files changed

Lines changed: 589 additions & 2 deletions

File tree

pyomnilogic_local/chlorinator.py

Lines changed: 307 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,316 @@
11
from pyomnilogic_local._base import OmniEquipment
22
from pyomnilogic_local.models.mspconfig import MSPChlorinator
33
from pyomnilogic_local.models.telemetry import TelemetryChlorinator
4+
from pyomnilogic_local.omnitypes import ChlorinatorOperatingMode, ChlorinatorStatus
45

56

67
class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
7-
"""Represents a chlorinator in the OmniLogic system."""
8+
"""Represents a chlorinator in the OmniLogic system.
9+
10+
A chlorinator is responsible for generating chlorine through electrolysis
11+
(for salt-based systems) or dispensing chlorine (for liquid/tablet systems).
12+
It monitors and reports salt levels, chlorine generation status, and various
13+
alerts and errors.
14+
15+
Attributes:
16+
mspconfig: The MSP configuration for this chlorinator
17+
telemetry: Real-time telemetry data for this chlorinator
18+
19+
Example:
20+
>>> chlorinator = pool.get_chlorinator()
21+
>>> print(f"Salt level: {chlorinator.avg_salt_level} ppm")
22+
>>> print(f"Is generating: {chlorinator.is_generating}")
23+
>>> if chlorinator.has_alert:
24+
... print(f"Alerts: {chlorinator.alert_messages}")
25+
"""
826

927
mspconfig: MSPChlorinator
1028
telemetry: TelemetryChlorinator
29+
30+
# Expose MSPConfig attributes
31+
@property
32+
def enabled(self) -> bool:
33+
"""Whether the chlorinator is enabled in the system configuration."""
34+
return self.mspconfig.enabled
35+
36+
@property
37+
def timed_percent(self) -> int:
38+
"""Configured chlorine generation percentage when in timed mode (0-100%)."""
39+
return self.mspconfig.timed_percent
40+
41+
@property
42+
def superchlor_timeout(self) -> int:
43+
"""Timeout duration for super-chlorination mode in minutes."""
44+
return self.mspconfig.superchlor_timeout
45+
46+
@property
47+
def orp_timeout(self) -> int:
48+
"""Timeout duration for ORP (Oxidation-Reduction Potential) mode in minutes."""
49+
return self.mspconfig.orp_timeout
50+
51+
@property
52+
def dispenser_type(self) -> str:
53+
"""Type of chlorine dispenser (SALT, LIQUID, or TABLET)."""
54+
return self.mspconfig.dispenser_type
55+
56+
@property
57+
def cell_type(self) -> str:
58+
"""Type of T-Cell installed (e.g., T3, T5, T9, T15)."""
59+
return self.mspconfig.cell_type
60+
61+
# Expose Telemetry attributes
62+
@property
63+
def operating_state(self) -> int:
64+
"""Current operational state of the chlorinator (raw value)."""
65+
return self.telemetry.operating_state
66+
67+
@property
68+
def operating_mode(self) -> ChlorinatorOperatingMode | int:
69+
"""Current operating mode (DISABLED, TIMED, ORP_AUTO, or ORP_TIMED_RW).
70+
71+
Returns:
72+
ChlorinatorOperatingMode: The operating mode enum value
73+
"""
74+
return self.telemetry.operating_mode
75+
76+
@property
77+
def timed_percent_telemetry(self) -> int | None:
78+
"""Current chlorine generation percentage from telemetry (0-100%).
79+
80+
This may differ from the configured timed_percent if the system
81+
is in a special mode (e.g., super-chlorination).
82+
83+
Returns:
84+
Current generation percentage, or None if not available
85+
"""
86+
return self.telemetry.timed_percent
87+
88+
@property
89+
def sc_mode(self) -> int:
90+
"""Super-chlorination mode status (raw value)."""
91+
return self.telemetry.sc_mode
92+
93+
@property
94+
def avg_salt_level(self) -> int:
95+
"""Average salt level reading in parts per million (ppm).
96+
97+
This is a smoothed reading over time, useful for monitoring
98+
long-term salt levels.
99+
"""
100+
return self.telemetry.avg_salt_level
101+
102+
@property
103+
def instant_salt_level(self) -> int:
104+
"""Instantaneous salt level reading in parts per million (ppm).
105+
106+
This is the current salt level reading, which may fluctuate
107+
more than the average salt level.
108+
"""
109+
return self.telemetry.instant_salt_level
110+
111+
# Computed properties for status, alerts, and errors
112+
@property
113+
def status(self) -> list[str]:
114+
"""List of active status flags as human-readable strings.
115+
116+
Decodes the status bitmask into individual flag names.
117+
Possible values include:
118+
- ERROR_PRESENT: An error condition exists (check error_messages)
119+
- ALERT_PRESENT: An alert condition exists (check alert_messages)
120+
- GENERATING: Power is applied to T-Cell, actively chlorinating
121+
- SYSTEM_PAUSED: System processor is pausing chlorination
122+
- LOCAL_PAUSED: Local processor is pausing chlorination
123+
- AUTHENTICATED: T-Cell is authenticated and recognized
124+
- K1_ACTIVE: K1 relay is active
125+
- K2_ACTIVE: K2 relay is active
126+
127+
Returns:
128+
List of active status flag names
129+
130+
Example:
131+
>>> chlorinator.status
132+
['GENERATING', 'AUTHENTICATED', 'K1_ACTIVE']
133+
"""
134+
return self.telemetry.status
135+
136+
@property
137+
def alert_messages(self) -> list[str]:
138+
"""List of active alert conditions as human-readable strings.
139+
140+
Decodes the alert bitmask into individual alert names.
141+
Possible values include:
142+
- SALT_LOW: Salt level is low (add salt soon)
143+
- SALT_TOO_LOW: Salt level is too low (add salt now)
144+
- HIGH_CURRENT: High current alert
145+
- LOW_VOLTAGE: Low voltage alert
146+
- CELL_TEMP_LOW: Cell water temperature is low
147+
- CELL_TEMP_SCALEBACK: Cell water temperature scaleback
148+
- CELL_TEMP_HIGH: Cell water temperature is high (bits 4+5 both set)
149+
- BOARD_TEMP_HIGH: Board temperature is high
150+
- BOARD_TEMP_CLEARING: Board temperature is clearing
151+
- CELL_CLEAN: Cell cleaning/runtime alert
152+
153+
Returns:
154+
List of active alert names
155+
156+
Example:
157+
>>> chlorinator.alert_messages
158+
['SALT_LOW', 'CELL_CLEAN']
159+
"""
160+
return self.telemetry.alerts
161+
162+
@property
163+
def error_messages(self) -> list[str]:
164+
"""List of active error conditions as human-readable strings.
165+
166+
Decodes the error bitmask into individual error names.
167+
Possible values include:
168+
- CURRENT_SENSOR_SHORT: Current sensor short circuit
169+
- CURRENT_SENSOR_OPEN: Current sensor open circuit
170+
- VOLTAGE_SENSOR_SHORT: Voltage sensor short circuit
171+
- VOLTAGE_SENSOR_OPEN: Voltage sensor open circuit
172+
- CELL_TEMP_SENSOR_SHORT: Cell temperature sensor short
173+
- CELL_TEMP_SENSOR_OPEN: Cell temperature sensor open
174+
- BOARD_TEMP_SENSOR_SHORT: Board temperature sensor short
175+
- BOARD_TEMP_SENSOR_OPEN: Board temperature sensor open
176+
- K1_RELAY_SHORT: K1 relay short circuit
177+
- K1_RELAY_OPEN: K1 relay open circuit
178+
- K2_RELAY_SHORT: K2 relay short circuit
179+
- K2_RELAY_OPEN: K2 relay open circuit
180+
- CELL_ERROR_TYPE: Cell type error
181+
- CELL_ERROR_AUTH: Cell authentication error
182+
- CELL_COMM_LOSS: Cell communication loss (bits 12+13 both set)
183+
- AQUARITE_PCB_ERROR: AquaRite PCB error
184+
185+
Returns:
186+
List of active error names
187+
188+
Example:
189+
>>> chlorinator.error_messages
190+
['CURRENT_SENSOR_SHORT', 'K1_RELAY_OPEN']
191+
"""
192+
return self.telemetry.errors
193+
194+
# High-level status properties
195+
@property
196+
def is_on(self) -> bool:
197+
"""Check if the chlorinator is currently enabled and operational.
198+
199+
A chlorinator is considered "on" if it is enabled in the configuration,
200+
regardless of whether it is actively generating chlorine at this moment.
201+
202+
Returns:
203+
True if the chlorinator is enabled, False otherwise
204+
205+
See Also:
206+
is_generating: Check if actively producing chlorine right now
207+
"""
208+
return self.enabled and self.telemetry.enable
209+
210+
@property
211+
def is_generating(self) -> bool:
212+
"""Check if the chlorinator is actively generating chlorine.
213+
214+
This indicates that power is currently applied to the T-Cell and
215+
chlorine is being produced through electrolysis.
216+
217+
Returns:
218+
True if the GENERATING status flag is set, False otherwise
219+
220+
Example:
221+
>>> if chlorinator.is_generating:
222+
... print(f"Generating at {chlorinator.timed_percent_telemetry}%")
223+
"""
224+
return self.telemetry.active
225+
226+
@property
227+
def is_paused(self) -> bool:
228+
"""Check if chlorination is currently paused.
229+
230+
Chlorination can be paused by either the system processor or the
231+
local processor for various reasons (e.g., low flow, maintenance).
232+
233+
Returns:
234+
True if either SYSTEM_PAUSED or LOCAL_PAUSED flags are set
235+
236+
Example:
237+
>>> if chlorinator.is_paused:
238+
... print("Chlorination is paused")
239+
"""
240+
return bool(
241+
(ChlorinatorStatus.SYSTEM_PAUSED.value & self.telemetry.status_raw)
242+
or (ChlorinatorStatus.LOCAL_PAUSED.value & self.telemetry.status_raw)
243+
)
244+
245+
@property
246+
def has_alert(self) -> bool:
247+
"""Check if any alert conditions are present.
248+
249+
Returns:
250+
True if the ALERT_PRESENT status flag is set, False otherwise
251+
252+
See Also:
253+
alert_messages: Get the list of specific alert conditions
254+
"""
255+
return ChlorinatorStatus.ALERT_PRESENT.value & self.telemetry.status_raw == ChlorinatorStatus.ALERT_PRESENT.value
256+
257+
@property
258+
def has_error(self) -> bool:
259+
"""Check if any error conditions are present.
260+
261+
Returns:
262+
True if the ERROR_PRESENT status flag is set, False otherwise
263+
264+
See Also:
265+
error_messages: Get the list of specific error conditions
266+
"""
267+
return ChlorinatorStatus.ERROR_PRESENT.value & self.telemetry.status_raw == ChlorinatorStatus.ERROR_PRESENT.value
268+
269+
@property
270+
def is_authenticated(self) -> bool:
271+
"""Check if the T-Cell is authenticated.
272+
273+
An authenticated T-Cell is recognized by the system and can generate
274+
chlorine. Unauthenticated cells may be counterfeit or damaged.
275+
276+
Returns:
277+
True if the AUTHENTICATED status flag is set, False otherwise
278+
"""
279+
return ChlorinatorStatus.AUTHENTICATED.value & self.telemetry.status_raw == ChlorinatorStatus.AUTHENTICATED.value
280+
281+
@property
282+
def salt_level_status(self) -> str:
283+
"""Get a human-readable status of the salt level.
284+
285+
Returns:
286+
'OK' if salt level is adequate
287+
'LOW' if salt is low (add salt soon)
288+
'TOO_LOW' if salt is too low (add salt now)
289+
290+
Example:
291+
>>> status = chlorinator.salt_level_status
292+
>>> if status != 'OK':
293+
... print(f"Salt level is {status}: {chlorinator.avg_salt_level} ppm")
294+
"""
295+
alerts = self.alert_messages
296+
if "SALT_TOO_LOW" in alerts:
297+
return "TOO_LOW"
298+
if "SALT_LOW" in alerts:
299+
return "LOW"
300+
return "OK"
301+
302+
@property
303+
def is_ready(self) -> bool:
304+
"""Check if the chlorinator is ready to accept commands.
305+
306+
A chlorinator is considered ready if it is authenticated and has no
307+
critical errors that would prevent it from operating.
308+
309+
Returns:
310+
True if chlorinator can accept commands, False otherwise
311+
312+
Example:
313+
>>> if chlorinator.is_ready:
314+
... await chlorinator.set_chlorine_level(75)
315+
"""
316+
return self.is_authenticated and not self.has_error

0 commit comments

Comments
 (0)