diff --git a/switchbot/adv_parser.py b/switchbot/adv_parser.py index 0914a1cb..59645582 100644 --- a/switchbot/adv_parser.py +++ b/switchbot/adv_parser.py @@ -15,6 +15,7 @@ from .adv_parsers.bot import process_wohand from .adv_parsers.bulb import process_color_bulb from .adv_parsers.ceiling_light import process_woceiling +from .adv_parsers.climate_panel import process_climate_panel from .adv_parsers.contact import process_wocontact from .adv_parsers.curtain import process_wocurtain from .adv_parsers.fan import process_fan @@ -370,6 +371,12 @@ class SwitchbotSupportedType(TypedDict): "func": process_vacuum, "manufacturer_id": 2409, }, + b"\x00\x10\xf3\xd8": { + "modelName": SwitchbotModel.CLIMATE_PANEL, + "modelFriendlyName": "Climate Panel", + "func": process_climate_panel, + "manufacturer_id": 2409, + }, } _SWITCHBOT_MODEL_TO_CHAR = { @@ -452,12 +459,12 @@ def _parse_data( if model_data.get("manufacturer_data_length") == len(_mfr_data): _model = model_chr break - if ( - _service_data - and len(_service_data) > 5 - and _service_data[-4:] in SUPPORTED_TYPES - ): - _model = _service_data[-4:] + + if _service_data and len(_service_data) > 5: + for s in (_service_data[-4:], _service_data[-5:-1]): + if s in SUPPORTED_TYPES: + _model = s + break if not _model: return None diff --git a/switchbot/adv_parsers/climate_panel.py b/switchbot/adv_parsers/climate_panel.py new file mode 100644 index 00000000..8c1c3d69 --- /dev/null +++ b/switchbot/adv_parsers/climate_panel.py @@ -0,0 +1,46 @@ +"""Advertisement data parser for climate panel devices.""" + +import logging + +_LOGGER = logging.getLogger(__name__) + + +def process_climate_panel( + data: bytes | None, mfr_data: bytes | None +) -> dict[str, bool | int | str]: + """Process Climate Panel data.""" + if mfr_data is None: + return {} + + seq_number = mfr_data[6] + isOn = bool(mfr_data[7] & 0x80) + battery = mfr_data[7] & 0x7F + humidity_alarm = (mfr_data[8] >> 6) & 0x03 + temp_alarm = (mfr_data[8] >> 4) & 0x03 + + temp_decimal = mfr_data[8] & 0x0F + temp_sign = 1 if (mfr_data[9] & 0x80) else -1 + temp_int = mfr_data[9] & 0x7F + temperature = temp_sign * (temp_int + temp_decimal / 10) + + humidity = mfr_data[10] & 0x7F + + pir_state = bool(mfr_data[15] & 0x80) + is_light = ((mfr_data[15] >> 2) & 0x03) == 0x10 + + result = { + "sequence_number": seq_number, + "isOn": isOn, + "battery": battery, + "temperature": temperature, + "humidity": humidity, + "temp_alarm": temp_alarm, + "humidity_alarm": humidity_alarm, + "motion_detected": pir_state, + "is_light": is_light, + } + + _LOGGER.debug( + "Processed climate panel mfr data: %s, result: %s", mfr_data.hex(), result + ) + return result diff --git a/switchbot/const/__init__.py b/switchbot/const/__init__.py index 688456ee..6b2191a6 100644 --- a/switchbot/const/__init__.py +++ b/switchbot/const/__init__.py @@ -97,6 +97,7 @@ class SwitchbotModel(StrEnum): RGBICWW_STRIP_LIGHT = "RGBICWW Strip Light" RGBICWW_FLOOR_LAMP = "RGBICWW Floor Lamp" K11_VACUUM = "K11+ Vacuum" + CLIMATE_PANEL = "Climate Panel" __all__ = [ diff --git a/tests/test_adv_parser.py b/tests/test_adv_parser.py index 3623b5c4..ee6008c5 100644 --- a/tests/test_adv_parser.py +++ b/tests/test_adv_parser.py @@ -3430,6 +3430,24 @@ def test_humidifer_with_empty_data() -> None: "K11+ Vacuum", SwitchbotModel.K11_VACUUM, ), + AdvTestCase( + b"\xb0\xe9\xfe\x8e\x98Oi_\x06\x9a,\x00\x00\x00\x00\xe4\x00\x08\x04\x00\x01\x00\x00", + b"\x00 _\x00\x10\xf3\xd8@", + { + "battery": 95, + "humidity": 44, + "sequence_number": 105, + "humidity_alarm": 0, + "isOn": False, + "is_light": False, + "motion_detected": True, + "temp_alarm": 0, + "temperature": 26.6, + }, + b"\x00\x10\xf3\xd8", + "Climate Panel", + SwitchbotModel.CLIMATE_PANEL, + ), ], ) def test_adv_active(test_case: AdvTestCase) -> None: @@ -3644,6 +3662,24 @@ def test_adv_active(test_case: AdvTestCase) -> None: "K11+ Vacuum", SwitchbotModel.K11_VACUUM, ), + AdvTestCase( + b"\xb0\xe9\xfe\x8e\x98Oi_\x06\x9a,\x00\x00\x00\x00\xe4\x00\x08\x04\x00\x01\x00\x00", + None, + { + "battery": 95, + "humidity": 44, + "sequence_number": 105, + "humidity_alarm": 0, + "isOn": False, + "is_light": False, + "motion_detected": True, + "temp_alarm": 0, + "temperature": 26.6, + }, + b"\x00\x10\xf3\xd8", + "Climate Panel", + SwitchbotModel.CLIMATE_PANEL, + ), ], ) def test_adv_passive(test_case: AdvTestCase) -> None: @@ -3809,6 +3845,14 @@ def test_adv_passive(test_case: AdvTestCase) -> None: "K11+ Vacuum", SwitchbotModel.K11_VACUUM, ), + AdvTestCase( + None, + b"\x00 _\x00\x10\xf3\xd8@", + {}, + b"\x00\x10\xf3\xd8", + "Climate Panel", + SwitchbotModel.CLIMATE_PANEL, + ), ], ) def test_adv_with_empty_data(test_case: AdvTestCase) -> None: