python-hotspring 1.0.0__py3-none-any.whl
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.
- hotspring/__init__.py +67 -0
- hotspring/const.py +237 -0
- hotspring/exceptions.py +28 -0
- hotspring/hotspring.py +487 -0
- hotspring/models.py +762 -0
- hotspring/py.typed +0 -0
- python_hotspring-1.0.0.dist-info/METADATA +208 -0
- python_hotspring-1.0.0.dist-info/RECORD +10 -0
- python_hotspring-1.0.0.dist-info/WHEEL +4 -0
- python_hotspring-1.0.0.dist-info/licenses/LICENSE +21 -0
hotspring/__init__.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Asynchronous Python client for Hot Spring Connected Spa Kit 2."""
|
|
2
|
+
|
|
3
|
+
from .const import (
|
|
4
|
+
BrightnessLevel,
|
|
5
|
+
HeatingMode,
|
|
6
|
+
JetSpeed,
|
|
7
|
+
LightColor,
|
|
8
|
+
LightWheelMode,
|
|
9
|
+
SpaFailureState,
|
|
10
|
+
TemperatureUnit,
|
|
11
|
+
)
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
HotSpringCommandError,
|
|
14
|
+
HotSpringConnectionError,
|
|
15
|
+
HotSpringConnectionTimeoutError,
|
|
16
|
+
HotSpringError,
|
|
17
|
+
HotSpringNotReadyError,
|
|
18
|
+
)
|
|
19
|
+
from .hotspring import HotSpring
|
|
20
|
+
from .models import (
|
|
21
|
+
Blower,
|
|
22
|
+
CleanCycle,
|
|
23
|
+
ConnectionStatus,
|
|
24
|
+
Diagnostics,
|
|
25
|
+
EnergySaving,
|
|
26
|
+
FreshWaterIQ,
|
|
27
|
+
Heater,
|
|
28
|
+
Jet,
|
|
29
|
+
LightZone,
|
|
30
|
+
LogoLight,
|
|
31
|
+
Spa,
|
|
32
|
+
SpaInfo,
|
|
33
|
+
SpaLock,
|
|
34
|
+
Versions,
|
|
35
|
+
WaterCare,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"Blower",
|
|
40
|
+
"BrightnessLevel",
|
|
41
|
+
"CleanCycle",
|
|
42
|
+
"ConnectionStatus",
|
|
43
|
+
"Diagnostics",
|
|
44
|
+
"EnergySaving",
|
|
45
|
+
"FreshWaterIQ",
|
|
46
|
+
"Heater",
|
|
47
|
+
"HeatingMode",
|
|
48
|
+
"HotSpring",
|
|
49
|
+
"HotSpringCommandError",
|
|
50
|
+
"HotSpringConnectionError",
|
|
51
|
+
"HotSpringConnectionTimeoutError",
|
|
52
|
+
"HotSpringError",
|
|
53
|
+
"HotSpringNotReadyError",
|
|
54
|
+
"Jet",
|
|
55
|
+
"JetSpeed",
|
|
56
|
+
"LightColor",
|
|
57
|
+
"LightWheelMode",
|
|
58
|
+
"LightZone",
|
|
59
|
+
"LogoLight",
|
|
60
|
+
"Spa",
|
|
61
|
+
"SpaFailureState",
|
|
62
|
+
"SpaInfo",
|
|
63
|
+
"SpaLock",
|
|
64
|
+
"TemperatureUnit",
|
|
65
|
+
"Versions",
|
|
66
|
+
"WaterCare",
|
|
67
|
+
]
|
hotspring/const.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Constants and enumerations for Hot Spring Connected Spa Kit 2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HeatingMode(Enum):
|
|
9
|
+
"""Heating mode for the spa heater.
|
|
10
|
+
|
|
11
|
+
Controls how the spa manages water temperature regulation.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
UNKNOWN = "unknown"
|
|
15
|
+
INVALID = "invalid"
|
|
16
|
+
HEAT_SAVER = "heatSaver"
|
|
17
|
+
HEAT_WITH_BOOST = "heatWithBoost"
|
|
18
|
+
CHILL = "chill"
|
|
19
|
+
AUTO_WITH_BOOST = "autoWithBoost"
|
|
20
|
+
AUTO_SAVER = "autoSaver"
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def build(cls, value: str | None) -> HeatingMode:
|
|
24
|
+
"""Parse a raw API string into a HeatingMode.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
----
|
|
28
|
+
value: The raw heating mode string from the API, or None.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
-------
|
|
32
|
+
The matching HeatingMode, or HeatingMode.UNKNOWN for
|
|
33
|
+
unrecognised values.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
if value is None:
|
|
37
|
+
return cls.UNKNOWN
|
|
38
|
+
return _HEATING_MODE_MAP.get(value, cls.UNKNOWN)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_HEATING_MODE_MAP: dict[str, HeatingMode] = {m.value: m for m in HeatingMode}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class JetSpeed(Enum):
|
|
45
|
+
"""Speed setting for a spa jet pump.
|
|
46
|
+
|
|
47
|
+
Jets can be single-speed or multi-speed depending on the spa model.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
UNKNOWN = "unknown"
|
|
51
|
+
OFF = "off"
|
|
52
|
+
LOW_SPEED = "lowSpeed"
|
|
53
|
+
HIGH_SPEED = "highSpeed"
|
|
54
|
+
SINGLE_SPEED = "singleSpeed"
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def build(cls, value: str | None) -> JetSpeed:
|
|
58
|
+
"""Parse a raw API string into a JetSpeed.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
----
|
|
62
|
+
value: The raw jet speed string from the API, or None.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
-------
|
|
66
|
+
The matching JetSpeed, or JetSpeed.UNKNOWN for
|
|
67
|
+
unrecognised values.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
if value is None:
|
|
71
|
+
return cls.UNKNOWN
|
|
72
|
+
return _JET_SPEED_MAP.get(value, cls.UNKNOWN)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_JET_SPEED_MAP: dict[str, JetSpeed] = {s.value: s for s in JetSpeed}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class LightColor(Enum):
|
|
79
|
+
"""Color setting for a spa light zone.
|
|
80
|
+
|
|
81
|
+
Represents the available color options for multi-zone LED lighting.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
UNKNOWN = "unknown"
|
|
85
|
+
OFF = "WHEEL_OFF"
|
|
86
|
+
ON = "WHEEL_ON"
|
|
87
|
+
RED = "RED"
|
|
88
|
+
BLUE = "BLUE"
|
|
89
|
+
GREEN = "GREEN"
|
|
90
|
+
YELLOW = "YELLOW"
|
|
91
|
+
WHITE = "WHITE"
|
|
92
|
+
AQUA = "AQUA"
|
|
93
|
+
MAGENTA = "MAGENTA"
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def build(cls, value: str | None) -> LightColor:
|
|
97
|
+
"""Parse a raw API string into a LightColor.
|
|
98
|
+
|
|
99
|
+
Case-insensitive matching (real API returns e.g. "BLUE").
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
----
|
|
103
|
+
value: The raw color string from the API, or None.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
-------
|
|
107
|
+
The matching LightColor, or LightColor.UNKNOWN for
|
|
108
|
+
unrecognised values.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if value is None:
|
|
112
|
+
return cls.UNKNOWN
|
|
113
|
+
return _LIGHT_COLOR_MAP.get(value.upper(), cls.UNKNOWN)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_LIGHT_COLOR_MAP: dict[str, LightColor] = {c.value.upper(): c for c in LightColor}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LightWheelMode(Enum):
|
|
120
|
+
"""Mode for the color light wheel loop."""
|
|
121
|
+
|
|
122
|
+
UNKNOWN = "unknown"
|
|
123
|
+
OFF = "off"
|
|
124
|
+
ON = "on"
|
|
125
|
+
LOOP_UP = "loopUp"
|
|
126
|
+
LOOP_DOWN = "loopDown"
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def build(cls, value: str | None) -> LightWheelMode:
|
|
130
|
+
"""Parse a raw API string into a LightWheelMode.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
----
|
|
134
|
+
value: The raw light wheel string from the API, or None.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
-------
|
|
138
|
+
The matching LightWheelMode, or LightWheelMode.UNKNOWN for
|
|
139
|
+
unrecognised values.
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
if value is None:
|
|
143
|
+
return cls.UNKNOWN
|
|
144
|
+
return _LIGHT_WHEEL_MAP.get(value, cls.UNKNOWN)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
_LIGHT_WHEEL_MAP: dict[str, LightWheelMode] = {w.value: w for w in LightWheelMode}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class BrightnessLevel(Enum):
|
|
151
|
+
"""Brightness level for the spa logo light.
|
|
152
|
+
|
|
153
|
+
The logo light supports a limited set of discrete brightness levels.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
UNKNOWN = "unknown"
|
|
157
|
+
LEVEL_1 = "brightness_level_1"
|
|
158
|
+
LEVEL_2 = "brightness_level_2"
|
|
159
|
+
LEVEL_3 = "brightness_level_3"
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def build(cls, value: str | None) -> BrightnessLevel:
|
|
163
|
+
"""Parse a raw API string into a BrightnessLevel.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
----
|
|
167
|
+
value: The raw brightness string from the API, or None.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
-------
|
|
171
|
+
The matching BrightnessLevel, or BrightnessLevel.UNKNOWN for
|
|
172
|
+
unrecognised values.
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if value is None:
|
|
176
|
+
return cls.UNKNOWN
|
|
177
|
+
return _BRIGHTNESS_MAP.get(value, cls.UNKNOWN)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
_BRIGHTNESS_MAP: dict[str, BrightnessLevel] = {b.value: b for b in BrightnessLevel}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TemperatureUnit(Enum):
|
|
184
|
+
"""Unit of temperature measurement used by the spa."""
|
|
185
|
+
|
|
186
|
+
UNKNOWN = "unknown"
|
|
187
|
+
FAHRENHEIT = "DegF"
|
|
188
|
+
CELSIUS = "DegC"
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def build(cls, value: str | None) -> TemperatureUnit:
|
|
192
|
+
"""Parse a raw API string into a TemperatureUnit.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
----
|
|
196
|
+
value: The raw temperature unit string from the API, or None.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
-------
|
|
200
|
+
The matching TemperatureUnit, or TemperatureUnit.UNKNOWN for
|
|
201
|
+
unrecognised values.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
if value is None:
|
|
205
|
+
return cls.UNKNOWN
|
|
206
|
+
return _TEMP_UNIT_MAP.get(value, cls.UNKNOWN)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
_TEMP_UNIT_MAP: dict[str, TemperatureUnit] = {t.value: t for t in TemperatureUnit}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class SpaFailureState(Enum):
|
|
213
|
+
"""Failure state of the spa as reported by diagnostics."""
|
|
214
|
+
|
|
215
|
+
UNKNOWN = "unknown"
|
|
216
|
+
OK = "Spa_Ok"
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def build(cls, value: str | None) -> SpaFailureState:
|
|
220
|
+
"""Parse a raw API string into a SpaFailureState.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
----
|
|
224
|
+
value: The raw failure state string from the API, or None.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
-------
|
|
228
|
+
The matching SpaFailureState, or SpaFailureState.UNKNOWN for
|
|
229
|
+
unrecognised values.
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
if value is None:
|
|
233
|
+
return cls.UNKNOWN
|
|
234
|
+
return _FAILURE_STATE_MAP.get(value, cls.UNKNOWN)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
_FAILURE_STATE_MAP: dict[str, SpaFailureState] = {s.value: s for s in SpaFailureState}
|
hotspring/exceptions.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Exceptions for Hot Spring Connected Spa Kit 2."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HotSpringError(Exception):
|
|
5
|
+
"""Generic Hot Spring exception."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HotSpringConnectionError(HotSpringError):
|
|
9
|
+
"""Hot Spring connection exception."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HotSpringConnectionTimeoutError(HotSpringConnectionError):
|
|
13
|
+
"""Hot Spring connection timeout exception."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HotSpringNotReadyError(HotSpringError):
|
|
17
|
+
"""Hot Spring SNA not ready exception.
|
|
18
|
+
|
|
19
|
+
Raised when the LoRA bridge between the HNA (house) and SNA (spa)
|
|
20
|
+
is not connected. Commands will fail and status data may be stale.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HotSpringCommandError(HotSpringError):
|
|
25
|
+
"""Hot Spring command error exception.
|
|
26
|
+
|
|
27
|
+
Raised when a command sent to the spa is rejected or fails.
|
|
28
|
+
"""
|