|
1 | 1 | from pyomnilogic_local._base import OmniEquipment |
2 | 2 | from pyomnilogic_local.models.mspconfig import MSPChlorinator |
3 | 3 | from pyomnilogic_local.models.telemetry import TelemetryChlorinator |
| 4 | +from pyomnilogic_local.omnitypes import ChlorinatorOperatingMode, ChlorinatorStatus |
4 | 5 |
|
5 | 6 |
|
6 | 7 | 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 | + """ |
8 | 26 |
|
9 | 27 | mspconfig: MSPChlorinator |
10 | 28 | 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