python-hotspring 2.1.0__tar.gz → 3.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hotspring
3
- Version: 2.1.0
3
+ Version: 3.0.0
4
4
  Summary: Asynchronous Python client for Hot Spring Connected Spa Kit 2.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -46,6 +46,10 @@ Asynchronous Python client for Hot Spring Connected Spa Kit 2.
46
46
  > This library is currently in heavy development. Not all features might work
47
47
  > perfectly, and some features may not be fully tested as they depend on the
48
48
  > physical hardware available for testing.
49
+ >
50
+ > The spa's energy consumption estimates (watts, power draw) are not accurate
51
+ > and are most likely incorrect, as the power calculation registers have not
52
+ > been fully reverse-engineered.
49
53
 
50
54
  ## About
51
55
 
@@ -66,18 +70,13 @@ It is primarily designed to be used as the communication layer for an official
66
70
  plus the logo light
67
71
  - **Water care** — Monitor FreshWater IQ salt system metrics (pH, chlorine,
68
72
  ORP, sensor life)
69
- - **Diagnostics** — Read voltage, power consumption, and failure states
73
+ - **Diagnostics & Test Metrics** — Read raw hardware test point currents, line voltages,
74
+ sensor flow/switch states, and failure diagnostics
75
+ - **Runtime Tracking** — Cumulative heater runtime and individual jet pump runtime in hours
70
76
  - **Connection monitoring** — Check LoRA bridge and cloud connectivity status
71
77
  - **Energy saving schedules** — View configured energy saving time windows
72
78
  - **Clean cycle** — Start or stop the 10-minute clean cycle
73
79
 
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
80
  ### Compatible Spas
82
81
 
83
82
  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 voltage, power consumption, and failure states
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 = "2.1.0"
25
+ # The CI/CD and GitHub Actions release workflows are responsible for setting the version tag
26
+ version = "3.0.0"
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
- return _BRIGHTNESS_MAP.get(value, cls.UNKNOWN)
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):