python-omnilogic-local 0.20.8__tar.gz → 0.20.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/PKG-INFO +1 -1
  2. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/api/api.py +1 -1
  3. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/chlorinator.py +3 -3
  4. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/debug/commands.py +121 -1
  5. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/colorlogiclight.py +17 -7
  6. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/omnitypes.py +1 -1
  7. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/sensor.py +1 -1
  8. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyproject.toml +1 -1
  9. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/LICENSE +0 -0
  10. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/README.md +0 -0
  11. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/__init__.py +0 -0
  12. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/_base.py +0 -0
  13. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/api/__init__.py +0 -0
  14. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/api/constants.py +0 -0
  15. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/api/exceptions.py +0 -0
  16. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/api/protocol.py +0 -0
  17. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/backyard.py +0 -0
  18. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/bow.py +0 -0
  19. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/chlorinator_equip.py +0 -0
  20. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/__init__.py +0 -0
  21. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/cli.py +0 -0
  22. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/debug/__init__.py +0 -0
  23. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/__init__.py +0 -0
  24. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/backyard.py +0 -0
  25. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/bows.py +0 -0
  26. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/chlorinators.py +0 -0
  27. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/commands.py +0 -0
  28. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/csads.py +0 -0
  29. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/filters.py +0 -0
  30. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/groups.py +0 -0
  31. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/heaters.py +0 -0
  32. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/lights.py +0 -0
  33. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/pumps.py +0 -0
  34. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/relays.py +0 -0
  35. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/schedules.py +0 -0
  36. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/sensors.py +0 -0
  37. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/get/valves.py +0 -0
  38. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/pcap_utils.py +0 -0
  39. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/cli/utils.py +0 -0
  40. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/collections.py +0 -0
  41. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/csad.py +0 -0
  42. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/csad_equip.py +0 -0
  43. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/decorators.py +0 -0
  44. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/filter.py +0 -0
  45. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/groups.py +0 -0
  46. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/heater.py +0 -0
  47. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/heater_equip.py +0 -0
  48. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/__init__.py +0 -0
  49. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/const.py +0 -0
  50. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/exceptions.py +0 -0
  51. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/filter_diagnostics.py +0 -0
  52. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/leadmessage.py +0 -0
  53. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/mspconfig.py +0 -0
  54. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/models/telemetry.py +0 -0
  55. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/omnilogic.py +0 -0
  56. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/pump.py +0 -0
  57. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/py.typed +0 -0
  58. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/relay.py +0 -0
  59. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/schedule.py +0 -0
  60. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/system.py +0 -0
  61. {python_omnilogic_local-0.20.8 → python_omnilogic_local-0.20.10}/pyomnilogic_local/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-omnilogic-local
3
- Version: 0.20.8
3
+ Version: 0.20.10
4
4
  Summary: A library for local control of Hayward OmniHub/OmniLogic pool controllers using their local API
5
5
  Author: Chris Jowett, djtimca, garionphx
6
6
  Author-email: Chris Jowett <421501+cryptk@users.noreply.github.com>
@@ -550,7 +550,7 @@ class OmniLogicAPI:
550
550
  sc_timeout: int,
551
551
  bow_type: int,
552
552
  orp_timeout: int,
553
- cfg_state: int = 3,
553
+ cfg_state: int = 3, # 3 == on, 2 == off
554
554
  ) -> None:
555
555
  body_element = ET.Element("Request", {"xmlns": XML_NAMESPACE})
556
556
 
@@ -14,7 +14,7 @@ from pyomnilogic_local.util import OmniEquipmentNotInitializedError
14
14
  if TYPE_CHECKING:
15
15
  from pyomnilogic_local.models.telemetry import Telemetry
16
16
  from pyomnilogic_local.omnilogic import OmniLogic
17
- from pyomnilogic_local.omnitypes import ChlorinatorCellType, ChlorinatorOperatingMode
17
+ from pyomnilogic_local.omnitypes import ChlorinatorCellType, ChlorinatorDispenserType, ChlorinatorOperatingMode
18
18
 
19
19
 
20
20
  class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
@@ -83,7 +83,7 @@ class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
83
83
  return self.mspconfig.orp_timeout
84
84
 
85
85
  @property
86
- def dispenser_type(self) -> str:
86
+ def dispenser_type(self) -> ChlorinatorDispenserType:
87
87
  """Type of chlorine dispenser (SALT, LIQUID, or TABLET)."""
88
88
  return self.mspconfig.dispenser_type
89
89
 
@@ -99,7 +99,7 @@ class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
99
99
  return self.telemetry.operating_state
100
100
 
101
101
  @property
102
- def operating_mode(self) -> ChlorinatorOperatingMode | int:
102
+ def operating_mode(self) -> ChlorinatorOperatingMode:
103
103
  """Current operating mode (DISABLED, TIMED, ORP_AUTO, or ORP_TIMED_RW).
104
104
 
105
105
  Returns:
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import asyncio
7
7
  from pathlib import Path
8
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, cast
9
9
 
10
10
  import click
11
11
 
@@ -15,6 +15,7 @@ from pyomnilogic_local.cli.utils import async_get_filter_diagnostics
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from pyomnilogic_local.api.api import OmniLogicAPI
18
+ from pyomnilogic_local.models.telemetry import TelemetryChlorinator
18
19
 
19
20
 
20
21
  @click.group()
@@ -209,3 +210,122 @@ def set_equipment(ctx: click.Context, bow_id: int, equip_id: int, is_on: str) ->
209
210
  except Exception as e:
210
211
  click.echo(f"Error setting equipment: {e}", err=True)
211
212
  raise click.Abort from e
213
+
214
+
215
+ @debug.command()
216
+ @click.argument("bow_id", type=int)
217
+ @click.argument("equip_id", type=int)
218
+ @click.argument("timed_percent", type=int)
219
+ @click.argument("op_mode", type=int)
220
+ @click.pass_context
221
+ def set_chlor_params(ctx: click.Context, bow_id: int, equip_id: int, timed_percent: int, op_mode: int) -> None:
222
+ """Set chlorinator parameters with explicit control over configuration.
223
+
224
+ This command sets chlorinator parameters using the current chlorinator's
225
+ configuration for cell_type, sc_timeout, bow_type, and orp_timeout, while
226
+ allowing you to specify timed_percent and op_mode. The cfg_state is derived
227
+ from the chlorinator's current on/off state.
228
+
229
+ BOW_ID: The Body of Water (pool/spa) system ID
230
+ EQUIP_ID: The chlorinator equipment system ID
231
+ TIMED_PERCENT: Chlorine generation percentage (0-100)
232
+ OP_MODE: Operating mode (0=DISABLED, 1=TIMED, 2=ORP_AUTO, 3=ORP_TIMED_RW)
233
+
234
+ Examples:
235
+ # Set chlorinator to 75% in TIMED mode
236
+ omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 75 1
237
+
238
+ # Disable chlorinator
239
+ omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 0 0
240
+
241
+ # Set to ORP AUTO mode with 50% generation
242
+ omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 50 2
243
+
244
+ """
245
+ ensure_connection(ctx)
246
+ omni: OmniLogicAPI = ctx.obj["OMNI"]
247
+
248
+ # Validate timed_percent
249
+ if not 0 <= timed_percent <= 100:
250
+ click.echo(f"Error: timed_percent must be between 0-100, got {timed_percent}", err=True)
251
+ raise click.Abort
252
+
253
+ # Validate op_mode
254
+ if not 0 <= op_mode <= 2:
255
+ click.echo(f"Error: op_mode must be between 0-3, got {op_mode}", err=True)
256
+ raise click.Abort
257
+
258
+ # Get MSPConfig and Telemetry to find the chlorinator
259
+ try:
260
+ mspconfig_raw = asyncio.run(omni.async_get_mspconfig(raw=False))
261
+ telemetry_raw = asyncio.run(omni.async_get_telemetry(raw=False))
262
+ except Exception as e:
263
+ click.echo(f"Error retrieving configuration: {e}", err=True)
264
+ raise click.Abort from e
265
+
266
+ # Find the BOW
267
+ bow = None
268
+ if mspconfig_raw.backyard.bow:
269
+ for candidate_bow in mspconfig_raw.backyard.bow:
270
+ if candidate_bow.system_id == bow_id:
271
+ bow = candidate_bow
272
+ break
273
+
274
+ if bow is None:
275
+ click.echo(f"Error: Body of Water with ID {bow_id} not found", err=True)
276
+ raise click.Abort
277
+
278
+ # Find the chlorinator
279
+ if bow.chlorinator is None or bow.chlorinator.system_id != equip_id:
280
+ click.echo(f"Error: Chlorinator with ID {equip_id} not found in BOW {bow_id}", err=True)
281
+ raise click.Abort
282
+
283
+ chlorinator = bow.chlorinator
284
+
285
+ # Get telemetry for the chlorinator to determine is_on state
286
+ chlorinator_telemetry = telemetry_raw.get_telem_by_systemid(equip_id)
287
+ if chlorinator_telemetry is None:
288
+ click.echo(f"Warning: No telemetry found for chlorinator {equip_id}, defaulting cfg_state to 3 (on)", err=True)
289
+ cfg_state = 3
290
+ else:
291
+ # Cast to TelemetryChlorinator to access enable attribute
292
+ chlorinator_telem = cast("TelemetryChlorinator", chlorinator_telemetry)
293
+ # Determine cfg_state from enable flag in telemetry
294
+ cfg_state = 3 if chlorinator_telem.enable else 2
295
+
296
+ # Determine bow_type from equipment type (0=pool, 1=spa)
297
+ bow_type = 0 if bow.equip_type == "BOW_POOL" else 1
298
+
299
+ # Get parameters from chlorinator config
300
+ cell_type = chlorinator.cell_type.value
301
+ sc_timeout = chlorinator.superchlor_timeout
302
+ orp_timeout = chlorinator.orp_timeout
303
+
304
+ # Execute the command
305
+ try:
306
+ asyncio.run(
307
+ omni.async_set_chlorinator_params(
308
+ pool_id=bow_id,
309
+ equipment_id=equip_id,
310
+ timed_percent=timed_percent,
311
+ cell_type=cell_type,
312
+ op_mode=op_mode,
313
+ sc_timeout=sc_timeout,
314
+ bow_type=bow_type,
315
+ orp_timeout=orp_timeout,
316
+ cfg_state=cfg_state,
317
+ )
318
+ )
319
+ click.echo(
320
+ f"Sent command to chlorinator {equip_id} in BOW {bow_id}:\n"
321
+ f" Timed Percent: {timed_percent}%\n"
322
+ f" Operating Mode: {op_mode}\n"
323
+ f" Config State: {cfg_state} ({'on' if cfg_state == 3 else 'off'})\n"
324
+ f" Cell Type: {cell_type}\n"
325
+ f" SC Timeout: {sc_timeout}\n"
326
+ f" BOW Type: {bow_type}\n"
327
+ f" ORP Timeout: {orp_timeout}"
328
+ )
329
+ except Exception as e:
330
+ click.echo(f"Error setting chlorinator parameters: {e}", err=True)
331
+ raise click.Abort from e
@@ -117,7 +117,7 @@ class ColorLogicLight(OmniEquipment[MSPColorLogicLight, TelemetryColorLogicLight
117
117
  super().__init__(omni, mspconfig, telemetry)
118
118
 
119
119
  @property
120
- def model(self) -> ColorLogicLightType:
120
+ def equip_type(self) -> ColorLogicLightType:
121
121
  """Returns the model of the light."""
122
122
  return self.mspconfig.equip_type
123
123
 
@@ -168,12 +168,17 @@ class ColorLogicLight(OmniEquipment[MSPColorLogicLight, TelemetryColorLogicLight
168
168
  @property
169
169
  def show(self) -> LightShows:
170
170
  """Returns the current light show."""
171
- return self.telemetry.show_name(self.model, self.v2_active)
171
+ return self.telemetry.show_name(self.equip_type, self.v2_active)
172
172
 
173
173
  @property
174
174
  def speed(self) -> ColorLogicSpeed:
175
175
  """Returns the current speed."""
176
- if self.model in [ColorLogicLightType.SAM, ColorLogicLightType.TWO_FIVE, ColorLogicLightType.FOUR_ZERO, ColorLogicLightType.UCL]:
176
+ if self.equip_type in [
177
+ ColorLogicLightType.SAM,
178
+ ColorLogicLightType.TWO_FIVE,
179
+ ColorLogicLightType.FOUR_ZERO,
180
+ ColorLogicLightType.UCL,
181
+ ]:
177
182
  return self.telemetry.speed
178
183
  # Non color-logic lights only support 1x speed
179
184
  return ColorLogicSpeed.ONE_TIMES
@@ -181,7 +186,12 @@ class ColorLogicLight(OmniEquipment[MSPColorLogicLight, TelemetryColorLogicLight
181
186
  @property
182
187
  def brightness(self) -> ColorLogicBrightness:
183
188
  """Returns the current brightness."""
184
- if self.model in [ColorLogicLightType.SAM, ColorLogicLightType.TWO_FIVE, ColorLogicLightType.FOUR_ZERO, ColorLogicLightType.UCL]:
189
+ if self.equip_type in [
190
+ ColorLogicLightType.SAM,
191
+ ColorLogicLightType.TWO_FIVE,
192
+ ColorLogicLightType.FOUR_ZERO,
193
+ ColorLogicLightType.UCL,
194
+ ]:
185
195
  return self.telemetry.brightness
186
196
  # Non color-logic lights only support 100% brightness
187
197
  return ColorLogicBrightness.ONE_HUNDRED_PERCENT
@@ -262,17 +272,17 @@ class ColorLogicLight(OmniEquipment[MSPColorLogicLight, TelemetryColorLogicLight
262
272
  and a warning will be logged.
263
273
  """
264
274
  # Non color-logic lights do not support speed or brightness control
265
- if self.model not in [
275
+ if self.equip_type not in [
266
276
  ColorLogicLightType.SAM,
267
277
  ColorLogicLightType.TWO_FIVE,
268
278
  ColorLogicLightType.FOUR_ZERO,
269
279
  ColorLogicLightType.UCL,
270
280
  ]:
271
281
  if speed is not None:
272
- _LOGGER.warning("Non colorlogic lights do not support speed control %s", self.model.name)
282
+ _LOGGER.warning("Non colorlogic lights do not support speed control %s", self.equip_type.name)
273
283
  speed = ColorLogicSpeed.ONE_TIMES
274
284
  if brightness is not None:
275
- _LOGGER.warning("Non colorlogic lights do not support brightness control %s", self.model.name)
285
+ _LOGGER.warning("Non colorlogic lights do not support brightness control %s", self.equip_type.name)
276
286
  brightness = ColorLogicBrightness.ONE_HUNDRED_PERCENT
277
287
 
278
288
  if self.bow_id is None or self.system_id is None:
@@ -150,7 +150,7 @@ class ChlorinatorOperatingMode(IntEnum, PrettyEnum):
150
150
  DISABLED = 0
151
151
  TIMED = 1
152
152
  ORP_AUTO = 2
153
- ORP_TIMED_RW = 3 # Chlorinator in ORP mode experienced condition that prevents ORP operation
153
+ ORP_TIMED_RW = 3 # Chlorinator in ORP mode experienced CSAD condition that prevents ORP operation
154
154
 
155
155
 
156
156
  class ChlorinatorType(StrEnum, PrettyEnum):
@@ -86,7 +86,7 @@ class Sensor(OmniEquipment[MSPSensor, None]):
86
86
  super().__init__(omni, mspconfig, telemetry)
87
87
 
88
88
  @property
89
- def sensor_type(self) -> SensorType | str:
89
+ def equip_type(self) -> SensorType | str:
90
90
  """Returns the type of sensor.
91
91
 
92
92
  Can be AIR_TEMP, SOLAR_TEMP, WATER_TEMP, FLOW, ORP, or EXT_INPUT.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-omnilogic-local"
3
- version = "0.20.8"
3
+ version = "0.20.10"
4
4
  description = "A library for local control of Hayward OmniHub/OmniLogic pool controllers using their local API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13,<4.0.0"