python-hotspring 2.1.0__tar.gz → 3.0.1__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.
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/PKG-INFO +9 -9
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/README.md +7 -8
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/pyproject.toml +3 -1
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/__init__.py +6 -0
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/const.py +71 -2
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/hotspring.py +309 -72
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/models.py +317 -112
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/LICENSE +0 -0
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/exceptions.py +0 -0
- {python_hotspring-2.1.0 → python_hotspring-3.0.1}/src/hotspring/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hotspring
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Asynchronous Python client for Hot Spring Connected Spa Kit 2.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
22
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
24
|
Requires-Dist: aiohttp (>=3.0.0)
|
|
24
25
|
Requires-Dist: awesomeversion (>=22.1.0)
|
|
@@ -46,6 +47,10 @@ Asynchronous Python client for Hot Spring Connected Spa Kit 2.
|
|
|
46
47
|
> This library is currently in heavy development. Not all features might work
|
|
47
48
|
> perfectly, and some features may not be fully tested as they depend on the
|
|
48
49
|
> physical hardware available for testing.
|
|
50
|
+
>
|
|
51
|
+
> The spa's energy consumption estimates (watts, power draw) are not accurate
|
|
52
|
+
> and are most likely incorrect, as the power calculation registers have not
|
|
53
|
+
> been fully reverse-engineered.
|
|
49
54
|
|
|
50
55
|
## About
|
|
51
56
|
|
|
@@ -66,18 +71,13 @@ It is primarily designed to be used as the communication layer for an official
|
|
|
66
71
|
plus the logo light
|
|
67
72
|
- **Water care** — Monitor FreshWater IQ salt system metrics (pH, chlorine,
|
|
68
73
|
ORP, sensor life)
|
|
69
|
-
- **Diagnostics** — Read
|
|
74
|
+
- **Diagnostics & Test Metrics** — Read raw hardware test point currents, line voltages,
|
|
75
|
+
sensor flow/switch states, and failure diagnostics
|
|
76
|
+
- **Runtime Tracking** — Cumulative heater runtime and individual jet pump runtime in hours
|
|
70
77
|
- **Connection monitoring** — Check LoRA bridge and cloud connectivity status
|
|
71
78
|
- **Energy saving schedules** — View configured energy saving time windows
|
|
72
79
|
- **Clean cycle** — Start or stop the 10-minute clean cycle
|
|
73
80
|
|
|
74
|
-
> [!CAUTION]
|
|
75
|
-
> The spa's energy consumption and usage metrics are not always accurate. Most
|
|
76
|
-
> values related to power (including current, amp, volt, etc) and values based
|
|
77
|
-
> on time (like the amount of time the jet have been turned on) are most likely
|
|
78
|
-
> incorrect, as I have not been able to fully reverse engineer these aspects of
|
|
79
|
-
> the API.
|
|
80
|
-
|
|
81
81
|
### Compatible Spas
|
|
82
82
|
|
|
83
83
|
This library works with any Hot Spring, Caldera, or Freeflow spa that supports
|
|
@@ -13,6 +13,10 @@ Asynchronous Python client for Hot Spring Connected Spa Kit 2.
|
|
|
13
13
|
> This library is currently in heavy development. Not all features might work
|
|
14
14
|
> perfectly, and some features may not be fully tested as they depend on the
|
|
15
15
|
> physical hardware available for testing.
|
|
16
|
+
>
|
|
17
|
+
> The spa's energy consumption estimates (watts, power draw) are not accurate
|
|
18
|
+
> and are most likely incorrect, as the power calculation registers have not
|
|
19
|
+
> been fully reverse-engineered.
|
|
16
20
|
|
|
17
21
|
## About
|
|
18
22
|
|
|
@@ -33,18 +37,13 @@ It is primarily designed to be used as the communication layer for an official
|
|
|
33
37
|
plus the logo light
|
|
34
38
|
- **Water care** — Monitor FreshWater IQ salt system metrics (pH, chlorine,
|
|
35
39
|
ORP, sensor life)
|
|
36
|
-
- **Diagnostics** — Read
|
|
40
|
+
- **Diagnostics & Test Metrics** — Read raw hardware test point currents, line voltages,
|
|
41
|
+
sensor flow/switch states, and failure diagnostics
|
|
42
|
+
- **Runtime Tracking** — Cumulative heater runtime and individual jet pump runtime in hours
|
|
37
43
|
- **Connection monitoring** — Check LoRA bridge and cloud connectivity status
|
|
38
44
|
- **Energy saving schedules** — View configured energy saving time windows
|
|
39
45
|
- **Clean cycle** — Start or stop the 10-minute clean cycle
|
|
40
46
|
|
|
41
|
-
> [!CAUTION]
|
|
42
|
-
> The spa's energy consumption and usage metrics are not always accurate. Most
|
|
43
|
-
> values related to power (including current, amp, volt, etc) and values based
|
|
44
|
-
> on time (like the amount of time the jet have been turned on) are most likely
|
|
45
|
-
> incorrect, as I have not been able to fully reverse engineer these aspects of
|
|
46
|
-
> the API.
|
|
47
|
-
|
|
48
47
|
### Compatible Spas
|
|
49
48
|
|
|
50
49
|
This library works with any Hot Spring, Caldera, or Freeflow spa that supports
|
|
@@ -22,7 +22,8 @@ packages = [
|
|
|
22
22
|
]
|
|
23
23
|
readme = "README.md"
|
|
24
24
|
repository = "https://github.com/Moustachauve/python-hotspring"
|
|
25
|
-
version
|
|
25
|
+
# The CI/CD and GitHub Actions release workflows are responsible for setting the version tag
|
|
26
|
+
version = "3.0.1"
|
|
26
27
|
|
|
27
28
|
[tool.poetry.dependencies]
|
|
28
29
|
aiohttp = ">=3.0.0"
|
|
@@ -50,6 +51,7 @@ pytest-cov = "7.1.0"
|
|
|
50
51
|
ruff = "0.15.11"
|
|
51
52
|
safety = "3.7.0"
|
|
52
53
|
yamllint = "1.38.0"
|
|
54
|
+
syrupy = "^6.0.0"
|
|
53
55
|
|
|
54
56
|
[tool.coverage.run]
|
|
55
57
|
plugins = ["covdefaults"]
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
from .const import (
|
|
4
4
|
BrightnessLevel,
|
|
5
5
|
DeviceType,
|
|
6
|
+
EnergySavingMode,
|
|
6
7
|
HeatingMode,
|
|
7
8
|
JetSpeed,
|
|
9
|
+
JetSpeedType,
|
|
8
10
|
LightColor,
|
|
9
11
|
LightWheelMode,
|
|
10
12
|
SpaBrand,
|
|
@@ -35,6 +37,7 @@ from .models import (
|
|
|
35
37
|
Spa,
|
|
36
38
|
SpaInfo,
|
|
37
39
|
SpaLock,
|
|
40
|
+
SpaTestData,
|
|
38
41
|
Versions,
|
|
39
42
|
WaterCare,
|
|
40
43
|
)
|
|
@@ -47,6 +50,7 @@ __all__ = [
|
|
|
47
50
|
"DeviceType",
|
|
48
51
|
"Diagnostics",
|
|
49
52
|
"EnergySaving",
|
|
53
|
+
"EnergySavingMode",
|
|
50
54
|
"FreshWaterIQ",
|
|
51
55
|
"Heater",
|
|
52
56
|
"HeatingMode",
|
|
@@ -60,6 +64,7 @@ __all__ = [
|
|
|
60
64
|
"HotSpringSNADetectedError",
|
|
61
65
|
"Jet",
|
|
62
66
|
"JetSpeed",
|
|
67
|
+
"JetSpeedType",
|
|
63
68
|
"LightColor",
|
|
64
69
|
"LightWheelMode",
|
|
65
70
|
"LightZone",
|
|
@@ -69,6 +74,7 @@ __all__ = [
|
|
|
69
74
|
"SpaFailureState",
|
|
70
75
|
"SpaInfo",
|
|
71
76
|
"SpaLock",
|
|
77
|
+
"SpaTestData",
|
|
72
78
|
"TemperatureUnit",
|
|
73
79
|
"Versions",
|
|
74
80
|
"WaterCare",
|
|
@@ -75,6 +75,35 @@ class JetSpeed(Enum):
|
|
|
75
75
|
_JET_SPEED_MAP: dict[str, JetSpeed] = {s.value: s for s in JetSpeed}
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
class JetSpeedType(Enum):
|
|
79
|
+
"""Configured speed capability of a jet pump."""
|
|
80
|
+
|
|
81
|
+
UNKNOWN = "unknown"
|
|
82
|
+
SINGLE_SPEED = "singleSpeed"
|
|
83
|
+
DUAL_SPEED = "dualSpeed"
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def build(cls, value: str | None) -> JetSpeedType:
|
|
87
|
+
"""Parse a raw API string into a JetSpeedType.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
----
|
|
91
|
+
value: The raw speed configuration string from the API, or None.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
-------
|
|
95
|
+
The matching JetSpeedType, or JetSpeedType.UNKNOWN for
|
|
96
|
+
unrecognized values.
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
if value is None:
|
|
100
|
+
return cls.UNKNOWN
|
|
101
|
+
return _JET_SPEED_TYPE_MAP.get(value, cls.UNKNOWN)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_JET_SPEED_TYPE_MAP: dict[str, JetSpeedType] = {t.value: t for t in JetSpeedType}
|
|
105
|
+
|
|
106
|
+
|
|
78
107
|
class LightColor(Enum):
|
|
79
108
|
"""Color setting for a spa light zone.
|
|
80
109
|
|
|
@@ -153,12 +182,13 @@ class BrightnessLevel(Enum):
|
|
|
153
182
|
"""
|
|
154
183
|
|
|
155
184
|
UNKNOWN = "unknown"
|
|
185
|
+
AUTO = "auto"
|
|
156
186
|
LEVEL_1 = "brightness_level_1"
|
|
157
187
|
LEVEL_2 = "brightness_level_2"
|
|
158
188
|
LEVEL_3 = "brightness_level_3"
|
|
159
189
|
|
|
160
190
|
@classmethod
|
|
161
|
-
def build(cls, value: str | None) -> BrightnessLevel:
|
|
191
|
+
def build(cls, value: str | int | None) -> BrightnessLevel:
|
|
162
192
|
"""Parse a raw API string into a BrightnessLevel.
|
|
163
193
|
|
|
164
194
|
Args:
|
|
@@ -173,10 +203,49 @@ class BrightnessLevel(Enum):
|
|
|
173
203
|
"""
|
|
174
204
|
if value is None:
|
|
175
205
|
return cls.UNKNOWN
|
|
176
|
-
|
|
206
|
+
val_str = str(value).strip().lower()
|
|
207
|
+
return _BRIGHTNESS_MAP.get(val_str, cls.UNKNOWN)
|
|
177
208
|
|
|
178
209
|
|
|
179
210
|
_BRIGHTNESS_MAP: dict[str, BrightnessLevel] = {b.value: b for b in BrightnessLevel}
|
|
211
|
+
_BRIGHTNESS_MAP.update(
|
|
212
|
+
{
|
|
213
|
+
"1": BrightnessLevel.LEVEL_1,
|
|
214
|
+
"2": BrightnessLevel.LEVEL_2,
|
|
215
|
+
"3": BrightnessLevel.LEVEL_3,
|
|
216
|
+
"auto": BrightnessLevel.AUTO,
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
_BRIGHTNESS_TO_WIRE: dict[BrightnessLevel, str] = {
|
|
221
|
+
BrightnessLevel.LEVEL_1: "1",
|
|
222
|
+
BrightnessLevel.LEVEL_2: "2",
|
|
223
|
+
BrightnessLevel.LEVEL_3: "3",
|
|
224
|
+
BrightnessLevel.AUTO: "auto",
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
MAX_ENERGY_SAVING_SCHEDULES = 2
|
|
228
|
+
VALID_ENERGY_SAVING_SCHEDULE_IDS = (1, 2)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class EnergySavingMode(Enum):
|
|
232
|
+
"""Energy saving schedule mode."""
|
|
233
|
+
|
|
234
|
+
UNKNOWN = "unknown"
|
|
235
|
+
OFF = "off"
|
|
236
|
+
ON = "on"
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def build(cls, value: object) -> EnergySavingMode:
|
|
240
|
+
"""Parse raw mode from API into EnergySavingMode."""
|
|
241
|
+
if value is None:
|
|
242
|
+
return cls.UNKNOWN
|
|
243
|
+
val_str = str(value).strip().lower()
|
|
244
|
+
if val_str in ("1", "on", "enable", "true"):
|
|
245
|
+
return cls.ON
|
|
246
|
+
if val_str in ("0", "off", "disable", "false"):
|
|
247
|
+
return cls.OFF
|
|
248
|
+
return cls.UNKNOWN
|
|
180
249
|
|
|
181
250
|
|
|
182
251
|
class TemperatureUnit(Enum):
|