PySwitchbot 0.51.0__tar.gz → 0.53.0__tar.gz
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.
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PKG-INFO +1 -1
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/SOURCES.txt +4 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/setup.py +1 -1
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/__init__.py +2 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parser.py +23 -1
- pyswitchbot-0.53.0/switchbot/adv_parsers/keypad.py +22 -0
- pyswitchbot-0.53.0/switchbot/adv_parsers/relay_switch.py +31 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/const.py +4 -0
- pyswitchbot-0.53.0/switchbot/devices/motion.py +1 -0
- pyswitchbot-0.53.0/switchbot/devices/relay_switch.py +155 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/discovery.py +4 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_adv_parser.py +25 -37
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/LICENSE +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/MANIFEST.in +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/requires.txt +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/top_level.txt +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/README.md +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/setup.cfg +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/__init__.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/bot.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/bulb.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/contact.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/curtain.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/hub2.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/humidifier.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/light_strip.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/lock.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/meter.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/motion.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/plug.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/api_config.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/__init__.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/base_cover.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/base_light.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/blind_tilt.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/bot.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/bulb.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/ceiling_light.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/contact.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/curtain.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/device.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/humidifier.py +0 -0
- /pyswitchbot-0.51.0/switchbot/devices/meter.py → /pyswitchbot-0.53.0/switchbot/devices/keypad.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/light_strip.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/lock.py +0 -0
- /pyswitchbot-0.51.0/switchbot/devices/motion.py → /pyswitchbot-0.53.0/switchbot/devices/meter.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/plug.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/enum.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/models.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_base_cover.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_blind_tilt.py +0 -0
- {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_curtain.py +0 -0
|
@@ -23,11 +23,13 @@ switchbot/adv_parsers/contact.py
|
|
|
23
23
|
switchbot/adv_parsers/curtain.py
|
|
24
24
|
switchbot/adv_parsers/hub2.py
|
|
25
25
|
switchbot/adv_parsers/humidifier.py
|
|
26
|
+
switchbot/adv_parsers/keypad.py
|
|
26
27
|
switchbot/adv_parsers/light_strip.py
|
|
27
28
|
switchbot/adv_parsers/lock.py
|
|
28
29
|
switchbot/adv_parsers/meter.py
|
|
29
30
|
switchbot/adv_parsers/motion.py
|
|
30
31
|
switchbot/adv_parsers/plug.py
|
|
32
|
+
switchbot/adv_parsers/relay_switch.py
|
|
31
33
|
switchbot/devices/__init__.py
|
|
32
34
|
switchbot/devices/base_cover.py
|
|
33
35
|
switchbot/devices/base_light.py
|
|
@@ -39,11 +41,13 @@ switchbot/devices/contact.py
|
|
|
39
41
|
switchbot/devices/curtain.py
|
|
40
42
|
switchbot/devices/device.py
|
|
41
43
|
switchbot/devices/humidifier.py
|
|
44
|
+
switchbot/devices/keypad.py
|
|
42
45
|
switchbot/devices/light_strip.py
|
|
43
46
|
switchbot/devices/lock.py
|
|
44
47
|
switchbot/devices/meter.py
|
|
45
48
|
switchbot/devices/motion.py
|
|
46
49
|
switchbot/devices/plug.py
|
|
50
|
+
switchbot/devices/relay_switch.py
|
|
47
51
|
tests/test_adv_parser.py
|
|
48
52
|
tests/test_base_cover.py
|
|
49
53
|
tests/test_blind_tilt.py
|
|
@@ -26,6 +26,7 @@ from .devices.humidifier import SwitchbotHumidifier
|
|
|
26
26
|
from .devices.light_strip import SwitchbotLightStrip
|
|
27
27
|
from .devices.lock import SwitchbotLock
|
|
28
28
|
from .devices.plug import SwitchbotPlugMini
|
|
29
|
+
from .devices.relay_switch import SwitchbotRelaySwitch
|
|
29
30
|
from .discovery import GetSwitchbotDevices
|
|
30
31
|
from .models import SwitchBotAdvertisement
|
|
31
32
|
|
|
@@ -54,4 +55,5 @@ __all__ = [
|
|
|
54
55
|
"SwitchbotModel",
|
|
55
56
|
"SwitchbotLock",
|
|
56
57
|
"SwitchbotBlindTilt",
|
|
58
|
+
"SwitchbotRelaySwitch",
|
|
57
59
|
]
|
|
@@ -18,11 +18,16 @@ from .adv_parsers.contact import process_wocontact
|
|
|
18
18
|
from .adv_parsers.curtain import process_wocurtain
|
|
19
19
|
from .adv_parsers.hub2 import process_wohub2
|
|
20
20
|
from .adv_parsers.humidifier import process_wohumidifier
|
|
21
|
+
from .adv_parsers.keypad import process_wokeypad
|
|
21
22
|
from .adv_parsers.light_strip import process_wostrip
|
|
22
23
|
from .adv_parsers.lock import process_wolock, process_wolock_pro
|
|
23
24
|
from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
|
|
24
25
|
from .adv_parsers.motion import process_wopresence
|
|
25
26
|
from .adv_parsers.plug import process_woplugmini
|
|
27
|
+
from .adv_parsers.relay_switch import (
|
|
28
|
+
process_worelay_switch_1plus,
|
|
29
|
+
process_worelay_switch_1pm,
|
|
30
|
+
)
|
|
26
31
|
from .const import SwitchbotModel
|
|
27
32
|
from .models import SwitchBotAdvertisement
|
|
28
33
|
|
|
@@ -69,7 +74,6 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
|
|
|
69
74
|
"modelFriendlyName": "Light Strip",
|
|
70
75
|
"func": process_wostrip,
|
|
71
76
|
"manufacturer_id": 2409,
|
|
72
|
-
"manufacturer_data_length": 16,
|
|
73
77
|
},
|
|
74
78
|
"{": {
|
|
75
79
|
"modelName": SwitchbotModel.CURTAIN,
|
|
@@ -174,6 +178,24 @@ SUPPORTED_TYPES: dict[str, SwitchbotSupportedType] = {
|
|
|
174
178
|
"func": process_woblindtilt,
|
|
175
179
|
"manufacturer_id": 2409,
|
|
176
180
|
},
|
|
181
|
+
"y": {
|
|
182
|
+
"modelName": SwitchbotModel.KEYPAD,
|
|
183
|
+
"modelFriendlyName": "Keypad",
|
|
184
|
+
"func": process_wokeypad,
|
|
185
|
+
"manufacturer_id": 2409,
|
|
186
|
+
},
|
|
187
|
+
"<": {
|
|
188
|
+
"modelName": SwitchbotModel.RELAY_SWITCH_1PM,
|
|
189
|
+
"modelFriendlyName": "Relay Switch 1PM",
|
|
190
|
+
"func": process_worelay_switch_1pm,
|
|
191
|
+
"manufacturer_id": 2409,
|
|
192
|
+
},
|
|
193
|
+
";": {
|
|
194
|
+
"modelName": SwitchbotModel.RELAY_SWITCH_1_PLUS,
|
|
195
|
+
"modelFriendlyName": "Relay Switch 1",
|
|
196
|
+
"func": process_worelay_switch_1plus,
|
|
197
|
+
"manufacturer_id": 2409,
|
|
198
|
+
},
|
|
177
199
|
}
|
|
178
200
|
|
|
179
201
|
_SWITCHBOT_MODEL_TO_CHAR = {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Keypad parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
_LOGGER = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def process_wokeypad(
|
|
11
|
+
data: bytes | None,
|
|
12
|
+
mfr_data: bytes | None,
|
|
13
|
+
) -> dict[str, bool | int | None]:
|
|
14
|
+
"""Process woKeypad services data."""
|
|
15
|
+
if data is None or mfr_data is None:
|
|
16
|
+
return {"battery": None, "attempt_state": None}
|
|
17
|
+
|
|
18
|
+
_LOGGER.debug("mfr_data: %s", mfr_data.hex())
|
|
19
|
+
if data:
|
|
20
|
+
_LOGGER.debug("data: %s", data.hex())
|
|
21
|
+
|
|
22
|
+
return {"battery": data[2] & 0b01111111, "attempt_state": mfr_data[6]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Relay Switch adv parser."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def process_worelay_switch_1pm(
|
|
6
|
+
data: bytes | None, mfr_data: bytes | None
|
|
7
|
+
) -> dict[str, bool | int]:
|
|
8
|
+
"""Process WoStrip services data."""
|
|
9
|
+
if mfr_data is None:
|
|
10
|
+
return {}
|
|
11
|
+
return {
|
|
12
|
+
"switchMode": True, # for compatibility, useless
|
|
13
|
+
"sequence_number": mfr_data[6],
|
|
14
|
+
"isOn": bool(mfr_data[7] & 0b10000000),
|
|
15
|
+
"power": ((mfr_data[10] << 8) + mfr_data[11]) / 10,
|
|
16
|
+
"voltage": 0,
|
|
17
|
+
"current": 0,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def process_worelay_switch_1plus(
|
|
22
|
+
data: bytes | None, mfr_data: bytes | None
|
|
23
|
+
) -> dict[str, bool | int]:
|
|
24
|
+
"""Process WoStrip services data."""
|
|
25
|
+
if mfr_data is None:
|
|
26
|
+
return {}
|
|
27
|
+
return {
|
|
28
|
+
"switchMode": True, # for compatibility, useless
|
|
29
|
+
"sequence_number": mfr_data[6],
|
|
30
|
+
"isOn": bool(mfr_data[7] & 0b10000000),
|
|
31
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Library to handle connection with Switchbot."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from enum import Enum
|
|
@@ -52,6 +53,9 @@ class SwitchbotModel(StrEnum):
|
|
|
52
53
|
LOCK_PRO = "WoLockPro"
|
|
53
54
|
BLIND_TILT = "WoBlindTilt"
|
|
54
55
|
HUB2 = "WoHub2"
|
|
56
|
+
KEYPAD = "WoKeypad"
|
|
57
|
+
RELAY_SWITCH_1PM = "Relay Switch 1PM"
|
|
58
|
+
RELAY_SWITCH_1_PLUS = "Relay Switch 1"
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
class LockStatus(Enum):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from bleak.backends.device import BLEDevice
|
|
5
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
6
|
+
|
|
7
|
+
from ..const import SwitchbotModel
|
|
8
|
+
from .device import SwitchbotSequenceDevice
|
|
9
|
+
|
|
10
|
+
COMMAND_HEADER = "57"
|
|
11
|
+
COMMAND_GET_CK_IV = f"{COMMAND_HEADER}0f2103"
|
|
12
|
+
COMMAND_TURN_OFF = f"{COMMAND_HEADER}0f70010000"
|
|
13
|
+
COMMAND_TURN_ON = f"{COMMAND_HEADER}0f70010100"
|
|
14
|
+
COMMAND_TOGGLE = f"{COMMAND_HEADER}0f70010200"
|
|
15
|
+
COMMAND_GET_VOLTAGE_AND_CURRENT = f"{COMMAND_HEADER}0f7106000000"
|
|
16
|
+
PASSIVE_POLL_INTERVAL = 1 * 60
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SwitchbotRelaySwitch(SwitchbotSequenceDevice):
|
|
20
|
+
"""Representation of a Switchbot relay switch 1pm."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
device: BLEDevice,
|
|
25
|
+
key_id: str,
|
|
26
|
+
encryption_key: str,
|
|
27
|
+
interface: int = 0,
|
|
28
|
+
model: SwitchbotModel = SwitchbotModel.RELAY_SWITCH_1PM,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> None:
|
|
31
|
+
if len(key_id) == 0:
|
|
32
|
+
raise ValueError("key_id is missing")
|
|
33
|
+
elif len(key_id) != 2:
|
|
34
|
+
raise ValueError("key_id is invalid")
|
|
35
|
+
if len(encryption_key) == 0:
|
|
36
|
+
raise ValueError("encryption_key is missing")
|
|
37
|
+
elif len(encryption_key) != 32:
|
|
38
|
+
raise ValueError("encryption_key is invalid")
|
|
39
|
+
self._iv = None
|
|
40
|
+
self._cipher = None
|
|
41
|
+
self._key_id = key_id
|
|
42
|
+
self._encryption_key = bytearray.fromhex(encryption_key)
|
|
43
|
+
self._model: SwitchbotModel = model
|
|
44
|
+
super().__init__(device, None, interface, **kwargs)
|
|
45
|
+
|
|
46
|
+
async def update(self, interface: int | None = None) -> None:
|
|
47
|
+
"""Update state of device."""
|
|
48
|
+
if info := await self.get_voltage_and_current():
|
|
49
|
+
self._last_full_update = time.monotonic()
|
|
50
|
+
self._update_parsed_data(info)
|
|
51
|
+
self._fire_callbacks()
|
|
52
|
+
|
|
53
|
+
async def get_voltage_and_current(self) -> dict[str, Any] | None:
|
|
54
|
+
"""Get voltage and current because advtisement don't have these"""
|
|
55
|
+
result = await self._send_command(COMMAND_GET_VOLTAGE_AND_CURRENT)
|
|
56
|
+
ok = self._check_command_result(result, 0, {1})
|
|
57
|
+
if ok:
|
|
58
|
+
return {
|
|
59
|
+
"voltage": (result[9] << 8) + result[10],
|
|
60
|
+
"current": (result[11] << 8) + result[12],
|
|
61
|
+
}
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def poll_needed(self, seconds_since_last_poll: float | None) -> bool:
|
|
65
|
+
"""Return if device needs polling."""
|
|
66
|
+
if (
|
|
67
|
+
seconds_since_last_poll is not None
|
|
68
|
+
and seconds_since_last_poll < PASSIVE_POLL_INTERVAL
|
|
69
|
+
):
|
|
70
|
+
return False
|
|
71
|
+
time_since_last_full_update = time.monotonic() - self._last_full_update
|
|
72
|
+
if time_since_last_full_update < PASSIVE_POLL_INTERVAL:
|
|
73
|
+
return False
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
async def turn_on(self) -> bool:
|
|
77
|
+
"""Turn device on."""
|
|
78
|
+
result = await self._send_command(COMMAND_TURN_ON)
|
|
79
|
+
ok = self._check_command_result(result, 0, {1})
|
|
80
|
+
if ok:
|
|
81
|
+
self._override_state({"isOn": True})
|
|
82
|
+
self._fire_callbacks()
|
|
83
|
+
return ok
|
|
84
|
+
|
|
85
|
+
async def turn_off(self) -> bool:
|
|
86
|
+
"""Turn device off."""
|
|
87
|
+
result = await self._send_command(COMMAND_TURN_OFF)
|
|
88
|
+
ok = self._check_command_result(result, 0, {1})
|
|
89
|
+
if ok:
|
|
90
|
+
self._override_state({"isOn": False})
|
|
91
|
+
self._fire_callbacks()
|
|
92
|
+
return ok
|
|
93
|
+
|
|
94
|
+
async def async_toggle(self, **kwargs) -> bool:
|
|
95
|
+
"""Toggle device."""
|
|
96
|
+
result = await self._send_command(COMMAND_TOGGLE)
|
|
97
|
+
status = self._check_command_result(result, 0, {1})
|
|
98
|
+
return status
|
|
99
|
+
|
|
100
|
+
def is_on(self) -> bool | None:
|
|
101
|
+
"""Return switch state from cache."""
|
|
102
|
+
return self._get_adv_value("isOn")
|
|
103
|
+
|
|
104
|
+
async def _send_command(
|
|
105
|
+
self, key: str, retry: int | None = None, encrypt: bool = True
|
|
106
|
+
) -> bytes | None:
|
|
107
|
+
if not encrypt:
|
|
108
|
+
return await super()._send_command(key[:2] + "000000" + key[2:], retry)
|
|
109
|
+
|
|
110
|
+
result = await self._ensure_encryption_initialized()
|
|
111
|
+
if not result:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
encrypted = (
|
|
115
|
+
key[:2] + self._key_id + self._iv[0:2].hex() + self._encrypt(key[2:])
|
|
116
|
+
)
|
|
117
|
+
result = await super()._send_command(encrypted, retry)
|
|
118
|
+
return result[:1] + self._decrypt(result[4:])
|
|
119
|
+
|
|
120
|
+
async def _ensure_encryption_initialized(self) -> bool:
|
|
121
|
+
if self._iv is not None:
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
result = await self._send_command(
|
|
125
|
+
COMMAND_GET_CK_IV + self._key_id, encrypt=False
|
|
126
|
+
)
|
|
127
|
+
ok = self._check_command_result(result, 0, {1})
|
|
128
|
+
if ok:
|
|
129
|
+
self._iv = result[4:]
|
|
130
|
+
|
|
131
|
+
return ok
|
|
132
|
+
|
|
133
|
+
async def _execute_disconnect(self) -> None:
|
|
134
|
+
await super()._execute_disconnect()
|
|
135
|
+
self._iv = None
|
|
136
|
+
self._cipher = None
|
|
137
|
+
|
|
138
|
+
def _get_cipher(self) -> Cipher:
|
|
139
|
+
if self._cipher is None:
|
|
140
|
+
self._cipher = Cipher(
|
|
141
|
+
algorithms.AES128(self._encryption_key), modes.CTR(self._iv)
|
|
142
|
+
)
|
|
143
|
+
return self._cipher
|
|
144
|
+
|
|
145
|
+
def _encrypt(self, data: str) -> str:
|
|
146
|
+
if len(data) == 0:
|
|
147
|
+
return ""
|
|
148
|
+
encryptor = self._get_cipher().encryptor()
|
|
149
|
+
return (encryptor.update(bytearray.fromhex(data)) + encryptor.finalize()).hex()
|
|
150
|
+
|
|
151
|
+
def _decrypt(self, data: bytearray) -> bytes:
|
|
152
|
+
if len(data) == 0:
|
|
153
|
+
return b""
|
|
154
|
+
decryptor = self._get_cipher().decryptor()
|
|
155
|
+
return decryptor.update(data) + decryptor.finalize()
|
|
@@ -124,6 +124,10 @@ class GetSwitchbotDevices:
|
|
|
124
124
|
lock_pros = await self._get_devices_by_model("$")
|
|
125
125
|
return {**locks, **lock_pros}
|
|
126
126
|
|
|
127
|
+
async def get_keypads(self) -> dict[str, SwitchBotAdvertisement]:
|
|
128
|
+
"""Return all WoKeypad/Keypad devices with services data."""
|
|
129
|
+
return await self._get_devices_by_model("y")
|
|
130
|
+
|
|
127
131
|
async def get_device_data(
|
|
128
132
|
self, address: str
|
|
129
133
|
) -> dict[str, SwitchBotAdvertisement] | None:
|
|
@@ -807,43 +807,6 @@ def test_bulb_active():
|
|
|
807
807
|
)
|
|
808
808
|
|
|
809
809
|
|
|
810
|
-
def test_lightstrip_passive():
|
|
811
|
-
"""Test parsing lightstrip as passive."""
|
|
812
|
-
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
813
|
-
adv_data = generate_advertisement_data(
|
|
814
|
-
manufacturer_data={
|
|
815
|
-
2409: b"`U\xf9(\xe5\x96\x00d\x02\xb0\x00\x00\x00\x00\x00\x00"
|
|
816
|
-
},
|
|
817
|
-
service_data={},
|
|
818
|
-
tx_power=-127,
|
|
819
|
-
rssi=-50,
|
|
820
|
-
)
|
|
821
|
-
result = parse_advertisement_data(ble_device, adv_data)
|
|
822
|
-
assert result == SwitchBotAdvertisement(
|
|
823
|
-
address="aa:bb:cc:dd:ee:ff",
|
|
824
|
-
data={
|
|
825
|
-
"data": {
|
|
826
|
-
"brightness": 100,
|
|
827
|
-
"color_mode": 2,
|
|
828
|
-
"delay": False,
|
|
829
|
-
"isOn": False,
|
|
830
|
-
"loop_index": 0,
|
|
831
|
-
"preset": False,
|
|
832
|
-
"sequence_number": 0,
|
|
833
|
-
"speed": 48,
|
|
834
|
-
},
|
|
835
|
-
"isEncrypted": False,
|
|
836
|
-
"model": "r",
|
|
837
|
-
"modelFriendlyName": "Light Strip",
|
|
838
|
-
"modelName": SwitchbotModel.LIGHT_STRIP,
|
|
839
|
-
"rawAdvData": None,
|
|
840
|
-
},
|
|
841
|
-
device=ble_device,
|
|
842
|
-
rssi=-50,
|
|
843
|
-
active=False,
|
|
844
|
-
)
|
|
845
|
-
|
|
846
|
-
|
|
847
810
|
def test_wosensor_passive_and_active():
|
|
848
811
|
"""Test parsing wosensor as passive with active data as well."""
|
|
849
812
|
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
@@ -1684,3 +1647,28 @@ def test_meter_pro_c_passive() -> None:
|
|
|
1684
1647
|
rssi=-67,
|
|
1685
1648
|
active=False,
|
|
1686
1649
|
)
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def test_parse_advertisement_data_keypad():
|
|
1653
|
+
"""Test parse_advertisement_data for the keypad."""
|
|
1654
|
+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
|
|
1655
|
+
adv_data = generate_advertisement_data(
|
|
1656
|
+
manufacturer_data={2409: b"\xeb\x13\x02\xe6#\x0f\x8fd\x00\x00\x00\x00"},
|
|
1657
|
+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"y\x00d"},
|
|
1658
|
+
rssi=-67,
|
|
1659
|
+
)
|
|
1660
|
+
result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.KEYPAD)
|
|
1661
|
+
assert result == SwitchBotAdvertisement(
|
|
1662
|
+
address="aa:bb:cc:dd:ee:ff",
|
|
1663
|
+
data={
|
|
1664
|
+
"data": {"attempt_state": 143, "battery": 100},
|
|
1665
|
+
"isEncrypted": False,
|
|
1666
|
+
"model": "y",
|
|
1667
|
+
"modelFriendlyName": "Keypad",
|
|
1668
|
+
"modelName": SwitchbotModel.KEYPAD,
|
|
1669
|
+
"rawAdvData": b"y\x00d",
|
|
1670
|
+
},
|
|
1671
|
+
device=ble_device,
|
|
1672
|
+
rssi=-67,
|
|
1673
|
+
active=True,
|
|
1674
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/pyswitchbot-0.51.0/switchbot/devices/meter.py → /pyswitchbot-0.53.0/switchbot/devices/keypad.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/pyswitchbot-0.51.0/switchbot/devices/motion.py → /pyswitchbot-0.53.0/switchbot/devices/meter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|