python-omnilogic-local 0.15.2__tar.gz → 0.17.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/PKG-INFO +2 -1
  2. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/api.py +41 -0
  3. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/cli.py +25 -0
  4. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/debug/__init__.py +0 -0
  5. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/debug/commands.py +95 -0
  6. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/get/__init__.py +0 -0
  7. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/get/commands.py +33 -0
  8. python_omnilogic_local-0.17.0/pyomnilogic_local/cli/utils.py +15 -0
  9. python_omnilogic_local-0.17.0/pyomnilogic_local/models/__init__.py +0 -0
  10. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/omnitypes.py +10 -0
  11. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyproject.toml +10 -4
  12. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/LICENSE +0 -0
  13. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/README.md +0 -0
  14. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/__init__.py +0 -0
  15. {python_omnilogic_local-0.15.2/pyomnilogic_local/models → python_omnilogic_local-0.17.0/pyomnilogic_local/cli}/__init__.py +0 -0
  16. /python_omnilogic_local-0.15.2/pyomnilogic_local/cli.py → /python_omnilogic_local-0.17.0/pyomnilogic_local/cli_legacy.py +0 -0
  17. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/exceptions.py +0 -0
  18. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/const.py +0 -0
  19. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/filter_diagnostics.py +0 -0
  20. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/leadmessage.py +0 -0
  21. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/mspconfig.py +0 -0
  22. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/telemetry.py +0 -0
  23. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/models/util.py +0 -0
  24. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/protocol.py +0 -0
  25. {python_omnilogic_local-0.15.2 → python_omnilogic_local-0.17.0}/pyomnilogic_local/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-omnilogic-local
3
- Version: 0.15.2
3
+ Version: 0.17.0
4
4
  Summary: A library for local control of Hayward OmniHub/OmniLogic pool controllers using their local API
5
5
  License: Apache-2.0
6
6
  Author: Chris Jowett
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: click (>=8.0.0,<8.1.0)
15
16
  Requires-Dist: pydantic (>=1.10.17)
16
17
  Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
17
18
  Description-Content-Type: text/markdown
@@ -572,3 +572,44 @@ class OmniLogicAPI:
572
572
  req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")
573
573
 
574
574
  return await self.async_send_message(MessageType.SET_SPILLOVER, req_body, False)
575
+
576
+ async def async_set_group_enable(
577
+ self,
578
+ group_id: int,
579
+ enabled: int | bool,
580
+ is_countdown_timer: bool = False,
581
+ start_time_hours: int = 0,
582
+ start_time_minutes: int = 0,
583
+ end_time_hours: int = 0,
584
+ end_time_minutes: int = 0,
585
+ days_active: int = 0,
586
+ recurring: bool = False,
587
+ ) -> None:
588
+ body_element = ET.Element("Request", {"xmlns": "http://nextgen.hayward.com/api"})
589
+
590
+ name_element = ET.SubElement(body_element, "Name")
591
+ name_element.text = "RunGroupCmd"
592
+
593
+ parameters_element = ET.SubElement(body_element, "Parameters")
594
+ parameter = ET.SubElement(parameters_element, "Parameter", name="GroupID", dataType="int")
595
+ parameter.text = str(group_id)
596
+ parameter = ET.SubElement(parameters_element, "Parameter", name="Data", dataType="int")
597
+ parameter.text = str(int(enabled))
598
+ parameter = ET.SubElement(parameters_element, "Parameter", name="IsCountDownTimer", dataType="bool")
599
+ parameter.text = str(int(is_countdown_timer))
600
+ parameter = ET.SubElement(parameters_element, "Parameter", name="StartTimeHours", dataType="int")
601
+ parameter.text = str(start_time_hours)
602
+ parameter = ET.SubElement(parameters_element, "Parameter", name="StartTimeMinutes", dataType="int")
603
+ parameter.text = str(start_time_minutes)
604
+ parameter = ET.SubElement(parameters_element, "Parameter", name="EndTimeHours", dataType="int")
605
+ parameter.text = str(end_time_hours)
606
+ parameter = ET.SubElement(parameters_element, "Parameter", name="EndTimeMinutes", dataType="int")
607
+ parameter.text = str(end_time_minutes)
608
+ parameter = ET.SubElement(parameters_element, "Parameter", name="DaysActive", dataType="int")
609
+ parameter.text = str(days_active)
610
+ parameter = ET.SubElement(parameters_element, "Parameter", name="Recurring", dataType="bool")
611
+ parameter.text = str(int(recurring))
612
+
613
+ req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")
614
+
615
+ return await self.async_send_message(MessageType.RUN_GROUP_CMD, req_body, False)
@@ -0,0 +1,25 @@
1
+ import asyncio
2
+
3
+ import click
4
+
5
+ from pyomnilogic_local.api import OmniLogicAPI
6
+ from pyomnilogic_local.cli.debug import commands as debug
7
+ from pyomnilogic_local.cli.get import commands as get
8
+
9
+
10
+ async def get_omni(host: str) -> OmniLogicAPI:
11
+ return OmniLogicAPI(host, 10444, 5.0)
12
+
13
+
14
+ @click.group()
15
+ @click.pass_context
16
+ @click.option("--host", default="127.0.0.1", help="Hostname or IP address of omnilogic system")
17
+ def entrypoint(ctx: click.Context, host: str) -> None:
18
+ ctx.ensure_object(dict)
19
+ omni = asyncio.run(get_omni(host))
20
+
21
+ ctx.obj["OMNI"] = omni
22
+
23
+
24
+ entrypoint.add_command(debug.debug)
25
+ entrypoint.add_command(get.get)
@@ -0,0 +1,95 @@
1
+ # Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
2
+ # mypy: disable-error-code="misc"
3
+ import asyncio
4
+ from typing import Literal, overload
5
+
6
+ import click
7
+
8
+ from pyomnilogic_local.api import OmniLogicAPI
9
+ from pyomnilogic_local.cli.utils import async_get_mspconfig, async_get_telemetry
10
+ from pyomnilogic_local.models.filter_diagnostics import FilterDiagnostics
11
+
12
+
13
+ @click.group()
14
+ @click.option("--raw/--no-raw", default=False, help="Output the raw XML from the OmniLogic, do not parse the response")
15
+ @click.pass_context
16
+ def debug(ctx: click.Context, raw: bool) -> None:
17
+ # Container for all get commands
18
+
19
+ ctx.ensure_object(dict)
20
+ ctx.obj["RAW"] = raw
21
+
22
+
23
+ @debug.command()
24
+ @click.pass_context
25
+ def get_mspconfig(ctx: click.Context) -> None:
26
+ mspconfig = asyncio.run(async_get_mspconfig(ctx.obj["OMNI"], ctx.obj["RAW"]))
27
+ click.echo(mspconfig)
28
+
29
+
30
+ @debug.command()
31
+ @click.pass_context
32
+ def get_telemetry(ctx: click.Context) -> None:
33
+ telemetry = asyncio.run(async_get_telemetry(ctx.obj["OMNI"], ctx.obj["RAW"]))
34
+ click.echo(telemetry)
35
+
36
+
37
+ @debug.command()
38
+ @click.pass_context
39
+ def get_alarm_list(ctx: click.Context) -> None:
40
+ alarm_list = asyncio.run(async_get_alarm_list(ctx.obj["OMNI"]))
41
+ click.echo(alarm_list)
42
+
43
+
44
+ async def async_get_alarm_list(omni: OmniLogicAPI) -> str:
45
+ alarm_list = await omni.async_get_alarm_list()
46
+ return alarm_list
47
+
48
+
49
+ @debug.command()
50
+ @click.option("--pool-id", help="System ID of the Body Of Water the filter is associated with")
51
+ @click.option("--filter-id", help="System ID of the filter to request diagnostics for")
52
+ @click.pass_context
53
+ def get_filter_diagnostics(ctx: click.Context, pool_id: int, filter_id: int) -> None:
54
+ filter_diags = asyncio.run(async_get_filter_diagnostics(ctx.obj["OMNI"], pool_id, filter_id, ctx.obj["RAW"]))
55
+ if ctx.obj["RAW"]:
56
+ click.echo(filter_diags)
57
+ else:
58
+ drv1 = chr(filter_diags.get_param_by_name("DriveFWRevisionB1"))
59
+ drv2 = chr(filter_diags.get_param_by_name("DriveFWRevisionB2"))
60
+ drv3 = chr(filter_diags.get_param_by_name("DriveFWRevisionB3"))
61
+ drv4 = chr(filter_diags.get_param_by_name("DriveFWRevisionB4"))
62
+ dfw1 = chr(filter_diags.get_param_by_name("DisplayFWRevisionB1"))
63
+ dfw2 = chr(filter_diags.get_param_by_name("DisplayFWRevisionB2"))
64
+ dfw3 = chr(filter_diags.get_param_by_name("DisplayFWRevisionB3"))
65
+ dfw4 = chr(filter_diags.get_param_by_name("DisplayFWRevisionB4"))
66
+ pow1 = filter_diags.get_param_by_name("PowerMSB")
67
+ pow2 = filter_diags.get_param_by_name("PowerLSB")
68
+ errs = filter_diags.get_param_by_name("ErrorStatus")
69
+ click.echo(
70
+ f"DRIVE FW REV: {drv1}{drv2}.{drv3}{drv4}\n"
71
+ f"DISPLAY FW REV: {dfw1}{dfw2}.{dfw3}.{dfw4}\n"
72
+ f"POWER: {pow1:x}{pow2:x}W\n"
73
+ f"ERROR STATUS: {errs}"
74
+ )
75
+
76
+
77
+ @overload
78
+ async def async_get_filter_diagnostics(omni: OmniLogicAPI, pool_id: int, filter_id: int, raw: Literal[True]) -> str: ...
79
+ @overload
80
+ async def async_get_filter_diagnostics(omni: OmniLogicAPI, pool_id: int, filter_id: int, raw: Literal[False]) -> FilterDiagnostics: ...
81
+ async def async_get_filter_diagnostics(omni: OmniLogicAPI, pool_id: int, filter_id: int, raw: bool) -> FilterDiagnostics | str:
82
+ filter_diags = await omni.async_get_filter_diagnostics(pool_id, filter_id, raw=raw)
83
+ return filter_diags
84
+
85
+
86
+ @debug.command()
87
+ @click.pass_context
88
+ def get_log_config(ctx: click.Context) -> None:
89
+ log_config = asyncio.run(async_get_log_config(ctx.obj["OMNI"]))
90
+ click.echo(log_config)
91
+
92
+
93
+ async def async_get_log_config(omni: OmniLogicAPI) -> str:
94
+ log_config = await omni.async_get_log_config()
95
+ return log_config
@@ -0,0 +1,33 @@
1
+ # Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
2
+ # mypy: disable-error-code="misc"
3
+ import asyncio
4
+
5
+ import click
6
+
7
+ from pyomnilogic_local.cli.utils import async_get_mspconfig
8
+
9
+
10
+ @click.group()
11
+ @click.pass_context
12
+ def get(ctx: click.Context) -> None:
13
+ # Container for all get commands
14
+
15
+ ctx.ensure_object(dict)
16
+
17
+
18
+ @get.command()
19
+ @click.pass_context
20
+ def lights(ctx: click.Context) -> None:
21
+ mspconfig = asyncio.run(async_get_mspconfig(ctx.obj["OMNI"]))
22
+ # Return data about lights in the backyard
23
+ if mspconfig.backyard.colorlogic_light:
24
+ for light in mspconfig.backyard.colorlogic_light:
25
+ click.echo(light)
26
+
27
+ # Return data about lights in the Body of Water
28
+ if mspconfig.backyard.bow:
29
+ for bow in mspconfig.backyard.bow:
30
+ if bow.colorlogic_light:
31
+ for cl_light in bow.colorlogic_light:
32
+ for k, v in cl_light:
33
+ click.echo(f"{k:15}\t{str(v)}")
@@ -0,0 +1,15 @@
1
+ from pyomnilogic_local.api import OmniLogicAPI
2
+ from pyomnilogic_local.models.mspconfig import MSPConfig
3
+ from pyomnilogic_local.models.telemetry import Telemetry
4
+
5
+
6
+ async def async_get_mspconfig(omni: OmniLogicAPI, raw: bool = False) -> MSPConfig:
7
+ mspconfig: MSPConfig
8
+ mspconfig = await omni.async_get_config(raw=raw)
9
+ return mspconfig
10
+
11
+
12
+ async def async_get_telemetry(omni: OmniLogicAPI, raw: bool = False) -> Telemetry:
13
+ telemetry: Telemetry
14
+ telemetry = await omni.async_get_telemetry(raw=raw)
15
+ return telemetry
@@ -23,6 +23,7 @@ class MessageType(Enum):
23
23
  GET_ALARM_LIST = 304
24
24
  SET_STANDALONE_LIGHT_SHOW = 308
25
25
  SET_SPILLOVER = 311
26
+ RUN_GROUP_CMD = 317
26
27
  RESTORE_IDLE_STATE = 340
27
28
  GET_FILTER_DIAGNOSTIC_INFO = 386
28
29
  HANDSHAKE = 1000
@@ -63,6 +64,9 @@ class OmniType(str, Enum):
63
64
  VALVE_ACTUATOR = "ValveActuator"
64
65
  VIRT_HEATER = "VirtualHeater"
65
66
 
67
+ def __str__(self) -> str:
68
+ return OmniType[self.name].value
69
+
66
70
 
67
71
  # Backyard/BoW
68
72
  class BackyardState(PrettyEnum):
@@ -172,6 +176,9 @@ class ColorLogicShow(PrettyEnum):
172
176
  WARM_WHITE = 25
173
177
  BRIGHT_YELLOW = 26
174
178
 
179
+ def __str__(self) -> str:
180
+ return self.name
181
+
175
182
 
176
183
  class ColorLogicPowerState(PrettyEnum):
177
184
  OFF = 0
@@ -187,6 +194,9 @@ class ColorLogicLightType(str, PrettyEnum):
187
194
  FOUR_ZERO = "COLOR_LOGIC_4_0"
188
195
  TWO_FIVE = "COLOR_LOGIC_2_5"
189
196
 
197
+ def __str__(self) -> str:
198
+ return ColorLogicLightType[self.name].value
199
+
190
200
 
191
201
  class CSADType(str, PrettyEnum):
192
202
  ACID = "ACID"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-omnilogic-local"
3
- version = "0.15.2"
3
+ version = "0.17.0"
4
4
  description = "A library for local control of Hayward OmniHub/OmniLogic pool controllers using their local API"
5
5
  authors = [
6
6
  {name = "Chris Jowett",email = "421501+cryptk@users.noreply.github.com"},
@@ -12,11 +12,12 @@ readme = "README.md"
12
12
  requires-python = ">=3.10,<3.14"
13
13
  dependencies = [
14
14
  "pydantic (>=1.10.17)",
15
- "xmltodict (>=0.13.0,<0.14.0)"
15
+ "xmltodict (>=0.13.0,<0.14.0)",
16
+ "click (>=8.0.0,<8.1.0)",
16
17
  ]
17
18
 
18
19
  [project.scripts]
19
- omnilogic = "pyomnilogic_local.cli:main"
20
+ omnilogic = "pyomnilogic_local.cli.cli:entrypoint"
20
21
 
21
22
  [tool.poetry]
22
23
  packages = [{include = "pyomnilogic_local"}]
@@ -41,7 +42,9 @@ profile = "black"
41
42
 
42
43
  [tool.mypy]
43
44
  python_version = "3.13"
44
- plugins = "pydantic.mypy"
45
+ plugins = [
46
+ "pydantic.mypy",
47
+ ]
45
48
  follow_imports = "silent"
46
49
  strict = true
47
50
  ignore_missing_imports = true
@@ -56,6 +59,9 @@ warn_untyped_fields = true
56
59
 
57
60
  [tool.pylint.MAIN]
58
61
  py-version = "3.13"
62
+ extension-pkg-allow-list = [
63
+ "pydantic",
64
+ ]
59
65
  ignore = [
60
66
  "tests",
61
67
  ]