python-omnilogic-local 0.23.0__tar.gz → 0.25.0__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 (62) hide show
  1. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/PKG-INFO +2 -2
  2. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/api.py +45 -0
  3. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/bow.py +1 -1
  4. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/backyard.py +1 -1
  5. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/bows.py +1 -1
  6. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/csads.py +2 -2
  7. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/filters.py +4 -4
  8. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/groups.py +1 -1
  9. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/heaters.py +4 -4
  10. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/lights.py +4 -4
  11. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/pumps.py +3 -3
  12. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/relays.py +4 -4
  13. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/sensors.py +2 -2
  14. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/valves.py +4 -4
  15. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/csad.py +2 -2
  16. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/filter.py +9 -4
  17. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/heater.py +5 -2
  18. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/heater_equip.py +1 -1
  19. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/mspconfig.py +1 -3
  20. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/omnitypes.py +46 -44
  21. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/pump.py +12 -4
  22. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/sensor.py +2 -2
  23. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/system.py +3 -3
  24. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/util.py +9 -3
  25. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyproject.toml +2 -2
  26. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/LICENSE +0 -0
  27. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/README.md +0 -0
  28. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/__init__.py +0 -0
  29. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/_base.py +0 -0
  30. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/__init__.py +0 -0
  31. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/constants.py +0 -0
  32. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/exceptions.py +0 -0
  33. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/mock_api.py +0 -0
  34. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/api/protocol.py +0 -0
  35. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/backyard.py +0 -0
  36. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/chlorinator.py +0 -0
  37. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/chlorinator_equip.py +0 -0
  38. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/__init__.py +0 -0
  39. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/cli.py +0 -0
  40. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/debug/__init__.py +0 -0
  41. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/debug/commands.py +0 -0
  42. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/__init__.py +0 -0
  43. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/chlorinators.py +0 -0
  44. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/commands.py +0 -0
  45. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/get/schedules.py +0 -0
  46. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/pcap_utils.py +0 -0
  47. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/cli/utils.py +0 -0
  48. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/collections.py +0 -0
  49. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/colorlogiclight.py +0 -0
  50. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/csad_equip.py +0 -0
  51. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/decorators.py +0 -0
  52. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/groups.py +0 -0
  53. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/__init__.py +0 -0
  54. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/const.py +0 -0
  55. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/exceptions.py +0 -0
  56. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/filter_diagnostics.py +0 -0
  57. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/leadmessage.py +0 -0
  58. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/models/telemetry.py +0 -0
  59. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/omnilogic.py +0 -0
  60. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/py.typed +0 -0
  61. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/relay.py +0 -0
  62. {python_omnilogic_local-0.23.0 → python_omnilogic_local-0.25.0}/pyomnilogic_local/schedule.py +0 -0
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-omnilogic-local
3
- Version: 0.23.0
3
+ Version: 0.25.0
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>
7
7
  License-File: LICENSE
8
8
  Requires-Dist: pydantic>=2.0.0,<3.0.0
9
- Requires-Dist: click>=8.0.0,<9.0.0
10
9
  Requires-Dist: xmltodict>=1.0.1,<2.0.0
11
10
  Requires-Dist: uv~=0.11.6 ; extra == 'build'
12
11
  Requires-Dist: scapy>=2.6.1,<3.0.0 ; extra == 'cli'
12
+ Requires-Dist: click>=8.0.0,<9.0.0 ; extra == 'cli'
13
13
  Requires-Python: >=3.14.2
14
14
  Provides-Extra: build
15
15
  Provides-Extra: cli
@@ -168,8 +168,12 @@ class OmniLogicAPI:
168
168
 
169
169
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
170
170
 
171
+ _LOGGER.debug("Sending RequestConfiguration with body: %s", req_body)
172
+
171
173
  resp = await self.async_send_message(MessageType.REQUEST_CONFIGURATION, req_body, True)
172
174
 
175
+ _LOGGER.debug("Received response for RequestConfiguration: %s", resp)
176
+
173
177
  if raw:
174
178
  return resp
175
179
  return MSPConfig.load_xml(resp)
@@ -206,8 +210,12 @@ class OmniLogicAPI:
206
210
 
207
211
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
208
212
 
213
+ _LOGGER.debug("Sending GetUIFilterDiagnosticInfo with body: %s", req_body)
214
+
209
215
  resp = await self.async_send_message(MessageType.GET_FILTER_DIAGNOSTIC_INFO, req_body, True)
210
216
 
217
+ _LOGGER.debug("Received response for GetUIFilterDiagnosticInfo: %s", resp)
218
+
211
219
  if raw:
212
220
  return resp
213
221
  return FilterDiagnostics.load_xml(resp)
@@ -231,8 +239,12 @@ class OmniLogicAPI:
231
239
 
232
240
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
233
241
 
242
+ _LOGGER.debug("Sending RequestTelemetryData with body: %s", req_body)
243
+
234
244
  resp = await self.async_send_message(MessageType.GET_TELEMETRY, req_body, True)
235
245
 
246
+ _LOGGER.debug("Received response for RequestTelemetryData: %s", resp)
247
+
236
248
  if raw:
237
249
  return resp
238
250
  return Telemetry.load_xml(resp)
@@ -268,6 +280,8 @@ class OmniLogicAPI:
268
280
 
269
281
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
270
282
 
283
+ _LOGGER.debug("Sending SetUIHeaterCmd with body: %s", req_body)
284
+
271
285
  return await self.async_send_message(MessageType.SET_HEATER_COMMAND, req_body, False)
272
286
 
273
287
  async def async_set_solar_heater(
@@ -301,6 +315,8 @@ class OmniLogicAPI:
301
315
 
302
316
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
303
317
 
318
+ _LOGGER.debug("Sending SetUISolarSetPointCmd with body: %s", req_body)
319
+
304
320
  return await self.async_send_message(MessageType.SET_SOLAR_SET_POINT_COMMAND, req_body, False)
305
321
 
306
322
  async def async_set_heater_mode(
@@ -334,6 +350,8 @@ class OmniLogicAPI:
334
350
 
335
351
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
336
352
 
353
+ _LOGGER.debug("Sending SetUIHeaterModeCmd with body: %s", req_body)
354
+
337
355
  return await self.async_send_message(MessageType.SET_HEATER_MODE_COMMAND, req_body, False)
338
356
 
339
357
  async def async_set_heater_enable(
@@ -367,6 +385,8 @@ class OmniLogicAPI:
367
385
 
368
386
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
369
387
 
388
+ _LOGGER.debug("Sending SetHeaterEnable with body: %s", req_body)
389
+
370
390
  return await self.async_send_message(MessageType.SET_HEATER_ENABLED, req_body, False)
371
391
 
372
392
  async def async_set_equipment(
@@ -427,6 +447,8 @@ class OmniLogicAPI:
427
447
 
428
448
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
429
449
 
450
+ _LOGGER.debug("Sending SetUIEquipmentCmd with body: %s", req_body)
451
+
430
452
  return await self.async_send_message(MessageType.SET_EQUIPMENT, req_body, False)
431
453
 
432
454
  async def async_set_filter_speed(self, pool_id: int, equipment_id: int, speed: int) -> None:
@@ -453,6 +475,8 @@ class OmniLogicAPI:
453
475
 
454
476
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
455
477
 
478
+ _LOGGER.debug("Sending SetUIFilterSpeedCmd with body: %s", req_body)
479
+
456
480
  return await self.async_send_message(MessageType.SET_FILTER_SPEED, req_body, False)
457
481
 
458
482
  async def async_set_light_show(
@@ -522,6 +546,9 @@ class OmniLogicAPI:
522
546
  parameter.text = str(int(recurring))
523
547
 
524
548
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
549
+
550
+ _LOGGER.debug("Sending SetStandAloneLightShow with body: %s", req_body)
551
+
525
552
  return await self.async_send_message(MessageType.SET_STANDALONE_LIGHT_SHOW, req_body, False)
526
553
 
527
554
  async def async_set_chlorinator_enable(self, pool_id: int, enabled: int | bool) -> None:
@@ -538,6 +565,8 @@ class OmniLogicAPI:
538
565
 
539
566
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
540
567
 
568
+ _LOGGER.debug("Sending SetCHLOREnable with body: %s", req_body)
569
+
541
570
  return await self.async_send_message(MessageType.SET_CHLOR_ENABLED, req_body, False)
542
571
 
543
572
  # This is used to set the ORP target value on a CSAD
@@ -562,6 +591,8 @@ class OmniLogicAPI:
562
591
 
563
592
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
564
593
 
594
+ _LOGGER.debug("Sending SetUICSADORPTargetLevel with body: %s", req_body)
595
+
565
596
  return await self.async_send_message(MessageType.SET_CSAD_ORP_TARGET, req_body, False)
566
597
 
567
598
  # This is used to set the pH target value on a CSAD
@@ -586,6 +617,8 @@ class OmniLogicAPI:
586
617
 
587
618
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
588
619
 
620
+ _LOGGER.debug("Sending UISetCSADTargetValue with body: %s", req_body)
621
+
589
622
  return await self.async_send_message(MessageType.SET_CSAD_TARGET_VALUE, req_body, False)
590
623
 
591
624
  async def async_set_chlorinator_params(
@@ -627,6 +660,8 @@ class OmniLogicAPI:
627
660
 
628
661
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
629
662
 
663
+ _LOGGER.debug("Sending SetCHLORParams with body: %s", req_body)
664
+
630
665
  return await self.async_send_message(MessageType.SET_CHLOR_PARAMS, req_body, False)
631
666
 
632
667
  async def async_set_chlorinator_superchlorinate(
@@ -650,6 +685,8 @@ class OmniLogicAPI:
650
685
 
651
686
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
652
687
 
688
+ _LOGGER.debug("Sending SetUISuperCHLORCmd with body: %s", req_body)
689
+
653
690
  return await self.async_send_message(MessageType.SET_SUPERCHLORINATE, req_body, False)
654
691
 
655
692
  async def async_restore_idle_state(self) -> None:
@@ -662,6 +699,8 @@ class OmniLogicAPI:
662
699
 
663
700
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
664
701
 
702
+ _LOGGER.debug("Sending RestoreIdleState with body: %s", req_body)
703
+
665
704
  return await self.async_send_message(MessageType.RESTORE_IDLE_STATE, req_body, False)
666
705
 
667
706
  async def async_set_spillover(
@@ -703,6 +742,8 @@ class OmniLogicAPI:
703
742
 
704
743
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
705
744
 
745
+ _LOGGER.debug("Sending SetUISpilloverCmd with body: %s", req_body)
746
+
706
747
  return await self.async_send_message(MessageType.SET_SPILLOVER, req_body, False)
707
748
 
708
749
  async def async_set_group_enable(
@@ -744,6 +785,8 @@ class OmniLogicAPI:
744
785
 
745
786
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
746
787
 
788
+ _LOGGER.debug("Sending RunGroupCmd with body: %s", req_body)
789
+
747
790
  return await self.async_send_message(MessageType.RUN_GROUP_CMD, req_body, False)
748
791
 
749
792
  async def async_edit_schedule(
@@ -815,4 +858,6 @@ class OmniLogicAPI:
815
858
 
816
859
  req_body = ET.tostring(body_element, xml_declaration=True, encoding=XML_ENCODING)
817
860
 
861
+ _LOGGER.debug("Sending EditUIScheduleCmd with body: %s", req_body)
862
+
818
863
  return await self.async_send_message(MessageType.EDIT_SCHEDULE, req_body, False)
@@ -170,7 +170,7 @@ class Bow(OmniEquipment[MSPBoW, TelemetryBoW]):
170
170
  return f"Bow({', '.join(parts)})"
171
171
 
172
172
  @property
173
- def equip_type(self) -> BodyOfWaterType | str:
173
+ def equip_type(self) -> BodyOfWaterType:
174
174
  """The equipment type of the bow (POOL or SPA)."""
175
175
  return self.mspconfig.equip_type
176
176
 
@@ -54,7 +54,7 @@ def _print_backyard_info(backyardconfig: MSPBackyard, telemetry: TelemetryType |
54
54
  continue
55
55
 
56
56
  if attr_name == "state":
57
- value = BackyardState(value).pretty()
57
+ value = str(BackyardState(value))
58
58
  elif isinstance(value, list):
59
59
  # Format lists nicely
60
60
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -58,7 +58,7 @@ def _print_bow_info(bow: MSPBoW, telemetry: TelemetryType | None) -> None:
58
58
  continue
59
59
 
60
60
  if attr_name == "type":
61
- value = BodyOfWaterType(value).pretty()
61
+ value = str(BodyOfWaterType(value))
62
62
  elif isinstance(value, list):
63
63
  # Format lists nicely
64
64
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -50,9 +50,9 @@ def _print_csad_info(csad: MSPCSAD, telemetry: TelemetryCSAD | None) -> None:
50
50
  csad_data: dict[Any, Any] = {**dict(csad), **dict(telemetry)} if telemetry else dict(csad)
51
51
  for attr_name, value in csad_data.items():
52
52
  if attr_name == "equip_type":
53
- value = CSADType(value).pretty()
53
+ value = str(CSADType(value))
54
54
  elif attr_name == "mode":
55
- value = CSADMode(value).pretty()
55
+ value = str(CSADMode(value))
56
56
  elif isinstance(value, list):
57
57
  # Format lists nicely
58
58
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -50,13 +50,13 @@ def _print_filter_info(filt: MSPFilter, telemetry: TelemetryType | None) -> None
50
50
  filter_data: dict[Any, Any] = {**dict(filt), **dict(telemetry)} if telemetry else dict(filt)
51
51
  for attr_name, value in filter_data.items():
52
52
  if attr_name == "state":
53
- value = FilterState(value).pretty()
53
+ value = str(FilterState(value))
54
54
  elif attr_name == "type":
55
- value = FilterType(value).pretty()
55
+ value = str(FilterType(value))
56
56
  elif attr_name == "valve_position":
57
- value = FilterValvePosition(value).pretty()
57
+ value = str(FilterValvePosition(value))
58
58
  elif attr_name == "why_on":
59
- value = FilterWhyOn(value).pretty()
59
+ value = str(FilterWhyOn(value))
60
60
  elif isinstance(value, list):
61
61
  # Format lists nicely
62
62
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -53,7 +53,7 @@ def _print_group_info(group: MSPGroup, telemetry: TelemetryGroup | None) -> None
53
53
  # Skip bow_id as it's not relevant for groups
54
54
  continue
55
55
  if attr_name == "state":
56
- value = GroupState(value).pretty()
56
+ value = str(GroupState(value))
57
57
  elif isinstance(value, list):
58
58
  # Format lists nicely
59
59
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -67,9 +67,9 @@ def _print_virtual_heater_info(virt_heater: MSPVirtualHeater, telemetry: Telemet
67
67
 
68
68
  for attr_name, value in display_data.items():
69
69
  if attr_name == "state":
70
- value = HeaterState(value).pretty()
70
+ value = str(HeaterState(value))
71
71
  elif attr_name == "mode":
72
- value = HeaterMode(value).pretty()
72
+ value = str(HeaterMode(value))
73
73
  elif isinstance(value, list):
74
74
  # Format lists nicely
75
75
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -105,9 +105,9 @@ def _print_heater_equipment_info(equip: MSPHeaterEquip, telemetry: Telemetry) ->
105
105
 
106
106
  for attr_name, value in equip_data.items():
107
107
  if attr_name == "heater_type":
108
- value = HeaterType(value).pretty()
108
+ value = str(HeaterType(value))
109
109
  elif attr_name == "state":
110
- value = HeaterState(value).pretty()
110
+ value = str(HeaterState(value))
111
111
  elif isinstance(value, list):
112
112
  value = ", ".join(str(v) for v in value) if value else "None"
113
113
 
@@ -51,16 +51,16 @@ def _print_light_info(light: MSPColorLogicLight, telemetry: TelemetryColorLogicL
51
51
 
52
52
  for attr_name, value in light_data.items():
53
53
  if attr_name == "brightness":
54
- value = ColorLogicBrightness(value).pretty()
54
+ value = str(ColorLogicBrightness(value))
55
55
  elif attr_name == "effects" and isinstance(value, list):
56
- show_names = [show.pretty() if hasattr(show, "pretty") else str(show) for show in value]
56
+ show_names = [str(show) for show in value]
57
57
  value = ", ".join(show_names) if show_names else "None"
58
58
  elif attr_name == "show" and value is not None:
59
59
  value = telemetry.show_name(light.equip_type, light.v2_active) if telemetry else str(value)
60
60
  elif attr_name == "speed":
61
- value = ColorLogicSpeed(value).pretty()
61
+ value = str(ColorLogicSpeed(value))
62
62
  elif attr_name == "state":
63
- value = ColorLogicPowerState(value).pretty()
63
+ value = str(ColorLogicPowerState(value))
64
64
  elif isinstance(value, list):
65
65
  # Format other lists nicely
66
66
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -50,11 +50,11 @@ def _print_pump_info(pump: MSPPump, telemetry: TelemetryType | None) -> None:
50
50
  pump_data: dict[Any, Any] = {**dict(pump), **dict(telemetry)} if telemetry else dict(pump)
51
51
  for attr_name, value in pump_data.items():
52
52
  if attr_name == "state":
53
- value = PumpState(value).pretty()
53
+ value = str(PumpState(value))
54
54
  elif attr_name == "equip_type":
55
- value = PumpType(value).pretty()
55
+ value = str(PumpType(value))
56
56
  elif attr_name == "function":
57
- value = PumpFunction(value).pretty()
57
+ value = str(PumpFunction(value))
58
58
  elif isinstance(value, list):
59
59
  # Format lists nicely
60
60
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -50,13 +50,13 @@ def _print_relay_info(relay: MSPRelay, telemetry: TelemetryType | None) -> None:
50
50
  relay_data: dict[Any, Any] = {**dict(relay), **dict(telemetry)} if telemetry else dict(relay)
51
51
  for attr_name, value in relay_data.items():
52
52
  if attr_name == "state":
53
- value = RelayState(value).pretty()
53
+ value = str(RelayState(value))
54
54
  elif attr_name == "type":
55
- value = RelayType(value).pretty()
55
+ value = str(RelayType(value))
56
56
  elif attr_name == "function":
57
- value = RelayFunction(value).pretty()
57
+ value = str(RelayFunction(value))
58
58
  elif attr_name == "why_on":
59
- value = RelayWhyOn(value).pretty()
59
+ value = str(RelayWhyOn(value))
60
60
  elif isinstance(value, list):
61
61
  # Format lists nicely
62
62
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -48,9 +48,9 @@ def _print_sensor_info(sensor: MSPSensor) -> None:
48
48
  sensor_data: dict[Any, Any] = dict(sensor)
49
49
  for attr_name, value in sensor_data.items():
50
50
  if attr_name == "equip_type":
51
- value = SensorType(value).pretty()
51
+ value = str(SensorType(value))
52
52
  elif attr_name == "units":
53
- value = SensorUnits(value).pretty()
53
+ value = str(SensorUnits(value))
54
54
  elif isinstance(value, list):
55
55
  # Format lists nicely
56
56
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -60,13 +60,13 @@ def _print_valve_info(relay: MSPRelay, telemetry: Telemetry) -> None:
60
60
 
61
61
  for attr_name, value in valve_data.items():
62
62
  if attr_name == "type":
63
- value = RelayType(value).pretty()
63
+ value = str(RelayType(value))
64
64
  elif attr_name == "function":
65
- value = RelayFunction(value).pretty()
65
+ value = str(RelayFunction(value))
66
66
  elif attr_name == "state":
67
- value = ValveActuatorState(value).pretty()
67
+ value = str(ValveActuatorState(value))
68
68
  elif attr_name == "why_on":
69
- value = RelayWhyOn(value).pretty()
69
+ value = str(RelayWhyOn(value))
70
70
  elif isinstance(value, list):
71
71
  # Format lists nicely
72
72
  value = ", ".join(str(v) for v in value) if value else "None"
@@ -69,7 +69,7 @@ class CSAD(OmniEquipment[MSPCSAD, TelemetryCSAD]):
69
69
  return self.mspconfig.enabled
70
70
 
71
71
  @property
72
- def equip_type(self) -> CSADType | str:
72
+ def equip_type(self) -> CSADType:
73
73
  """Type of CSAD system (ACID or CO2)."""
74
74
  return self.mspconfig.equip_type
75
75
 
@@ -160,7 +160,7 @@ class CSAD(OmniEquipment[MSPCSAD, TelemetryCSAD]):
160
160
  return self.telemetry.orp
161
161
 
162
162
  @property
163
- def mode(self) -> CSADMode | int:
163
+ def mode(self) -> CSADMode:
164
164
  """Current operating mode of the CSAD.
165
165
 
166
166
  Returns:
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from pyomnilogic_local._base import OmniEquipment
4
6
  from pyomnilogic_local.decorators import control_method
5
7
  from pyomnilogic_local.models.mspconfig import MSPFilter
@@ -7,6 +9,9 @@ from pyomnilogic_local.models.telemetry import TelemetryFilter
7
9
  from pyomnilogic_local.omnitypes import FilterSpeedPresets, FilterState
8
10
  from pyomnilogic_local.util import OmniEquipmentNotInitializedError
9
11
 
12
+ if TYPE_CHECKING:
13
+ from pyomnilogic_local.omnitypes import FilterType, FilterValvePosition, FilterWhyOn
14
+
10
15
 
11
16
  class Filter(OmniEquipment[MSPFilter, TelemetryFilter]):
12
17
  """Represents a pool/spa filtration pump in the OmniLogic system.
@@ -81,7 +86,7 @@ class Filter(OmniEquipment[MSPFilter, TelemetryFilter]):
81
86
 
82
87
  # Expose MSPConfig attributes
83
88
  @property
84
- def equip_type(self) -> str:
89
+ def equip_type(self) -> FilterType:
85
90
  """The filter type (e.g., FMT_VARIABLE_SPEED_PUMP)."""
86
91
  return self.mspconfig.equip_type
87
92
 
@@ -127,7 +132,7 @@ class Filter(OmniEquipment[MSPFilter, TelemetryFilter]):
127
132
 
128
133
  # Expose Telemetry attributes
129
134
  @property
130
- def state(self) -> FilterState | int:
135
+ def state(self) -> FilterState:
131
136
  """Current filter state."""
132
137
  return self.telemetry.state
133
138
 
@@ -137,12 +142,12 @@ class Filter(OmniEquipment[MSPFilter, TelemetryFilter]):
137
142
  return self.telemetry.speed
138
143
 
139
144
  @property
140
- def valve_position(self) -> int:
145
+ def valve_position(self) -> FilterValvePosition:
141
146
  """Current valve position."""
142
147
  return self.telemetry.valve_position
143
148
 
144
149
  @property
145
- def why_on(self) -> int:
150
+ def why_on(self) -> FilterWhyOn:
146
151
  """Reason why the filter is on."""
147
152
  return self.telemetry.why_on
148
153
 
@@ -140,7 +140,7 @@ class Heater(OmniEquipment[MSPVirtualHeater, TelemetryVirtualHeater]):
140
140
  return self.mspconfig.min_temp
141
141
 
142
142
  @property
143
- def mode(self) -> HeaterMode | int:
143
+ def mode(self) -> HeaterMode:
144
144
  """Returns the current heater mode from telemetry."""
145
145
  return self.telemetry.mode
146
146
 
@@ -174,7 +174,10 @@ class Heater(OmniEquipment[MSPVirtualHeater, TelemetryVirtualHeater]):
174
174
 
175
175
  @property
176
176
  def why_on(self) -> int:
177
- """Returns the reason why the heater is on from telemetry."""
177
+ """Returns the reason why the heater is on from telemetry.
178
+
179
+ We don't have a good understanding of what these values mean yet
180
+ """
178
181
  return self.telemetry.why_on
179
182
 
180
183
  @property
@@ -110,7 +110,7 @@ class HeaterEquipment(OmniEquipment[MSPHeaterEquip, TelemetryHeater]):
110
110
  return self.mspconfig.supports_cooling
111
111
 
112
112
  @property
113
- def state(self) -> HeaterState | int:
113
+ def state(self) -> HeaterState:
114
114
  """Returns the current state of the heater equipment (OFF, ON, or PAUSE)."""
115
115
  return self.telemetry.state
116
116
 
@@ -59,9 +59,7 @@ class OmniBase(BaseModel):
59
59
 
60
60
  def without_subdevices(self) -> Self:
61
61
  data = self.model_dump(exclude=self._sub_devices, round_trip=True, by_alias=True)
62
- copied = self.model_validate(data)
63
- _LOGGER.debug("without_subdevices: original=%s, copied=%s", self, copied)
64
- return copied
62
+ return self.model_validate(data)
65
63
 
66
64
  def propagate_bow_id(self, bow_id: int) -> None:
67
65
  # First we set our own bow_id
@@ -6,7 +6,7 @@ from .util import PrettyEnum
6
6
 
7
7
 
8
8
  # OmniAPI Enums
9
- class MessageType(IntEnum, PrettyEnum):
9
+ class MessageType(PrettyEnum, IntEnum):
10
10
  XML_ACK = 0000
11
11
  REQUEST_CONFIGURATION = 1
12
12
  SET_FILTER_SPEED = 9
@@ -39,7 +39,7 @@ class MessageType(IntEnum, PrettyEnum):
39
39
  MSP_BLOCKMESSAGE = 1999
40
40
 
41
41
 
42
- class ClientType(IntEnum, PrettyEnum):
42
+ class ClientType(PrettyEnum, IntEnum):
43
43
  XML = 0
44
44
  SIMPLE = 1
45
45
  OMNI = 3
@@ -71,7 +71,7 @@ class OmniType(StrEnum):
71
71
 
72
72
 
73
73
  # Backyard/BoW
74
- class BackyardState(IntEnum, PrettyEnum):
74
+ class BackyardState(PrettyEnum, IntEnum):
75
75
  OFF = 0
76
76
  ON = 1
77
77
  SERVICE_MODE = 2
@@ -79,12 +79,12 @@ class BackyardState(IntEnum, PrettyEnum):
79
79
  TIMED_SERVICE_MODE = 4
80
80
 
81
81
 
82
- class BodyOfWaterState(IntEnum, PrettyEnum):
82
+ class BodyOfWaterState(PrettyEnum, IntEnum):
83
83
  NO_FLOW = 0
84
84
  FLOW = 1
85
85
 
86
86
 
87
- class BodyOfWaterType(StrEnum, PrettyEnum):
87
+ class BodyOfWaterType(PrettyEnum, StrEnum):
88
88
  POOL = "BOW_POOL"
89
89
  SPA = "BOW_SPA"
90
90
 
@@ -150,26 +150,26 @@ class ChlorinatorError(Flag):
150
150
  AQUARITE_PCB_ERROR = 1 << 14
151
151
 
152
152
 
153
- class ChlorinatorOperatingMode(IntEnum, PrettyEnum):
153
+ class ChlorinatorOperatingMode(PrettyEnum, IntEnum):
154
154
  DISABLED = 0
155
155
  TIMED = 1
156
156
  ORP_AUTO = 2
157
157
  ORP_TIMED_RW = 3 # Chlorinator in ORP mode experienced CSAD condition that prevents ORP operation
158
158
 
159
159
 
160
- class ChlorinatorType(StrEnum, PrettyEnum):
160
+ class ChlorinatorType(PrettyEnum, StrEnum):
161
161
  MAIN_PANEL = "CHLOR_TYPE_MAIN_PANEL"
162
162
  DISPENSER = "CHLOR_TYPE_DISPENSER"
163
163
  AQUA_RITE = "CHLOR_TYPE_AQUA_RITE"
164
164
 
165
165
 
166
- class ChlorinatorDispenserType(StrEnum, PrettyEnum):
166
+ class ChlorinatorDispenserType(PrettyEnum, StrEnum):
167
167
  SALT = "SALT_DISPENSING"
168
168
  LIQUID = "LIQUID_DISPENSING"
169
169
  TABLET = "TABLET_DISPENSING"
170
170
 
171
171
 
172
- class ChlorinatorCellType(IntEnum, PrettyEnum):
172
+ class ChlorinatorCellType(PrettyEnum, IntEnum):
173
173
  CELL_TYPE_UNKNOWN = 0
174
174
  CELL_TYPE_T3 = 1
175
175
  CELL_TYPE_T5 = 2
@@ -184,7 +184,7 @@ class ChlorinatorCellType(IntEnum, PrettyEnum):
184
184
 
185
185
 
186
186
  # Lights
187
- class ColorLogicSpeed(IntEnum, PrettyEnum):
187
+ class ColorLogicSpeed(PrettyEnum, IntEnum):
188
188
  ONE_SIXTEENTH = 0
189
189
  ONE_EIGHTH = 1
190
190
  ONE_QUARTER = 2
@@ -196,7 +196,7 @@ class ColorLogicSpeed(IntEnum, PrettyEnum):
196
196
  SIXTEEN_TIMES = 8
197
197
 
198
198
 
199
- class ColorLogicBrightness(IntEnum, PrettyEnum):
199
+ class ColorLogicBrightness(PrettyEnum, IntEnum):
200
200
  TWENTY_PERCENT = 0
201
201
  FOURTY_PERCENT = 1
202
202
  SIXTY_PERCENT = 2
@@ -207,7 +207,7 @@ class ColorLogicBrightness(IntEnum, PrettyEnum):
207
207
  type LightShows = ColorLogicShow25 | ColorLogicShow40 | ColorLogicShowUCL | ColorLogicShowUCLV2 | PentairShow | ZodiacShow
208
208
 
209
209
 
210
- class ColorLogicShow25(IntEnum, PrettyEnum):
210
+ class ColorLogicShow25(PrettyEnum, IntEnum):
211
211
  VOODOO_LOUNGE = 0
212
212
  DEEP_BLUE_SEA = 1
213
213
  AFTERNOON_SKY = 2
@@ -222,7 +222,7 @@ class ColorLogicShow25(IntEnum, PrettyEnum):
222
222
  COOL_CABARET = 11
223
223
 
224
224
 
225
- class ColorLogicShow40(IntEnum, PrettyEnum):
225
+ class ColorLogicShow40(PrettyEnum, IntEnum):
226
226
  VOODOO_LOUNGE = 0
227
227
  DEEP_BLUE_SEA = 1
228
228
  AFTERNOON_SKY = 2
@@ -237,7 +237,7 @@ class ColorLogicShow40(IntEnum, PrettyEnum):
237
237
  COOL_CABARET = 11
238
238
 
239
239
 
240
- class ColorLogicShowUCL(IntEnum, PrettyEnum):
240
+ class ColorLogicShowUCL(PrettyEnum, IntEnum):
241
241
  VOODOO_LOUNGE = 0
242
242
  DEEP_BLUE_SEA = 1
243
243
  ROYAL_BLUE = 2
@@ -257,7 +257,7 @@ class ColorLogicShowUCL(IntEnum, PrettyEnum):
257
257
  COOL_CABARET = 16
258
258
 
259
259
 
260
- class ColorLogicShowUCLV2(IntEnum, PrettyEnum):
260
+ class ColorLogicShowUCLV2(PrettyEnum, IntEnum):
261
261
  VOODOO_LOUNGE = 0
262
262
  DEEP_BLUE_SEA = 1
263
263
  ROYAL_BLUE = 2
@@ -288,7 +288,7 @@ class ColorLogicShowUCLV2(IntEnum, PrettyEnum):
288
288
  BRIGHT_YELLOW = 26
289
289
 
290
290
 
291
- class PentairShow(IntEnum, PrettyEnum):
291
+ class PentairShow(PrettyEnum, IntEnum):
292
292
  SAM = 0
293
293
  PARTY = 1
294
294
  ROMANCE = 2
@@ -303,7 +303,7 @@ class PentairShow(IntEnum, PrettyEnum):
303
303
  MAGENTA = 11
304
304
 
305
305
 
306
- class ZodiacShow(IntEnum, PrettyEnum):
306
+ class ZodiacShow(PrettyEnum, IntEnum):
307
307
  ALPINE_WHITE = 0
308
308
  SKY_BLUE = 1
309
309
  COBALT_BLUE = 2
@@ -320,7 +320,7 @@ class ZodiacShow(IntEnum, PrettyEnum):
320
320
  DISCO_TECH = 13
321
321
 
322
322
 
323
- class ColorLogicPowerState(IntEnum, PrettyEnum):
323
+ class ColorLogicPowerState(PrettyEnum, IntEnum):
324
324
  OFF = 0
325
325
  POWERING_OFF = 1
326
326
  CHANGING_SHOW = 3
@@ -329,7 +329,7 @@ class ColorLogicPowerState(IntEnum, PrettyEnum):
329
329
  COOLDOWN = 7
330
330
 
331
331
 
332
- class ColorLogicLightType(StrEnum, PrettyEnum):
332
+ class ColorLogicLightType(PrettyEnum, StrEnum):
333
333
  UCL = "COLOR_LOGIC_UCL"
334
334
  FOUR_ZERO = "COLOR_LOGIC_4_0"
335
335
  TWO_FIVE = "COLOR_LOGIC_2_5"
@@ -342,22 +342,22 @@ class ColorLogicLightType(StrEnum, PrettyEnum):
342
342
  return ColorLogicLightType[self.name].value
343
343
 
344
344
 
345
- class CSADType(StrEnum, PrettyEnum):
345
+ class CSADType(PrettyEnum, StrEnum):
346
346
  ACID = "ACID"
347
347
  CO2 = "CO2"
348
348
 
349
349
 
350
- class CSADEquipmentType(StrEnum, PrettyEnum):
350
+ class CSADEquipmentType(PrettyEnum, StrEnum):
351
351
  AQL_CHEM = "AQL-CHEM"
352
352
 
353
353
 
354
354
  # Chemistry Sense and Dispense
355
- class CSADStatus(IntEnum, PrettyEnum):
355
+ class CSADStatus(PrettyEnum, IntEnum):
356
356
  NOT_DISPENSING = 0
357
357
  DISPENSING = 1
358
358
 
359
359
 
360
- class CSADMode(IntEnum, PrettyEnum):
360
+ class CSADMode(PrettyEnum, IntEnum):
361
361
  OFF = 0
362
362
  AUTO = 1
363
363
  FORCE_ON = 2
@@ -366,7 +366,7 @@ class CSADMode(IntEnum, PrettyEnum):
366
366
 
367
367
 
368
368
  # Filters
369
- class FilterState(IntEnum, PrettyEnum):
369
+ class FilterState(PrettyEnum, IntEnum):
370
370
  OFF = 0
371
371
  ON = 1
372
372
  PRIMING = 2
@@ -381,13 +381,13 @@ class FilterState(IntEnum, PrettyEnum):
381
381
  FILTER_WAITING_TURN_OFF = 11
382
382
 
383
383
 
384
- class FilterType(StrEnum, PrettyEnum):
384
+ class FilterType(PrettyEnum, StrEnum):
385
385
  VARIABLE_SPEED = "FMT_VARIABLE_SPEED_PUMP"
386
386
  DUAL_SPEED = "FMT_DUAL_SPEED"
387
387
  SINGLE_SPEED = "FMT_SINGLE_SPEED"
388
388
 
389
389
 
390
- class FilterValvePosition(IntEnum, PrettyEnum):
390
+ class FilterValvePosition(PrettyEnum, IntEnum):
391
391
  POOL_ONLY = 1
392
392
  SPA_ONLY = 2
393
393
  SPILLOVER = 3
@@ -395,7 +395,7 @@ class FilterValvePosition(IntEnum, PrettyEnum):
395
395
  HIGH_PRIO_HEAT = 5
396
396
 
397
397
 
398
- class FilterWhyOn(IntEnum, PrettyEnum):
398
+ class FilterWhyOn(PrettyEnum, IntEnum):
399
399
  OFF = 0
400
400
  NO_WATER_FLOW = 1
401
401
  COOLDOWN = 2
@@ -417,28 +417,30 @@ class FilterWhyOn(IntEnum, PrettyEnum):
417
417
  GROUP_COMMAND = 18
418
418
  SPILLOVER_INTERLOCK = 19
419
419
  MAX_VALUE = 20
420
+ UNKNOWN_1 = 21
421
+ UNKNOWN_2 = 22
420
422
 
421
423
 
422
- class FilterSpeedPresets(StrEnum, PrettyEnum):
424
+ class FilterSpeedPresets(PrettyEnum, StrEnum):
423
425
  LOW = auto()
424
426
  MEDIUM = auto()
425
427
  HIGH = auto()
426
428
 
427
429
 
428
430
  # Groups
429
- class GroupState(IntEnum, PrettyEnum):
431
+ class GroupState(PrettyEnum, IntEnum):
430
432
  OFF = 0
431
433
  ON = 1
432
434
 
433
435
 
434
436
  # Heaters
435
- class HeaterState(IntEnum, PrettyEnum):
437
+ class HeaterState(PrettyEnum, IntEnum):
436
438
  OFF = 0
437
439
  ON = 1
438
440
  PAUSE = 2
439
441
 
440
442
 
441
- class HeaterType(StrEnum, PrettyEnum):
443
+ class HeaterType(PrettyEnum, StrEnum):
442
444
  GAS = "HTR_GAS"
443
445
  HEAT_PUMP = "HTR_HEAT_PUMP"
444
446
  SOLAR = "HTR_SOLAR"
@@ -449,26 +451,26 @@ class HeaterType(StrEnum, PrettyEnum):
449
451
  SMART_HEAT_PUMP = "HTR_SMART_HEAT_PUMP"
450
452
 
451
453
 
452
- class HeaterMode(IntEnum, PrettyEnum):
454
+ class HeaterMode(PrettyEnum, IntEnum):
453
455
  HEAT = 0
454
456
  COOL = 1
455
457
  AUTO = 2
456
458
 
457
459
 
458
460
  # Pumps
459
- class PumpState(IntEnum, PrettyEnum):
461
+ class PumpState(PrettyEnum, IntEnum):
460
462
  OFF = 0
461
463
  ON = 1
462
464
  FREEZE_PROTECT = 2 # This is an assumption that 2 means freeze protect, ref: https://github.com/cryptk/haomnilogic-local/issues/147
463
465
 
464
466
 
465
- class PumpType(StrEnum, PrettyEnum):
467
+ class PumpType(PrettyEnum, StrEnum):
466
468
  SINGLE_SPEED = "PMP_SINGLE_SPEED"
467
469
  DUAL_SPEED = "PMP_DUAL_SPEED"
468
470
  VARIABLE_SPEED = "PMP_VARIABLE_SPEED_PUMP"
469
471
 
470
472
 
471
- class PumpFunction(StrEnum, PrettyEnum):
473
+ class PumpFunction(PrettyEnum, StrEnum):
472
474
  PUMP = "PMP_PUMP"
473
475
  WATER_FEATURE = "PMP_WATER_FEATURE"
474
476
  CLEANER = "PMP_CLEANER"
@@ -485,14 +487,14 @@ class PumpFunction(StrEnum, PrettyEnum):
485
487
  CLEANER_IN_FLOOR = "PMP_CLEANER_IN_FLOOR"
486
488
 
487
489
 
488
- class PumpSpeedPresets(StrEnum, PrettyEnum):
490
+ class PumpSpeedPresets(PrettyEnum, StrEnum):
489
491
  LOW = auto()
490
492
  MEDIUM = auto()
491
493
  HIGH = auto()
492
494
 
493
495
 
494
496
  # Relays
495
- class RelayFunction(StrEnum, PrettyEnum):
497
+ class RelayFunction(PrettyEnum, StrEnum):
496
498
  WATER_FEATURE = "RLY_WATER_FEATURE"
497
499
  LIGHT = "RLY_LIGHT"
498
500
  BACKYARD_LIGHT = "RLY_BACKYARD_LIGHT"
@@ -512,18 +514,18 @@ class RelayFunction(StrEnum, PrettyEnum):
512
514
  CLEANER_IN_FLOOR = "RLY_CLEANER_IN_FLOOR"
513
515
 
514
516
 
515
- class RelayState(IntEnum, PrettyEnum):
517
+ class RelayState(PrettyEnum, IntEnum):
516
518
  OFF = 0
517
519
  ON = 1
518
520
 
519
521
 
520
- class RelayType(StrEnum, PrettyEnum):
522
+ class RelayType(PrettyEnum, StrEnum):
521
523
  VALVE_ACTUATOR = "RLY_VALVE_ACTUATOR"
522
524
  HIGH_VOLTAGE = "RLY_HIGH_VOLTAGE_RELAY"
523
525
  LOW_VOLTAGE = "RLY_LOW_VOLTAGE_RELAY"
524
526
 
525
527
 
526
- class RelayWhyOn(IntEnum, PrettyEnum):
528
+ class RelayWhyOn(PrettyEnum, IntEnum):
527
529
  NO_MESSAGE = 0
528
530
  MANUAL_OFF = 1
529
531
  COUNTDOWN_DONE = 2
@@ -539,7 +541,7 @@ class RelayWhyOn(IntEnum, PrettyEnum):
539
541
 
540
542
 
541
543
  # Sensors
542
- class SensorType(StrEnum, PrettyEnum):
544
+ class SensorType(PrettyEnum, StrEnum):
543
545
  AIR_TEMP = "SENSOR_AIR_TEMP"
544
546
  SOLAR_TEMP = "SENSOR_SOLAR_TEMP"
545
547
  WATER_TEMP = "SENSOR_WATER_TEMP"
@@ -548,7 +550,7 @@ class SensorType(StrEnum, PrettyEnum):
548
550
  EXT_INPUT = "SENSOR_EXT_INPUT"
549
551
 
550
552
 
551
- class SensorUnits(StrEnum, PrettyEnum):
553
+ class SensorUnits(PrettyEnum, StrEnum):
552
554
  FAHRENHEIT = "UNITS_FAHRENHEIT"
553
555
  CELSIUS = "UNITS_CELSIUS"
554
556
  PPM = "UNITS_PPM"
@@ -559,13 +561,13 @@ class SensorUnits(StrEnum, PrettyEnum):
559
561
 
560
562
 
561
563
  # Valve Actuators
562
- class ValveActuatorState(IntEnum, PrettyEnum):
564
+ class ValveActuatorState(PrettyEnum, IntEnum):
563
565
  OFF = 0
564
566
  ON = 1
565
567
 
566
568
 
567
569
  # Schedules
568
- class ScheduleDaysActive(Flag, PrettyEnum):
570
+ class ScheduleDaysActive(PrettyEnum, Flag):
569
571
  MONDAY = 1 << 0
570
572
  TUESDAY = 1 << 1
571
573
  WEDNESDAY = 1 << 2
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from pyomnilogic_local._base import OmniEquipment
4
6
  from pyomnilogic_local.decorators import control_method
5
7
  from pyomnilogic_local.models.mspconfig import MSPPump
@@ -7,6 +9,9 @@ from pyomnilogic_local.models.telemetry import TelemetryPump
7
9
  from pyomnilogic_local.omnitypes import PumpSpeedPresets, PumpState
8
10
  from pyomnilogic_local.util import OmniEquipmentNotInitializedError
9
11
 
12
+ if TYPE_CHECKING:
13
+ from pyomnilogic_local.omnitypes import PumpFunction, PumpType
14
+
10
15
 
11
16
  class Pump(OmniEquipment[MSPPump, TelemetryPump]):
12
17
  """Represents a pump in the OmniLogic system.
@@ -80,12 +85,12 @@ class Pump(OmniEquipment[MSPPump, TelemetryPump]):
80
85
 
81
86
  # Expose MSPConfig attributes
82
87
  @property
83
- def equip_type(self) -> str:
88
+ def equip_type(self) -> PumpType:
84
89
  """The pump type (e.g., PMP_VARIABLE_SPEED_PUMP)."""
85
90
  return self.mspconfig.equip_type
86
91
 
87
92
  @property
88
- def function(self) -> str:
93
+ def function(self) -> PumpFunction:
89
94
  """The pump function (e.g., PMP_PUMP, PMP_WATER_FEATURE)."""
90
95
  return self.mspconfig.function
91
96
 
@@ -131,7 +136,7 @@ class Pump(OmniEquipment[MSPPump, TelemetryPump]):
131
136
 
132
137
  # Expose Telemetry attributes
133
138
  @property
134
- def state(self) -> PumpState | int:
139
+ def state(self) -> PumpState:
135
140
  """Current pump state."""
136
141
  return self.telemetry.state
137
142
 
@@ -147,7 +152,10 @@ class Pump(OmniEquipment[MSPPump, TelemetryPump]):
147
152
 
148
153
  @property
149
154
  def why_on(self) -> int:
150
- """Reason why the pump is on."""
155
+ """Reason why the pump is on.
156
+
157
+ We don't have a confirmation that these are the same as the FilterWhyOn states yet.
158
+ """
151
159
  return self.telemetry.why_on
152
160
 
153
161
  # Computed properties
@@ -86,7 +86,7 @@ class Sensor(OmniEquipment[MSPSensor, None]):
86
86
  super().__init__(omni, mspconfig, telemetry)
87
87
 
88
88
  @property
89
- def equip_type(self) -> SensorType | str:
89
+ def equip_type(self) -> SensorType:
90
90
  """Returns the type of sensor.
91
91
 
92
92
  Can be AIR_TEMP, SOLAR_TEMP, WATER_TEMP, FLOW, ORP, or EXT_INPUT.
@@ -94,7 +94,7 @@ class Sensor(OmniEquipment[MSPSensor, None]):
94
94
  return self.mspconfig.equip_type
95
95
 
96
96
  @property
97
- def units(self) -> SensorUnits | str:
97
+ def units(self) -> SensorUnits:
98
98
  """Returns the units used by the sensor.
99
99
 
100
100
  Can be FAHRENHEIT, CELSIUS, PPM, GRAMS_PER_LITER, MILLIVOLTS,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Literal
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from pyomnilogic_local.models.mspconfig import MSPSystem
@@ -15,12 +15,12 @@ class System:
15
15
  self.update_config(mspconfig)
16
16
 
17
17
  @property
18
- def vsp_speed_format(self) -> str | None:
18
+ def vsp_speed_format(self) -> Literal["RPM", "Percent"]:
19
19
  """The VSP speed format of the system."""
20
20
  return self.mspconfig.vsp_speed_format
21
21
 
22
22
  @property
23
- def units(self) -> str | None:
23
+ def units(self) -> Literal["Standard", "Metric"]:
24
24
  """The units of the system."""
25
25
  return self.mspconfig.units
26
26
 
@@ -40,9 +40,15 @@ class OmniConnectionError(OmniLogicLocalError):
40
40
 
41
41
 
42
42
  class PrettyEnum(Enum):
43
- def pretty(self) -> str:
43
+ def __str__(self) -> str:
44
+ """Return a human-friendly string representation of the enum member."""
44
45
  return self.name.replace("_", " ").title()
45
46
 
46
47
  @classmethod
47
- def from_pretty(cls, name: str) -> Self:
48
- return cls[name.upper().replace(" ", "_")]
48
+ def from_str(cls, name: str) -> Self:
49
+ lookup_key = name.upper().replace(" ", "_")
50
+ try:
51
+ return cls[lookup_key]
52
+ except KeyError as err:
53
+ msg = f"'{name}' is not a valid {cls.__name__}"
54
+ raise ValueError(msg) from err
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-omnilogic-local"
3
- version = "0.23.0"
3
+ version = "0.25.0"
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.14.2"
@@ -12,7 +12,6 @@ authors = [
12
12
  license-files = ["LICENSE"]
13
13
  dependencies = [
14
14
  "pydantic >=2.0.0,<3.0.0",
15
- "click >=8.0.0,<9.0.0",
16
15
  "xmltodict >=1.0.1,<2.0.0",
17
16
  ]
18
17
 
@@ -22,6 +21,7 @@ omnilogic = "pyomnilogic_local.cli.cli:entrypoint"
22
21
  [project.optional-dependencies]
23
22
  cli = [
24
23
  "scapy>=2.6.1,<3.0.0",
24
+ "click >=8.0.0,<9.0.0",
25
25
  ]
26
26
  build = [
27
27
  "uv ~= 0.11.6"