python-omnilogic-local 0.14.6__py3-none-any.whl → 0.15.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
pyomnilogic_local/api.py CHANGED
@@ -1,22 +1,23 @@
1
+ # pylint: disable=too-many-positional-arguments
1
2
  from __future__ import annotations
2
3
 
3
4
  import asyncio
4
5
  import logging
5
- from typing import Literal, overload
6
6
  import xml.etree.ElementTree as ET
7
+ from typing import Literal, overload
7
8
 
8
9
  from .models.filter_diagnostics import FilterDiagnostics
9
10
  from .models.mspconfig import MSPConfig
10
11
  from .models.telemetry import Telemetry
11
12
  from .models.util import to_pydantic
12
- from .protocol import OmniLogicProtocol
13
- from .types import (
13
+ from .omnitypes import (
14
14
  ColorLogicBrightness,
15
15
  ColorLogicShow,
16
16
  ColorLogicSpeed,
17
17
  HeaterMode,
18
18
  MessageType,
19
19
  )
20
+ from .protocol import OmniLogicProtocol
20
21
 
21
22
  _LOGGER = logging.getLogger(__name__)
22
23
 
@@ -30,12 +31,10 @@ class OmniLogicAPI:
30
31
  self._protocol_factory = OmniLogicProtocol
31
32
 
32
33
  @overload
33
- async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[True]) -> str:
34
- ...
34
+ async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[True]) -> str: ...
35
35
 
36
36
  @overload
37
- async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[False]) -> None:
38
- ...
37
+ async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[False]) -> None: ...
39
38
 
40
39
  async def async_send_message(self, message_type: MessageType, message: str | None, need_response: bool = False) -> str | None:
41
40
  """Send a message via the Hayward Omni UDP protocol along with properly handling timeouts and responses.
@@ -97,7 +96,11 @@ class OmniLogicAPI:
97
96
  return await self.async_send_message(MessageType.REQUEST_CONFIGURATION, req_body, True)
98
97
 
99
98
  @to_pydantic(pydantic_type=FilterDiagnostics)
100
- async def async_get_filter_diagnostics(self, pool_id: int, equipment_id: int) -> str:
99
+ async def async_get_filter_diagnostics(
100
+ self,
101
+ pool_id: int,
102
+ equipment_id: int,
103
+ ) -> str:
101
104
  """Retrieve filter diagnostics from the Omni, optionally parse it into a pydantic model.
102
105
 
103
106
  Args:
@@ -146,7 +149,13 @@ class OmniLogicAPI:
146
149
 
147
150
  return await self.async_send_message(MessageType.GET_TELEMETRY, req_body, True)
148
151
 
149
- async def async_set_heater(self, pool_id: int, equipment_id: int, temperature: int, unit: str) -> None:
152
+ async def async_set_heater(
153
+ self,
154
+ pool_id: int,
155
+ equipment_id: int,
156
+ temperature: int,
157
+ unit: str,
158
+ ) -> None:
150
159
  """Set the temperature for a heater on the Omni
151
160
 
152
161
  Args:
@@ -175,7 +184,13 @@ class OmniLogicAPI:
175
184
 
176
185
  return await self.async_send_message(MessageType.SET_HEATER_COMMAND, req_body, False)
177
186
 
178
- async def async_set_solar_heater(self, pool_id: int, equipment_id: int, temperature: int, unit: str) -> None:
187
+ async def async_set_solar_heater(
188
+ self,
189
+ pool_id: int,
190
+ equipment_id: int,
191
+ temperature: int,
192
+ unit: str,
193
+ ) -> None:
179
194
  """Set the solar set point for a heater on the Omni.
180
195
 
181
196
  Args:
@@ -204,7 +219,12 @@ class OmniLogicAPI:
204
219
 
205
220
  return await self.async_send_message(MessageType.SET_SOLAR_SET_POINT_COMMAND, req_body, False)
206
221
 
207
- async def async_set_heater_mode(self, pool_id: int, equipment_id: int, mode: HeaterMode) -> None:
222
+ async def async_set_heater_mode(
223
+ self,
224
+ pool_id: int,
225
+ equipment_id: int,
226
+ mode: HeaterMode,
227
+ ) -> None:
208
228
  """Set what mode (Heat/Cool/Auto) the heater should use.
209
229
 
210
230
  Args:
@@ -232,7 +252,12 @@ class OmniLogicAPI:
232
252
 
233
253
  return await self.async_send_message(MessageType.SET_HEATER_MODE_COMMAND, req_body, False)
234
254
 
235
- async def async_set_heater_enable(self, pool_id: int, equipment_id: int, enabled: int | bool) -> None:
255
+ async def async_set_heater_enable(
256
+ self,
257
+ pool_id: int,
258
+ equipment_id: int,
259
+ enabled: int | bool,
260
+ ) -> None:
236
261
  """async_set_heater_enable handles sending a SetHeaterEnable XML API call to the Hayward Omni pool controller
237
262
 
238
263
  Args:
@@ -472,7 +497,12 @@ class OmniLogicAPI:
472
497
 
473
498
  return await self.async_send_message(MessageType.SET_CHLOR_PARAMS, req_body, False)
474
499
 
475
- async def async_set_chlorinator_superchlorinate(self, pool_id: int, equipment_id: int, enabled: int | bool) -> None:
500
+ async def async_set_chlorinator_superchlorinate(
501
+ self,
502
+ pool_id: int,
503
+ equipment_id: int,
504
+ enabled: int | bool,
505
+ ) -> None:
476
506
  body_element = ET.Element("Request", {"xmlns": "http://nextgen.hayward.com/api"})
477
507
 
478
508
  name_element = ET.SubElement(body_element, "Name")
pyomnilogic_local/cli.py CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
-
3
2
  import asyncio
4
3
  import logging
5
4
  import os
@@ -23,6 +23,7 @@ class FilterDiagnostics(BaseModel):
23
23
  orm_mode = True
24
24
 
25
25
  def get_param_by_name(self, name: str) -> int:
26
+ # pylint: disable=not-an-iterable
26
27
  return [param.value for param in self.parameters if param.name == name][0]
27
28
 
28
29
  @staticmethod
@@ -13,7 +13,7 @@ from pydantic.v1 import BaseModel, Field, ValidationError
13
13
  from xmltodict import parse as xml_parse
14
14
 
15
15
  from ..exceptions import OmniParsingException
16
- from ..types import (
16
+ from ..omnitypes import (
17
17
  BodyOfWaterType,
18
18
  ChlorinatorCellType,
19
19
  ChlorinatorDispenserType,
@@ -219,7 +219,7 @@ class MSPBackyard(OmniBase):
219
219
  colorlogic_light: list[MSPColorLogicLight] | None = Field(alias="ColorLogic-Light")
220
220
 
221
221
 
222
- class MSPSchedule(OmniBase):
222
+ class MSPSchedule(OmniBase): # type: ignore[override]
223
223
  omni_type: OmniType = OmniType.SCHEDULE
224
224
  system_id: int = Field(alias="schedule-system-id")
225
225
  bow_id: int = Field(alias="bow-system-id")
@@ -6,7 +6,7 @@ from pydantic.v1 import BaseModel, Field, ValidationError
6
6
  from xmltodict import parse as xml_parse
7
7
 
8
8
  from ..exceptions import OmniParsingException
9
- from ..types import (
9
+ from ..omnitypes import (
10
10
  BackyardState,
11
11
  ChlorinatorOperatingMode,
12
12
  ColorLogicBrightness,
@@ -71,7 +71,7 @@ class TelemetryChlorinator(BaseModel):
71
71
  sc_mode: int = Field(alias="@scMode")
72
72
  operating_state: int = Field(alias="@operatingState")
73
73
  timed_percent: int | None = Field(alias="@Timed-Percent")
74
- operating_mode: ChlorinatorOperatingMode = Field(alias="@operatingMode")
74
+ operating_mode: ChlorinatorOperatingMode | int = Field(alias="@operatingMode")
75
75
  enable: bool = Field(alias="@enable")
76
76
 
77
77
  # Still need to do a bit more work to determine if a chlorinator is actively chlorinating
@@ -200,12 +200,10 @@ class Telemetry(BaseModel):
200
200
  TypeVar("VT", SupportsInt, Any)
201
201
 
202
202
  @overload
203
- def xml_postprocessor(path: Any, key: Any, value: SupportsInt) -> tuple[Any, SupportsInt]:
204
- ...
203
+ def xml_postprocessor(path: Any, key: Any, value: SupportsInt) -> tuple[Any, SupportsInt]: ...
205
204
 
206
205
  @overload
207
- def xml_postprocessor(path: Any, key: Any, value: Any) -> tuple[Any, Any]:
208
- ...
206
+ def xml_postprocessor(path: Any, key: Any, value: Any) -> tuple[Any, Any]: ...
209
207
 
210
208
  def xml_postprocessor(path: Any, key: Any, value: SupportsInt | Any) -> tuple[Any, SupportsInt | Any]:
211
209
  """Post process XML to attempt to convert values to int.
@@ -1,5 +1,5 @@
1
- from collections.abc import Awaitable, Callable
2
1
  import logging
2
+ from collections.abc import Awaitable, Callable
3
3
  from typing import Any, Literal, TypeVar, cast, overload
4
4
 
5
5
  from pydantic.v1.utils import GetterDict
@@ -24,17 +24,17 @@ F = TypeVar("F", bound=Callable[..., Awaitable[str]])
24
24
  TPydanticTypes = Telemetry | MSPConfig | FilterDiagnostics
25
25
 
26
26
 
27
- def to_pydantic(pydantic_type: type[TPydanticTypes]) -> Callable[..., Any]:
27
+ def to_pydantic(
28
+ pydantic_type: type[TPydanticTypes],
29
+ ) -> Callable[..., Any]:
28
30
  def inner(func: F, *args: Any, **kwargs: Any) -> F:
29
31
  """Wrap an API function that returns XML and parse it into a Pydantic model"""
30
32
 
31
33
  @overload
32
- async def wrapper(*args: Any, raw: Literal[True], **kwargs: Any) -> str:
33
- ...
34
+ async def wrapper(*args: Any, raw: Literal[True], **kwargs: Any) -> str: ...
34
35
 
35
36
  @overload
36
- async def wrapper(*args: Any, raw: Literal[False], **kwargs: Any) -> TPydanticTypes:
37
- ...
37
+ async def wrapper(*args: Any, raw: Literal[False], **kwargs: Any) -> TPydanticTypes: ...
38
38
 
39
39
  async def wrapper(*args: Any, raw: bool = False, **kwargs: Any) -> TPydanticTypes | str:
40
40
  resp_body = await func(*args, **kwargs)
@@ -87,14 +87,19 @@ class BodyOfWaterType(str, PrettyEnum):
87
87
  # Chlorinator status is a bitmask that we still need to figure out
88
88
  # class ChlorinatorStatus(str,Enum):
89
89
  # pass
90
+
91
+
92
+ # I have seen one pool that had an operatingMode of 3, I am not sure what that means, perhaps that is an OFF mode
90
93
  class ChlorinatorOperatingMode(IntEnum):
91
94
  DISABLED = 0
92
95
  TIMED = 1
93
96
  ORP = 2
97
+ OFF = 3
94
98
 
95
99
 
96
100
  class ChlorinatorDispenserType(str, PrettyEnum):
97
101
  SALT = "SALT_DISPENSING"
102
+ LIQUID = "LIQUID_DISPENSING"
98
103
 
99
104
 
100
105
  class ChlorinatorCellType(PrettyEnum):
@@ -102,6 +107,7 @@ class ChlorinatorCellType(PrettyEnum):
102
107
  T5 = "CELL_TYPE_T5"
103
108
  T9 = "CELL_TYPE_T9"
104
109
  T15 = "CELL_TYPE_T15"
110
+ LIQUID = "CELL_TYPE_LIQUID"
105
111
 
106
112
  # There is probably an easier way to do this
107
113
  def __int__(self) -> int:
@@ -3,15 +3,15 @@ import logging
3
3
  import random
4
4
  import struct
5
5
  import time
6
- from typing import Any, cast
7
6
  import xml.etree.ElementTree as ET
8
7
  import zlib
8
+ from typing import Any, cast
9
9
 
10
10
  from typing_extensions import Self
11
11
 
12
12
  from .exceptions import OmniTimeoutException
13
13
  from .models.leadmessage import LeadMessage
14
- from .types import ClientType, MessageType
14
+ from .omnitypes import ClientType, MessageType
15
15
 
16
16
  _LOGGER = logging.getLogger(__name__)
17
17
 
@@ -28,7 +28,13 @@ class OmniLogicMessage:
28
28
  compressed: bool = False
29
29
  reserved_2: int = 0
30
30
 
31
- def __init__(self, msg_id: int, msg_type: MessageType, payload: str | None = None, version: str = "1.19") -> None:
31
+ def __init__(
32
+ self,
33
+ msg_id: int,
34
+ msg_type: MessageType,
35
+ payload: str | None = None,
36
+ version: str = "1.19",
37
+ ) -> None:
32
38
  self.id = msg_id
33
39
  self.type = msg_type
34
40
  # If we are speaking the XML API, it seems like we need client_type 0, otherwise we need client_type 1
@@ -67,7 +73,7 @@ class OmniLogicMessage:
67
73
  header = data[:24]
68
74
  rdata: bytes = data[24:]
69
75
 
70
- msg_id, tstamp, vers, msg_type, client_type, res1, compressed, res2 = struct.unpack(cls.header_format, header)
76
+ (msg_id, tstamp, vers, msg_type, client_type, res1, compressed, res2) = struct.unpack(cls.header_format, header)
71
77
  message = cls(msg_id=msg_id, msg_type=MessageType(msg_type), version=vers.decode("utf-8"))
72
78
  message.timestamp = tstamp
73
79
  message.client_type = ClientType(int(client_type))
@@ -125,7 +131,11 @@ class OmniLogicProtocol(asyncio.DatagramProtocol):
125
131
  # eventually time out waiting for it, that way we can deal with the dropped packets
126
132
  message = await self.data_queue.get()
127
133
 
128
- async def _ensure_sent(self, message: OmniLogicMessage, max_attempts: int = 5) -> None:
134
+ async def _ensure_sent(
135
+ self,
136
+ message: OmniLogicMessage,
137
+ max_attempts: int = 5,
138
+ ) -> None:
129
139
  for attempt in range(0, max_attempts):
130
140
  self.transport.sendto(bytes(message))
131
141
 
@@ -143,12 +153,22 @@ class OmniLogicProtocol(asyncio.DatagramProtocol):
143
153
  else:
144
154
  raise OmniTimeoutException("Failed to receive acknowledgement of command, max retries exceeded") from exc
145
155
 
146
- async def send_and_receive(self, msg_type: MessageType, payload: str | None, msg_id: int | None = None) -> str:
156
+ async def send_and_receive(
157
+ self,
158
+ msg_type: MessageType,
159
+ payload: str | None,
160
+ msg_id: int | None = None,
161
+ ) -> str:
147
162
  await self.send_message(msg_type, payload, msg_id)
148
163
  return await self._receive_file()
149
164
 
150
165
  # Send a message that you do NOT need a response to
151
- async def send_message(self, msg_type: MessageType, payload: str | None, msg_id: int | None = None) -> None:
166
+ async def send_message(
167
+ self,
168
+ msg_type: MessageType,
169
+ payload: str | None,
170
+ msg_id: int | None = None,
171
+ ) -> None:
152
172
  # If we aren't sending a specific msg_id, lets randomize it
153
173
  if not msg_id:
154
174
  msg_id = random.randrange(2**32)
pyomnilogic_local/util.py CHANGED
@@ -1,5 +1,5 @@
1
- from enum import Enum
2
1
  import sys
2
+ from enum import Enum
3
3
 
4
4
  if sys.version_info >= (3, 11):
5
5
  from typing import Self
@@ -1,26 +1,19 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-omnilogic-local
3
- Version: 0.14.6
3
+ Version: 0.15.2
4
4
  Summary: A library for local control of Hayward OmniHub/OmniLogic pool controllers using their local API
5
- Home-page: https://github.com/cryptk/python-omnilogic-local
6
5
  License: Apache-2.0
7
- Author: cryptk
8
- Author-email: cryptk@users.noreply.github.com
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: Developers
6
+ Author: Chris Jowett
7
+ Author-email: 421501+cryptk@users.noreply.github.com
8
+ Requires-Python: >=3.10,<3.14
12
9
  Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Natural Language :: English
14
- Classifier: Operating System :: OS Independent
15
10
  Classifier: Programming Language :: Python :: 3
16
11
  Classifier: Programming Language :: Python :: 3.10
17
12
  Classifier: Programming Language :: Python :: 3.11
18
13
  Classifier: Programming Language :: Python :: 3.12
19
14
  Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Topic :: Software Development :: Libraries
21
15
  Requires-Dist: pydantic (>=1.10.17)
22
16
  Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
23
- Project-URL: Repository, https://github.com/cryptk/python-omnilogic-local
24
17
  Description-Content-Type: text/markdown
25
18
 
26
19
  # Pyomnilogic Local
@@ -0,0 +1,19 @@
1
+ pyomnilogic_local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pyomnilogic_local/api.py,sha256=G991ro3UM-ksVg9gGjTArOvZe41MWsSopbcyqPwwdsU,28390
3
+ pyomnilogic_local/cli.py,sha256=YsLPDuBUM9hDmSiYVfz0H346OtPUgRwiLHfKWUKbD7Q,3930
4
+ pyomnilogic_local/exceptions.py,sha256=7-EYTP-_VgGrA8WWGwQPUE1NGjJEDWB-ovyvSM2iaNY,164
5
+ pyomnilogic_local/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pyomnilogic_local/models/const.py,sha256=j4HSiPJW5If296yJ-AiIsBI9EdlckF4QkVFIpTLb5C4,51
7
+ pyomnilogic_local/models/filter_diagnostics.py,sha256=74-VjHB-Rf6imeJTYawybA40_m_wb1Dww-Eu4ucGBdo,1577
8
+ pyomnilogic_local/models/leadmessage.py,sha256=XnzXTpLLQzhLQR1OgfAjKmW5N8BBgM7OrntUi1I2VRY,410
9
+ pyomnilogic_local/models/mspconfig.py,sha256=6YUz5e1V25KX0VynT6iwyZyzi8cPUvZwwLSY0M2bTV0,10594
10
+ pyomnilogic_local/models/telemetry.py,sha256=8ncC-10SRQneFmrH-LrnlVTS3Q-oqeXS_EtYKiIfDks,10612
11
+ pyomnilogic_local/models/util.py,sha256=X9jCiU3hFbI0Nc6NSCnvwyVK9XyA3BsiA9W7JYCJmKs,1462
12
+ pyomnilogic_local/omnitypes.py,sha256=rmmBo1xKgsHsX9bjbBh-0YPv4lID1jGrNNU1W1m6zBo,7832
13
+ pyomnilogic_local/protocol.py,sha256=M2vyj8o_PCKyLEswb6JGpUIdyj_swEJC18KrqcQV4kI,10356
14
+ pyomnilogic_local/util.py,sha256=sLkCn2eWbwP4FRiEgYlfmNxiHPeiWYjeUyvRRhWuNYE,359
15
+ python_omnilogic_local-0.15.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ python_omnilogic_local-0.15.2.dist-info/METADATA,sha256=JnhXfISXcBHkTvEJjHfYPvhQcc_F2Nx1FvxZvaSqyfo,2634
17
+ python_omnilogic_local-0.15.2.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
18
+ python_omnilogic_local-0.15.2.dist-info/entry_points.txt,sha256=PUvdumqSijeB0dHH_s5oE2TnWtPWdJSNpSOsn8yTtOo,56
19
+ python_omnilogic_local-0.15.2.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- pyomnilogic_local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pyomnilogic_local/api.py,sha256=48_NxsDwZXOVgkbl1NJ4lUfNAS6qMDrRNdTr_eXBMJU,28112
3
- pyomnilogic_local/cli.py,sha256=RwzKydC612-ay6TGrlSg8UEQPjYIlylxjf11S062bsk,3931
4
- pyomnilogic_local/exceptions.py,sha256=7-EYTP-_VgGrA8WWGwQPUE1NGjJEDWB-ovyvSM2iaNY,164
5
- pyomnilogic_local/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- pyomnilogic_local/models/const.py,sha256=j4HSiPJW5If296yJ-AiIsBI9EdlckF4QkVFIpTLb5C4,51
7
- pyomnilogic_local/models/filter_diagnostics.py,sha256=DnkbjOsmfa39-sbD5qO5PV_Ltn5FHSiImrPBxr7Y0cI,1535
8
- pyomnilogic_local/models/leadmessage.py,sha256=XnzXTpLLQzhLQR1OgfAjKmW5N8BBgM7OrntUi1I2VRY,410
9
- pyomnilogic_local/models/mspconfig.py,sha256=sa_fn5VOVlzVfZhl60ly8XkDb4T8r5om40mFE7dxV2A,10564
10
- pyomnilogic_local/models/telemetry.py,sha256=oud_Tusqahw1bagiLFE_dN6cyC4ykMiMuRYSKrq_iMQ,10626
11
- pyomnilogic_local/models/util.py,sha256=MgqHmDy7OE-62vFX5Rq1tRxs6GmzamBuWoaLYGm-fPI,1479
12
- pyomnilogic_local/protocol.py,sha256=1Z7U70mV0sUlqrp3XK1Fvj6NuChRKLz0ejzKaQLSPE8,10194
13
- pyomnilogic_local/types.py,sha256=c9Ujw67lZFZ2V7gl-J6EVYa-OlQQig_IF0z6g5Cxw8Y,7639
14
- pyomnilogic_local/util.py,sha256=agbRBKnecrYycC9iUmo1aJDjbVg9VxHyq4QY0D8-bfA,359
15
- python_omnilogic_local-0.14.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- python_omnilogic_local-0.14.6.dist-info/METADATA,sha256=nYBd5MLkh270MdCpAGd_LZOKje_MVdExeF25GqGsL0s,2984
17
- python_omnilogic_local-0.14.6.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
18
- python_omnilogic_local-0.14.6.dist-info/entry_points.txt,sha256=PUvdumqSijeB0dHH_s5oE2TnWtPWdJSNpSOsn8yTtOo,56
19
- python_omnilogic_local-0.14.6.dist-info/RECORD,,