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
2 changes: 2 additions & 0 deletions switchbot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from .devices.evaporative_humidifier import SwitchbotEvaporativeHumidifier
from .devices.fan import SwitchbotFan, SwitchbotStandingFan
from .devices.humidifier import SwitchbotHumidifier
from .devices.keypad import SwitchbotKeypad
from .devices.keypad_vision import SwitchbotKeypadVision
from .devices.light_strip import (
SwitchbotCandleWarmerLamp,
Expand Down Expand Up @@ -112,6 +113,7 @@
"SwitchbotFan",
"SwitchbotGarageDoorOpener",
"SwitchbotHumidifier",
"SwitchbotKeypad",
"SwitchbotKeypadVision",
"SwitchbotLightStrip",
"SwitchbotLock",
Expand Down
18 changes: 18 additions & 0 deletions switchbot/devices/keypad.py
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
"""Keypad device handling."""

from __future__ import annotations

from .device import SwitchbotDevice


class SwitchbotKeypad(SwitchbotDevice):
"""
Representation of a Switchbot Keypad (WoKeypad) device.

Passive BLE-only — no commands. Battery and attempt_state come from
advertisement parsing in adv_parsers/keypad.py.
"""

@property
def attempt_state(self) -> int | None:
"""Return the last attempt state from advertisement data."""
return self._get_adv_value("attempt_state")
13 changes: 13 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ class AdvTestCase:
modelName: SwitchbotModel


KEYPAD_INFO = AdvTestCase(
b"\xeb\x13\x02\xe6#\x0f\x8fd\x00\x00\x00\x00",
b"y\x00d",
{
"battery": 100,
"attempt_state": 143,
},
"y",
"Keypad",
SwitchbotModel.KEYPAD,
)


STRIP_LIGHT_3_INFO = AdvTestCase(
b'\xc0N0\xe0U\x9a\x85\x9e"\xd0\x00\x00\x00\x00\x00\x00\x12\x91\x00',
b"\x00\x00\x00\x00\x10\xd0\xb1",
Expand Down
59 changes: 59 additions & 0 deletions tests/test_keypad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Test keypad device advertisement parsing."""

from switchbot import SwitchbotKeypad, SwitchbotModel
from switchbot.adv_parser import parse_advertisement_data

from . import KEYPAD_INFO
from .test_adv_parser import generate_advertisement_data, generate_ble_device


def test_keypad_advertisement_battery() -> None:
"""Test that battery is parsed from keypad advertisement data."""
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
adv_data = generate_advertisement_data(
manufacturer_data={2409: KEYPAD_INFO.manufacturer_data},
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": KEYPAD_INFO.service_data},
rssi=-80,
)
advertisement = parse_advertisement_data(
ble_device, adv_data, SwitchbotModel.KEYPAD
)
device = SwitchbotKeypad(ble_device)
device.update_from_advertisement(advertisement)

assert device.get_battery_percent() == 100


def test_keypad_advertisement_attempt_state() -> None:
"""Test that attempt_state is parsed from keypad advertisement data."""
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
adv_data = generate_advertisement_data(
manufacturer_data={2409: KEYPAD_INFO.manufacturer_data},
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": KEYPAD_INFO.service_data},
rssi=-80,
)
advertisement = parse_advertisement_data(
ble_device, adv_data, SwitchbotModel.KEYPAD
)
device = SwitchbotKeypad(ble_device)
device.update_from_advertisement(advertisement)

assert device.attempt_state == 143


def test_keypad_advertisement_battery_none_when_no_data() -> None:
"""Test that battery is None when advertisement data is missing."""
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
adv_data = generate_advertisement_data(
manufacturer_data={},
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": KEYPAD_INFO.service_data},
rssi=-80,
)
advertisement = parse_advertisement_data(
ble_device, adv_data, SwitchbotModel.KEYPAD
)
device = SwitchbotKeypad(ble_device)
device.update_from_advertisement(advertisement)

assert device.get_battery_percent() is None
assert device.attempt_state is None
Loading