Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions switchbot/adv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
from .adv_parsers.motion import process_wopresence
from .adv_parsers.plug import process_woplugmini
from .adv_parsers.presence_sensor import process_presence_sensor
from .adv_parsers.relay_switch import (
process_garage_door_opener,
process_relay_switch_1pm,
Expand Down Expand Up @@ -384,6 +385,12 @@ class SwitchbotSupportedType(TypedDict):
"func": process_smart_thermostat_radiator,
"manufacturer_id": 2409,
},
b"\x00\x10\xcc\xc8": {
"modelName": SwitchbotModel.PRESENCE_SENSOR,
"modelFriendlyName": "Presence Sensor",
"func": process_presence_sensor,
"manufacturer_id": 2409,
},
}

_SWITCHBOT_MODEL_TO_CHAR = {
Expand Down
43 changes: 43 additions & 0 deletions switchbot/adv_parsers/presence_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Advertisement data parser for presence sensor devices."""

import logging

from ..const.presence_sensor import BATTERY_LEVEL_MAP

_LOGGER = logging.getLogger(__name__)


def process_presence_sensor(
data: bytes | None, mfr_data: bytes | None
) -> dict[str, bool | int | str]:
"""Process Presence Sensor data."""
if mfr_data is None:
return {}

seq_number = mfr_data[6]
adaptive_state = bool(mfr_data[7] & 0x80)
motion_detected = bool(mfr_data[7] & 0x40)
battery_bits = (mfr_data[7] >> 2) & 0x03
battery_range = BATTERY_LEVEL_MAP.get(battery_bits, "Unknown")
trigger_flag = mfr_data[10]
led_state = bool(mfr_data[11] & 0x80)
light_level = mfr_data[11] & 0x0F

result = {
"sequence_number": seq_number,
"adaptive_state": adaptive_state,
"motion_detected": motion_detected,
"battery_range": battery_range,
"trigger_flag": trigger_flag,
"led_state": led_state,
"lightLevel": light_level,
}

if data:
battery = data[2] & 0x7F
result["battery"] = battery

_LOGGER.debug(
"Processed presence sensor mfr data: %s, result: %s", mfr_data.hex(), result
)
return result
1 change: 1 addition & 0 deletions switchbot/const/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class SwitchbotModel(StrEnum):
K11_VACUUM = "K11+ Vacuum"
CLIMATE_PANEL = "Climate Panel"
SMART_THERMOSTAT_RADIATOR = "Smart Thermostat Radiator"
PRESENCE_SENSOR = "Presence Sensor"


__all__ = [
Expand Down
8 changes: 8 additions & 0 deletions switchbot/const/presence_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Constants for presence sensor devices."""

BATTERY_LEVEL_MAP = {
0: "<10%",
1: "10-19%",
2: "20-59%",
3: ">=60%",
}
1 change: 1 addition & 0 deletions switchbot/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def _extract_region(userinfo: dict[str, Any]) -> str:
"W1083002": SwitchbotModel.RELAY_SWITCH_1, # Relay Switch 1
"W1079000": SwitchbotModel.METER_PRO, # Meter Pro (another variant)
"W1102001": SwitchbotModel.STRIP_LIGHT_3, # RGBWW Strip Light 3
"W1101000": SwitchbotModel.PRESENCE_SENSOR,
}

REQ_HEADER = "570f"
Expand Down
41 changes: 41 additions & 0 deletions tests/test_adv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3468,6 +3468,23 @@ def test_humidifer_with_empty_data() -> None:
"Smart Thermostat Radiator",
SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
),
AdvTestCase(
b"\xb0\xe9\xfelf\xaa\x06\xcc\x04V\x00\x8c",
b"\x00 d\x00\x10\xcc\xc8",
{
"adaptive_state": True,
"battery": 100,
"battery_range": ">=60%",
"led_state": True,
"lightLevel": 12,
"motion_detected": True,
"sequence_number": 6,
"trigger_flag": 0,
},
b"\x00\x10\xcc\xc8",
"Presence Sensor",
SwitchbotModel.PRESENCE_SENSOR,
),
],
)
def test_adv_active(test_case: AdvTestCase) -> None:
Expand Down Expand Up @@ -3720,6 +3737,22 @@ def test_adv_active(test_case: AdvTestCase) -> None:
"Smart Thermostat Radiator",
SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
),
AdvTestCase(
b"\xb0\xe9\xfelf\xaa\x06\xcc\x04V\x00\x8c",
None,
{
"adaptive_state": True,
"battery_range": ">=60%",
"led_state": True,
"lightLevel": 12,
"motion_detected": True,
"sequence_number": 6,
"trigger_flag": 0,
},
b"\x00\x10\xcc\xc8",
"Presence Sensor",
SwitchbotModel.PRESENCE_SENSOR,
),
],
)
def test_adv_passive(test_case: AdvTestCase) -> None:
Expand Down Expand Up @@ -3901,6 +3934,14 @@ def test_adv_passive(test_case: AdvTestCase) -> None:
"Smart Thermostat Radiator",
SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
),
AdvTestCase(
None,
b"\x00 d\x00\x10\xcc\xc8",
{},
b"\x00\x10\xcc\xc8",
"Presence Sensor",
SwitchbotModel.PRESENCE_SENSOR,
),
],
)
def test_adv_with_empty_data(test_case: AdvTestCase) -> None:
Expand Down