Skip to content

Commit 1d6fe62

Browse files
Add support for Smart Thermostat Radiator (#402)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9d8a9c2 commit 1d6fe62

File tree

9 files changed

+660
-0
lines changed

9 files changed

+660
-0
lines changed

switchbot/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
AirPurifierMode,
1414
BulbColorMode,
1515
CeilingLightColorMode,
16+
ClimateAction,
17+
ClimateMode,
1618
ColorMode,
1719
FanMode,
1820
HumidifierAction,
1921
HumidifierMode,
2022
HumidifierWaterLevel,
2123
LockStatus,
24+
SmartThermostatRadiatorMode,
2225
StripLightColorMode,
2326
SwitchbotAccountConnectionError,
2427
SwitchbotApiError,
@@ -54,6 +57,7 @@
5457
SwitchbotRelaySwitch2PM,
5558
)
5659
from .devices.roller_shade import SwitchbotRollerShade
60+
from .devices.smart_thermostat_radiator import SwitchbotSmartThermostatRadiator
5761
from .devices.vacuum import SwitchbotVacuum
5862
from .discovery import GetSwitchbotDevices
5963
from .models import SwitchBotAdvertisement
@@ -62,13 +66,16 @@
6266
"AirPurifierMode",
6367
"BulbColorMode",
6468
"CeilingLightColorMode",
69+
"ClimateAction",
70+
"ClimateMode",
6571
"ColorMode",
6672
"FanMode",
6773
"GetSwitchbotDevices",
6874
"HumidifierAction",
6975
"HumidifierMode",
7076
"HumidifierWaterLevel",
7177
"LockStatus",
78+
"SmartThermostatRadiatorMode",
7279
"StripLightColorMode",
7380
"SwitchBotAdvertisement",
7481
"Switchbot",
@@ -99,6 +106,7 @@
99106
"SwitchbotRelaySwitch2PM",
100107
"SwitchbotRgbicLight",
101108
"SwitchbotRollerShade",
109+
"SwitchbotSmartThermostatRadiator",
102110
"SwitchbotStripLight3",
103111
"SwitchbotSupportedType",
104112
"SwitchbotSupportedType",

switchbot/adv_parser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
)
4444
from .adv_parsers.remote import process_woremote
4545
from .adv_parsers.roller_shade import process_worollershade
46+
from .adv_parsers.smart_thermostat_radiator import process_smart_thermostat_radiator
4647
from .adv_parsers.vacuum import process_vacuum, process_vacuum_k
4748
from .const import SwitchbotModel
4849
from .models import SwitchBotAdvertisement
@@ -377,6 +378,12 @@ class SwitchbotSupportedType(TypedDict):
377378
"func": process_climate_panel,
378379
"manufacturer_id": 2409,
379380
},
381+
b"\x00\x116@": {
382+
"modelName": SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
383+
"modelFriendlyName": "Smart Thermostat Radiator",
384+
"func": process_smart_thermostat_radiator,
385+
"manufacturer_id": 2409,
386+
},
380387
}
381388

382389
_SWITCHBOT_MODEL_TO_CHAR = {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Smart Thermostat Radiator"""
2+
3+
import logging
4+
5+
from ..const.climate import SmartThermostatRadiatorMode
6+
7+
_LOGGER = logging.getLogger(__name__)
8+
9+
10+
def process_smart_thermostat_radiator(
11+
data: bytes | None, mfr_data: bytes | None
12+
) -> dict[str, bool | int | str]:
13+
"""Process Smart Thermostat Radiator data."""
14+
if mfr_data is None:
15+
return {}
16+
17+
_seq_num = mfr_data[6]
18+
_isOn = bool(mfr_data[7] & 0b10000000)
19+
_battery = mfr_data[7] & 0b01111111
20+
21+
temp_data = mfr_data[8:11]
22+
target_decimal = (temp_data[0] >> 4) & 0x0F
23+
local_decimal = temp_data[0] & 0x0F
24+
25+
local_sign = 1 if (temp_data[1] & 0x80) else -1
26+
local_int = temp_data[1] & 0x7F
27+
local_temp = local_sign * (local_int + (local_decimal / 10))
28+
29+
target_sign = 1 if (temp_data[2] & 0x80) else -1
30+
target_int = temp_data[2] & 0x7F
31+
target_temp = target_sign * (target_int + (target_decimal / 10))
32+
33+
last_mode = SmartThermostatRadiatorMode.get_mode_name((mfr_data[11] >> 4) & 0x0F)
34+
mode = SmartThermostatRadiatorMode.get_mode_name(mfr_data[11] & 0x07)
35+
36+
need_update_temp = bool((mfr_data[12] >> 5) & 0x01)
37+
restarted = bool((mfr_data[12] >> 4) & 0x01)
38+
fault_code = (mfr_data[12] >> 1) & 0x07
39+
door_open = bool(mfr_data[12] & 0x01)
40+
41+
result = {
42+
"sequence_number": _seq_num,
43+
"isOn": _isOn,
44+
"battery": _battery,
45+
"temperature": local_temp,
46+
"target_temperature": target_temp,
47+
"mode": mode,
48+
"last_mode": last_mode,
49+
"need_update_temp": need_update_temp,
50+
"restarted": restarted,
51+
"fault_code": fault_code,
52+
"door_open": door_open,
53+
}
54+
55+
_LOGGER.debug(
56+
"Smart Thermostat Radiator mfr data: %s, result: %s", mfr_data.hex(), result
57+
)
58+
return result

switchbot/const/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ..enum import StrEnum
66
from .air_purifier import AirPurifierMode
7+
from .climate import ClimateAction, ClimateMode, SmartThermostatRadiatorMode
78
from .evaporative_humidifier import (
89
HumidifierAction,
910
HumidifierMode,
@@ -98,6 +99,7 @@ class SwitchbotModel(StrEnum):
9899
RGBICWW_FLOOR_LAMP = "RGBICWW Floor Lamp"
99100
K11_VACUUM = "K11+ Vacuum"
100101
CLIMATE_PANEL = "Climate Panel"
102+
SMART_THERMOSTAT_RADIATOR = "Smart Thermostat Radiator"
101103

102104

103105
__all__ = [
@@ -107,12 +109,15 @@ class SwitchbotModel(StrEnum):
107109
"AirPurifierMode",
108110
"BulbColorMode",
109111
"CeilingLightColorMode",
112+
"ClimateAction",
113+
"ClimateMode",
110114
"ColorMode",
111115
"FanMode",
112116
"HumidifierAction",
113117
"HumidifierMode",
114118
"HumidifierWaterLevel",
115119
"LockStatus",
120+
"SmartThermostatRadiatorMode",
116121
"StripLightColorMode",
117122
"SwitchbotAccountConnectionError",
118123
"SwitchbotApiError",

switchbot/const/climate.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Representation of climate-related constants."""
2+
3+
from enum import Enum
4+
5+
6+
class ClimateMode(Enum):
7+
"""Climate Modes."""
8+
9+
OFF = 0
10+
HEAT = 1
11+
COOL = 2
12+
HEAT_COOL = 3
13+
AUTO = 4
14+
DRY = 5
15+
FAN_ONLY = 6
16+
17+
18+
class ClimateAction(Enum):
19+
"""Climate Actions."""
20+
21+
OFF = 0
22+
IDLE = 1
23+
HEATING = 2
24+
25+
26+
class SmartThermostatRadiatorMode(Enum):
27+
"""Smart Thermostat Radiator Modes."""
28+
29+
SCHEDULE = 0
30+
MANUAL = 1
31+
OFF = 2
32+
ECONOMIC = 3
33+
COMFORT = 4
34+
FAST_HEATING = 5
35+
36+
@property
37+
def lname(self) -> str:
38+
return self.name.lower()
39+
40+
@classmethod
41+
def get_modes(cls) -> list[str]:
42+
return [mode.lname for mode in cls]
43+
44+
@classmethod
45+
def get_mode_name(cls, mode_value: int) -> str:
46+
return cls(mode_value).lname
47+
48+
@classmethod
49+
def get_valid_modes(cls) -> list[str]:
50+
return [mode.lname for mode in cls if mode != cls.OFF]

0 commit comments

Comments
 (0)