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.
Files changed (56) hide show
  1. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PKG-INFO +1 -1
  2. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/PKG-INFO +1 -1
  3. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/SOURCES.txt +4 -0
  4. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/setup.py +1 -1
  5. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/__init__.py +2 -0
  6. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parser.py +23 -1
  7. pyswitchbot-0.53.0/switchbot/adv_parsers/keypad.py +22 -0
  8. pyswitchbot-0.53.0/switchbot/adv_parsers/relay_switch.py +31 -0
  9. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/const.py +4 -0
  10. pyswitchbot-0.53.0/switchbot/devices/motion.py +1 -0
  11. pyswitchbot-0.53.0/switchbot/devices/relay_switch.py +155 -0
  12. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/discovery.py +4 -0
  13. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_adv_parser.py +25 -37
  14. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/LICENSE +0 -0
  15. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/MANIFEST.in +0 -0
  16. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/dependency_links.txt +0 -0
  17. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/requires.txt +0 -0
  18. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/PySwitchbot.egg-info/top_level.txt +0 -0
  19. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/README.md +0 -0
  20. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/setup.cfg +0 -0
  21. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/__init__.py +0 -0
  22. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/blind_tilt.py +0 -0
  23. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/bot.py +0 -0
  24. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/bulb.py +0 -0
  25. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/ceiling_light.py +0 -0
  26. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/contact.py +0 -0
  27. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/curtain.py +0 -0
  28. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/hub2.py +0 -0
  29. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/humidifier.py +0 -0
  30. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/light_strip.py +0 -0
  31. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/lock.py +0 -0
  32. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/meter.py +0 -0
  33. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/motion.py +0 -0
  34. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/adv_parsers/plug.py +0 -0
  35. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/api_config.py +0 -0
  36. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/__init__.py +0 -0
  37. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/base_cover.py +0 -0
  38. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/base_light.py +0 -0
  39. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/blind_tilt.py +0 -0
  40. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/bot.py +0 -0
  41. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/bulb.py +0 -0
  42. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/ceiling_light.py +0 -0
  43. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/contact.py +0 -0
  44. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/curtain.py +0 -0
  45. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/device.py +0 -0
  46. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/humidifier.py +0 -0
  47. /pyswitchbot-0.51.0/switchbot/devices/meter.py → /pyswitchbot-0.53.0/switchbot/devices/keypad.py +0 -0
  48. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/light_strip.py +0 -0
  49. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/lock.py +0 -0
  50. /pyswitchbot-0.51.0/switchbot/devices/motion.py → /pyswitchbot-0.53.0/switchbot/devices/meter.py +0 -0
  51. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/devices/plug.py +0 -0
  52. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/enum.py +0 -0
  53. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/switchbot/models.py +0 -0
  54. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_base_cover.py +0 -0
  55. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_blind_tilt.py +0 -0
  56. {pyswitchbot-0.51.0 → pyswitchbot-0.53.0}/tests/test_curtain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.51.0
3
+ Version: 0.53.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySwitchbot
3
- Version: 0.51.0
3
+ Version: 0.53.0
4
4
  Summary: A library to communicate with Switchbot
5
5
  Home-page: https://github.com/Danielhiversen/pySwitchbot/
6
6
  Author: Daniel Hjelseth Hoyer
@@ -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
@@ -10,7 +10,7 @@ setup(
10
10
  "cryptography>=39.0.0",
11
11
  "pyOpenSSL>=23.0.0",
12
12
  ],
13
- version="0.51.0",
13
+ version="0.53.0",
14
14
  description="A library to communicate with Switchbot",
15
15
  author="Daniel Hjelseth Hoyer",
16
16
  url="https://github.com/Danielhiversen/pySwitchbot/",
@@ -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