python-omnilogic-local 0.25.1__tar.gz → 0.26.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 (63) hide show
  1. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/PKG-INFO +1 -1
  2. python_omnilogic_local-0.26.0/pyomnilogic_local/api/mock_api.py +118 -0
  3. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/omnitypes.py +1 -0
  4. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyproject.toml +1 -1
  5. python_omnilogic_local-0.25.1/pyomnilogic_local/api/mock_api.py +0 -88
  6. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/LICENSE +0 -0
  7. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/README.md +0 -0
  8. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/__init__.py +0 -0
  9. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/_base.py +0 -0
  10. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/api/__init__.py +0 -0
  11. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/api/api.py +0 -0
  12. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/api/constants.py +0 -0
  13. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/api/exceptions.py +0 -0
  14. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/api/protocol.py +0 -0
  15. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/backyard.py +0 -0
  16. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/bow.py +0 -0
  17. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/chlorinator.py +0 -0
  18. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/chlorinator_equip.py +0 -0
  19. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/__init__.py +0 -0
  20. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/cli.py +0 -0
  21. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/debug/__init__.py +0 -0
  22. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/debug/commands.py +0 -0
  23. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/__init__.py +0 -0
  24. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/backyard.py +0 -0
  25. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/bows.py +0 -0
  26. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/chlorinators.py +0 -0
  27. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/commands.py +0 -0
  28. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/csads.py +0 -0
  29. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/filters.py +0 -0
  30. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/groups.py +0 -0
  31. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/heaters.py +0 -0
  32. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/lights.py +0 -0
  33. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/pumps.py +0 -0
  34. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/relays.py +0 -0
  35. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/schedules.py +0 -0
  36. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/sensors.py +0 -0
  37. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/get/valves.py +0 -0
  38. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/pcap_utils.py +0 -0
  39. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/cli/utils.py +0 -0
  40. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/collections.py +0 -0
  41. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/colorlogiclight.py +0 -0
  42. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/csad.py +0 -0
  43. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/csad_equip.py +0 -0
  44. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/decorators.py +0 -0
  45. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/filter.py +0 -0
  46. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/groups.py +0 -0
  47. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/heater.py +0 -0
  48. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/heater_equip.py +0 -0
  49. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/__init__.py +0 -0
  50. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/const.py +0 -0
  51. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/exceptions.py +0 -0
  52. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/filter_diagnostics.py +0 -0
  53. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/leadmessage.py +0 -0
  54. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/mspconfig.py +0 -0
  55. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/models/telemetry.py +0 -0
  56. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/omnilogic.py +0 -0
  57. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/pump.py +0 -0
  58. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/py.typed +0 -0
  59. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/relay.py +0 -0
  60. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/schedule.py +0 -0
  61. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/sensor.py +0 -0
  62. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/pyomnilogic_local/system.py +0 -0
  63. {python_omnilogic_local-0.25.1 → python_omnilogic_local-0.26.0}/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.25.1
3
+ Version: 0.26.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>
@@ -0,0 +1,118 @@
1
+ """Mock API for simulation mode — loads data from a local JSON file instead of a live controller."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any, Literal, overload
9
+
10
+ from pyomnilogic_local.models.mspconfig import MSPConfig
11
+ from pyomnilogic_local.models.telemetry import Telemetry
12
+
13
+ _LOGGER = logging.getLogger(__name__)
14
+
15
+
16
+ class OmniLogicMockAPI:
17
+ """Drop-in replacement for OmniLogicAPI that serves pre-recorded data from one or more JSON files.
18
+
19
+ Each JSON file must contain the simulation data at the paths:
20
+ - ``.data.telemetry`` — raw XML telemetry string
21
+ - ``.data.msp_config`` — raw XML MSP config string
22
+
23
+ When multiple files are provided the class maintains a round-robin pointer into the
24
+ list. Whether the pointer advances after each call is controlled by two attributes:
25
+
26
+ - ``increment_on_mspconfig`` (default ``False``) — advance after ``async_get_mspconfig``
27
+ - ``increment_on_telemetry`` (default ``True``) — advance after ``async_get_telemetry``
28
+
29
+ Any API call other than ``async_get_telemetry`` or ``async_get_mspconfig`` is silently
30
+ absorbed and logged at INFO level; no network traffic is generated.
31
+ """
32
+
33
+ def __init__(self, filepath: str) -> None:
34
+ """Load simulation data from *filepath*.
35
+
36
+ Args:
37
+ filepath: Comma-separated path(s) to JSON simulation data file(s).
38
+ A single path with no commas is treated as a one-element list.
39
+
40
+ Raises:
41
+ FileNotFoundError: If any file does not exist.
42
+ KeyError: If the expected JSON structure is not present in a file.
43
+ """
44
+ paths = filepath.split(",")
45
+
46
+ self._sim_data: list[dict[str, Any]] = []
47
+ for fp in paths:
48
+ path = Path(fp)
49
+ if not path.exists():
50
+ msg = f"Simulation data file not found: {fp}"
51
+ raise FileNotFoundError(msg)
52
+ data = json.loads(path.read_text(encoding="utf-8"))
53
+ data["data"]["filepath"] = fp
54
+ self._sim_data.append(data["data"])
55
+
56
+ self._index = 0
57
+ self.increment_on_mspconfig = False
58
+ self.increment_on_telemetry = True
59
+
60
+ _LOGGER.warning(
61
+ "Running in simulation mode using data from %s. No API calls will be made to the OmniLogic controller.",
62
+ paths,
63
+ )
64
+
65
+ @overload
66
+ async def async_get_mspconfig(self, raw: Literal[True]) -> str: ...
67
+ @overload
68
+ async def async_get_mspconfig(self, raw: Literal[False]) -> MSPConfig: ...
69
+ @overload
70
+ async def async_get_mspconfig(self) -> MSPConfig: ...
71
+ async def async_get_mspconfig(self, raw: bool = False) -> MSPConfig | str:
72
+ """Return the pre-loaded MSP config from the current simulation file."""
73
+ data = self._sim_data[self._index]
74
+ if self.increment_on_mspconfig:
75
+ _LOGGER.debug(
76
+ "Advancing simulation file index from %s to %s, filepath: %s",
77
+ self._index,
78
+ (self._index + 1) % len(self._sim_data),
79
+ data["filepath"],
80
+ )
81
+ self._index = (self._index + 1) % len(self._sim_data)
82
+ if raw:
83
+ return data["msp_config"]
84
+ return MSPConfig.load_xml(data["msp_config"])
85
+
86
+ @overload
87
+ async def async_get_telemetry(self, raw: Literal[True]) -> str: ...
88
+ @overload
89
+ async def async_get_telemetry(self, raw: Literal[False]) -> Telemetry: ...
90
+ @overload
91
+ async def async_get_telemetry(self) -> Telemetry: ...
92
+ async def async_get_telemetry(self, raw: bool = False) -> Telemetry | str:
93
+ """Return the pre-loaded telemetry from the current simulation file."""
94
+ data = self._sim_data[self._index]
95
+ if self.increment_on_telemetry:
96
+ _LOGGER.debug(
97
+ "Advancing simulation file index from %s to %s, filepath: %s",
98
+ self._index,
99
+ (self._index + 1) % len(self._sim_data),
100
+ data["filepath"],
101
+ )
102
+ self._index = (self._index + 1) % len(self._sim_data)
103
+ if raw:
104
+ return data["telemetry"]
105
+ return Telemetry.load_xml(data["telemetry"])
106
+
107
+ def __getattr__(self, name: str) -> Any:
108
+ """Return a no-op async callable for any API method not explicitly implemented."""
109
+
110
+ async def _noop(*args: Any, **kwargs: Any) -> None:
111
+ _LOGGER.info(
112
+ "Simulation mode: ignoring call to %s (args=%s, kwargs=%s)",
113
+ name,
114
+ args,
115
+ kwargs,
116
+ )
117
+
118
+ return _noop
@@ -455,6 +455,7 @@ class HeaterMode(PrettyEnum, IntEnum):
455
455
  HEAT = 0
456
456
  COOL = 1
457
457
  AUTO = 2
458
+ UNKNOWN_1 = 3
458
459
 
459
460
 
460
461
  # Pumps
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-omnilogic-local"
3
- version = "0.25.1"
3
+ version = "0.26.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"
@@ -1,88 +0,0 @@
1
- """Mock API for simulation mode — loads data from a local JSON file instead of a live controller."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- from pathlib import Path
8
- from typing import Any, Literal, overload
9
-
10
- from pyomnilogic_local.models.mspconfig import MSPConfig
11
- from pyomnilogic_local.models.telemetry import Telemetry
12
-
13
- _LOGGER = logging.getLogger(__name__)
14
-
15
-
16
- class OmniLogicMockAPI:
17
- """Drop-in replacement for OmniLogicAPI that serves pre-recorded data from a JSON file.
18
-
19
- The JSON file must contain the simulation data at the paths:
20
- - ``.data.telemetry`` — raw XML telemetry string
21
- - ``.data.msp_config`` — raw XML MSP config string
22
-
23
- Any API call other than ``async_get_telemetry`` or ``async_get_mspconfig`` is silently
24
- absorbed and logged at INFO level; no network traffic is generated.
25
- """
26
-
27
- def __init__(self, filepath: str) -> None:
28
- """Load simulation data from *filepath*.
29
-
30
- Args:
31
- filepath: Path to the JSON simulation data file.
32
-
33
- Raises:
34
- FileNotFoundError: If the file does not exist at *filepath*.
35
- KeyError: If the expected JSON structure is not present in the file.
36
- """
37
- path = Path(filepath)
38
- if not path.exists():
39
- msg = f"Simulation data file not found: {filepath}"
40
- raise FileNotFoundError(msg)
41
-
42
- data = json.loads(path.read_text(encoding="utf-8"))
43
- sim_data: dict[str, Any] = data["data"]
44
-
45
- self._mspconfig = sim_data["msp_config"]
46
- self._telemetry = sim_data["telemetry"]
47
-
48
- _LOGGER.warning(
49
- "Running in simulation mode using data from '%s'. No API calls will be made to the OmniLogic controller.",
50
- filepath,
51
- )
52
-
53
- @overload
54
- async def async_get_mspconfig(self, raw: Literal[True]) -> str: ...
55
- @overload
56
- async def async_get_mspconfig(self, raw: Literal[False]) -> MSPConfig: ...
57
- @overload
58
- async def async_get_mspconfig(self) -> MSPConfig: ...
59
- async def async_get_mspconfig(self, raw: bool = False) -> MSPConfig | str:
60
- """Return the pre-loaded MSP config from the simulation file."""
61
- if raw:
62
- return self._mspconfig
63
- return MSPConfig.load_xml(self._mspconfig)
64
-
65
- @overload
66
- async def async_get_telemetry(self, raw: Literal[True]) -> str: ...
67
- @overload
68
- async def async_get_telemetry(self, raw: Literal[False]) -> Telemetry: ...
69
- @overload
70
- async def async_get_telemetry(self) -> Telemetry: ...
71
- async def async_get_telemetry(self, raw: bool = False) -> Telemetry | str:
72
- """Return the pre-loaded telemetry from the simulation file."""
73
- if raw:
74
- return self._telemetry
75
- return Telemetry.load_xml(self._telemetry)
76
-
77
- def __getattr__(self, name: str) -> Any:
78
- """Return a no-op async callable for any API method not explicitly implemented."""
79
-
80
- async def _noop(*args: Any, **kwargs: Any) -> None:
81
- _LOGGER.info(
82
- "Simulation mode: ignoring call to %s (args=%s, kwargs=%s)",
83
- name,
84
- args,
85
- kwargs,
86
- )
87
-
88
- return _noop