test_adv_parser.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284
  1. from __future__ import annotations
  2. from typing import Any
  3. from bleak.backends.device import BLEDevice
  4. from bleak.backends.scanner import AdvertisementData
  5. from switchbot import LockStatus, SwitchbotModel
  6. from switchbot.adv_parser import parse_advertisement_data
  7. from switchbot.models import SwitchBotAdvertisement
  8. ADVERTISEMENT_DATA_DEFAULTS = {
  9. "local_name": "",
  10. "manufacturer_data": {},
  11. "service_data": {},
  12. "service_uuids": [],
  13. "rssi": -127,
  14. "platform_data": ((),),
  15. "tx_power": -127,
  16. }
  17. BLE_DEVICE_DEFAULTS = {
  18. "name": None,
  19. "rssi": -127,
  20. "details": None,
  21. }
  22. def generate_ble_device(
  23. address: str | None = None,
  24. name: str | None = None,
  25. details: Any | None = None,
  26. rssi: int | None = None,
  27. **kwargs: Any,
  28. ) -> BLEDevice:
  29. """Generate a BLEDevice with defaults."""
  30. new = kwargs.copy()
  31. if address is not None:
  32. new["address"] = address
  33. if name is not None:
  34. new["name"] = name
  35. if details is not None:
  36. new["details"] = details
  37. if rssi is not None:
  38. new["rssi"] = rssi
  39. for key, value in BLE_DEVICE_DEFAULTS.items():
  40. new.setdefault(key, value)
  41. return BLEDevice(**new)
  42. def generate_advertisement_data(**kwargs: Any) -> AdvertisementData:
  43. """Generate advertisement data with defaults."""
  44. new = kwargs.copy()
  45. for key, value in ADVERTISEMENT_DATA_DEFAULTS.items():
  46. new.setdefault(key, value)
  47. return AdvertisementData(**new)
  48. def test_parse_advertisement_data_curtain():
  49. """Test parse_advertisement_data for curtain."""
  50. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  51. adv_data = generate_advertisement_data(
  52. manufacturer_data={2409: b"\xe7\xabF\xac\x8f\x92|\x0f\x00\x11\x04"},
  53. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"c\xc0X\x00\x11\x04"},
  54. rssi=-80,
  55. )
  56. result = parse_advertisement_data(ble_device, adv_data)
  57. assert result == SwitchBotAdvertisement(
  58. address="aa:bb:cc:dd:ee:ff",
  59. data={
  60. "rawAdvData": b"c\xc0X\x00\x11\x04",
  61. "data": {
  62. "calibration": True,
  63. "battery": 88,
  64. "inMotion": False,
  65. "position": 100,
  66. "lightLevel": 1,
  67. "deviceChain": 1,
  68. },
  69. "isEncrypted": False,
  70. "model": "c",
  71. "modelFriendlyName": "Curtain",
  72. "modelName": SwitchbotModel.CURTAIN,
  73. },
  74. device=ble_device,
  75. rssi=-80,
  76. active=True,
  77. )
  78. def test_parse_advertisement_data_curtain_passive():
  79. """Test parse_advertisement_data for curtain passive."""
  80. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  81. adv_data = generate_advertisement_data(
  82. manufacturer_data={2409: b"\xe7\xabF\xac\x8f\x92|\x0f\x00\x11\x04"},
  83. service_data={},
  84. rssi=-80,
  85. )
  86. result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.CURTAIN)
  87. assert result == SwitchBotAdvertisement(
  88. address="aa:bb:cc:dd:ee:ff",
  89. data={
  90. "rawAdvData": None,
  91. "data": {
  92. "calibration": None,
  93. "battery": None,
  94. "inMotion": False,
  95. "position": 100,
  96. "lightLevel": 1,
  97. "deviceChain": 1,
  98. },
  99. "isEncrypted": False,
  100. "model": "c",
  101. "modelFriendlyName": "Curtain",
  102. "modelName": SwitchbotModel.CURTAIN,
  103. },
  104. device=ble_device,
  105. rssi=-80,
  106. active=False,
  107. )
  108. def test_parse_advertisement_data_curtain_position_zero():
  109. """Test parse_advertisement_data for curtain position zero."""
  110. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  111. adv_data = generate_advertisement_data(
  112. local_name="WoCurtain",
  113. manufacturer_data={89: b"\xc1\xc7'}U\xab"},
  114. service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"c\xd0\xced\x11\x04"},
  115. service_uuids=[
  116. "00001800-0000-1000-8000-00805f9b34fb",
  117. "00001801-0000-1000-8000-00805f9b34fb",
  118. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  119. ],
  120. rssi=-52,
  121. )
  122. result = parse_advertisement_data(ble_device, adv_data)
  123. assert result == SwitchBotAdvertisement(
  124. address="aa:bb:cc:dd:ee:ff",
  125. data={
  126. "rawAdvData": b"c\xd0\xced\x11\x04",
  127. "data": {
  128. "calibration": True,
  129. "battery": 78,
  130. "inMotion": False,
  131. "position": 0,
  132. "lightLevel": 1,
  133. "deviceChain": 1,
  134. },
  135. "isEncrypted": False,
  136. "model": "c",
  137. "modelFriendlyName": "Curtain",
  138. "modelName": SwitchbotModel.CURTAIN,
  139. },
  140. device=ble_device,
  141. rssi=-52,
  142. active=True,
  143. )
  144. def test_parse_advertisement_data_curtain_firmware_six_position_100():
  145. """Test parse_advertisement_data with firmware six for curtain position 100."""
  146. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  147. adv_data = generate_advertisement_data(
  148. local_name="WoCurtain",
  149. manufacturer_data={
  150. 89: b"\xf5\x98\x94\x08\xa0\xe7",
  151. 2409: b'\xf5\x98\x94\x08\xa0\xe7\x9b\x0f\x00"\x04',
  152. },
  153. service_data={
  154. "00000d00-0000-1000-8000-00805f9b34fb": b"c\xd0H\x00\x12\x04",
  155. "0000fd3d-0000-1000-8000-00805f9b34fb": b'c\xc0G\x00"\x04',
  156. },
  157. service_uuids=[
  158. "00001800-0000-1000-8000-00805f9b34fb",
  159. "00001801-0000-1000-8000-00805f9b34fb",
  160. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  161. ],
  162. rssi=-62,
  163. )
  164. result = parse_advertisement_data(ble_device, adv_data)
  165. assert result == SwitchBotAdvertisement(
  166. address="aa:bb:cc:dd:ee:ff",
  167. data={
  168. "rawAdvData": b'c\xc0G\x00"\x04',
  169. "data": {
  170. "calibration": True,
  171. "battery": 71,
  172. "inMotion": False,
  173. "position": 100,
  174. "lightLevel": 2,
  175. "deviceChain": 2,
  176. },
  177. "isEncrypted": False,
  178. "model": "c",
  179. "modelFriendlyName": "Curtain",
  180. "modelName": SwitchbotModel.CURTAIN,
  181. },
  182. device=ble_device,
  183. rssi=-62,
  184. active=True,
  185. )
  186. def test_parse_advertisement_data_curtain_firmware_six_position_100_other_rssi():
  187. """Test parse_advertisement_data with firmware six for curtain position 100 other rssi."""
  188. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  189. adv_data = generate_advertisement_data(
  190. local_name="WoCurtain",
  191. manufacturer_data={
  192. 89: b"\xf5\x98\x94\x08\xa0\xe7",
  193. 2409: b'\xf5\x98\x94\x08\xa0\xe7\xa5\x0fc"\x04',
  194. },
  195. service_data={
  196. "00000d00-0000-1000-8000-00805f9b34fb": b"c\xd0H\x00\x12\x04",
  197. "0000fd3d-0000-1000-8000-00805f9b34fb": b'c\xc0Gc"\x04',
  198. },
  199. service_uuids=[
  200. "00001800-0000-1000-8000-00805f9b34fb",
  201. "00001801-0000-1000-8000-00805f9b34fb",
  202. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  203. ],
  204. rssi=-67,
  205. )
  206. result = parse_advertisement_data(ble_device, adv_data)
  207. assert result == SwitchBotAdvertisement(
  208. address="aa:bb:cc:dd:ee:ff",
  209. data={
  210. "rawAdvData": b'c\xc0Gc"\x04',
  211. "data": {
  212. "calibration": True,
  213. "battery": 71,
  214. "inMotion": False,
  215. "position": 1,
  216. "lightLevel": 2,
  217. "deviceChain": 2,
  218. },
  219. "isEncrypted": False,
  220. "model": "c",
  221. "modelFriendlyName": "Curtain",
  222. "modelName": SwitchbotModel.CURTAIN,
  223. },
  224. device=ble_device,
  225. rssi=-67,
  226. active=True,
  227. )
  228. def test_parse_advertisement_data_curtain_fully_closed():
  229. """Test parse_advertisement_data with firmware six fully closed."""
  230. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  231. adv_data = generate_advertisement_data(
  232. local_name="WoCurtain",
  233. manufacturer_data={2409: b"\xc1\xc7'}U\xab\"\x0fd\x11\x04"},
  234. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"c\xc0Sd\x11\x04"},
  235. service_uuids=[
  236. "00001800-0000-1000-8000-00805f9b34fb",
  237. "00001801-0000-1000-8000-00805f9b34fb",
  238. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  239. ],
  240. rssi=1,
  241. )
  242. result = parse_advertisement_data(ble_device, adv_data)
  243. assert result == SwitchBotAdvertisement(
  244. address="aa:bb:cc:dd:ee:ff",
  245. data={
  246. "rawAdvData": b"c\xc0Sd\x11\x04",
  247. "data": {
  248. "calibration": True,
  249. "battery": 83,
  250. "inMotion": False,
  251. "position": 0,
  252. "lightLevel": 1,
  253. "deviceChain": 1,
  254. },
  255. "isEncrypted": False,
  256. "model": "c",
  257. "modelFriendlyName": "Curtain",
  258. "modelName": SwitchbotModel.CURTAIN,
  259. },
  260. device=ble_device,
  261. rssi=1,
  262. active=True,
  263. )
  264. def test_parse_advertisement_data_curtain_fully_open():
  265. """Test parse_advertisement_data with firmware six fully open."""
  266. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  267. adv_data = generate_advertisement_data(
  268. local_name="WoCurtain",
  269. manufacturer_data={2409: b"\xc1\xc7'}U\xab%\x0f\x00\x11\x04"},
  270. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"c\xc0S\x00\x11\x04"},
  271. service_uuids=[
  272. "00001800-0000-1000-8000-00805f9b34fb",
  273. "00001801-0000-1000-8000-00805f9b34fb",
  274. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  275. ],
  276. rssi=1,
  277. )
  278. result = parse_advertisement_data(ble_device, adv_data)
  279. assert result == SwitchBotAdvertisement(
  280. address="aa:bb:cc:dd:ee:ff",
  281. data={
  282. "rawAdvData": b"c\xc0S\x00\x11\x04",
  283. "data": {
  284. "calibration": True,
  285. "battery": 83,
  286. "inMotion": False,
  287. "position": 100,
  288. "lightLevel": 1,
  289. "deviceChain": 1,
  290. },
  291. "isEncrypted": False,
  292. "model": "c",
  293. "modelFriendlyName": "Curtain",
  294. "modelName": SwitchbotModel.CURTAIN,
  295. },
  296. device=ble_device,
  297. rssi=1,
  298. active=True,
  299. )
  300. def test_parse_advertisement_data_contact():
  301. """Test parse_advertisement_data for the contact sensor."""
  302. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  303. adv_data = generate_advertisement_data(
  304. manufacturer_data={2409: b"\xe7\xabF\xac\x8f\x92|\x0f\x00\x11\x04"},
  305. service_data={
  306. "0000fd3d-0000-1000-8000-00805f9b34fb": b"d@d\x05\x00u\x00\xf8\x12"
  307. },
  308. rssi=-80,
  309. )
  310. result = parse_advertisement_data(ble_device, adv_data)
  311. assert result == SwitchBotAdvertisement(
  312. address="aa:bb:cc:dd:ee:ff",
  313. data={
  314. "rawAdvData": b"d@d\x05\x00u\x00\xf8\x12",
  315. "data": {
  316. "button_count": 2,
  317. "contact_open": True,
  318. "contact_timeout": True,
  319. "is_light": True,
  320. "battery": 100,
  321. "motion_detected": True,
  322. "tested": False,
  323. },
  324. "isEncrypted": False,
  325. "model": "d",
  326. "modelFriendlyName": "Contact Sensor",
  327. "modelName": SwitchbotModel.CONTACT_SENSOR,
  328. },
  329. device=ble_device,
  330. rssi=-80,
  331. active=True,
  332. )
  333. def test_parse_advertisement_data_empty():
  334. """Test parse_advertisement_data with empty data does not blow up."""
  335. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  336. adv_data = generate_advertisement_data(
  337. manufacturer_data={2403: b"\xe7\xabF\xac\x8f\x92|\x0f\x00\x11\x04"},
  338. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b""},
  339. )
  340. result = parse_advertisement_data(ble_device, adv_data)
  341. assert result is None
  342. def test_new_bot_firmware():
  343. """Test parsing adv data from new bot firmware."""
  344. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  345. adv_data = generate_advertisement_data(
  346. manufacturer_data={89: b"\xd8.\xad\xcd\r\x85"},
  347. service_data={"00000d00-0000-1000-8000-00805f9b34fb": b"H\x10\xe1"},
  348. service_uuids=["CBA20D00-224D-11E6-9FB8-0002A5D5C51B"],
  349. rssi=-90,
  350. )
  351. result = parse_advertisement_data(ble_device, adv_data)
  352. assert result == SwitchBotAdvertisement(
  353. address="aa:bb:cc:dd:ee:ff",
  354. data={
  355. "rawAdvData": b"H\x10\xe1",
  356. "data": {"switchMode": False, "isOn": False, "battery": 97},
  357. "model": "H",
  358. "isEncrypted": False,
  359. "modelFriendlyName": "Bot",
  360. "modelName": SwitchbotModel.BOT,
  361. },
  362. device=ble_device,
  363. rssi=-90,
  364. active=True,
  365. )
  366. def test_parse_advertisement_data_curtain_firmware_six_fully_closed():
  367. """Test parse_advertisement_data with firmware six fully closed."""
  368. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  369. adv_data = generate_advertisement_data(
  370. local_name="WoCurtain",
  371. manufacturer_data={
  372. 89: b"\xcc\xf4\xc4\xf9\xacl",
  373. 2409: b"\xcc\xf4\xc4\xf9\xacl\xeb\x0fd\x12\x04",
  374. },
  375. service_data={
  376. "00000d00-0000-1000-8000-00805f9b34fb": b"c\xd0Yd\x11\x04",
  377. "0000fd3d-0000-1000-8000-00805f9b34fb": b"c\xc0dd\x12\x04",
  378. },
  379. service_uuids=[
  380. "00001800-0000-1000-8000-00805f9b34fb",
  381. "00001801-0000-1000-8000-00805f9b34fb",
  382. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  383. ],
  384. rssi=-2,
  385. )
  386. result = parse_advertisement_data(ble_device, adv_data)
  387. assert result == SwitchBotAdvertisement(
  388. address="aa:bb:cc:dd:ee:ff",
  389. data={
  390. "rawAdvData": b"c\xc0dd\x12\x04",
  391. "data": {
  392. "calibration": True,
  393. "battery": 100,
  394. "inMotion": False,
  395. "position": 0,
  396. "lightLevel": 1,
  397. "deviceChain": 2,
  398. },
  399. "isEncrypted": False,
  400. "model": "c",
  401. "modelFriendlyName": "Curtain",
  402. "modelName": SwitchbotModel.CURTAIN,
  403. },
  404. device=ble_device,
  405. rssi=-2,
  406. active=True,
  407. )
  408. def test_parse_advertisement_data_curtain_firmware_six_fully_open():
  409. """Test parse_advertisement_data with firmware six fully open."""
  410. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  411. adv_data = generate_advertisement_data(
  412. local_name="WoCurtain",
  413. manufacturer_data={
  414. 89: b"\xcc\xf4\xc4\xf9\xacl",
  415. 2409: b"\xcc\xf4\xc4\xf9\xacl\xe2\x0f\x00\x12\x04",
  416. },
  417. service_data={
  418. "00000d00-0000-1000-8000-00805f9b34fb": b"c\xd0Yd\x11\x04",
  419. "0000fd3d-0000-1000-8000-00805f9b34fb": b"c\xc0d\x00\x12\x04",
  420. },
  421. service_uuids=[
  422. "00001800-0000-1000-8000-00805f9b34fb",
  423. "00001801-0000-1000-8000-00805f9b34fb",
  424. "cba20d00-224d-11e6-9fb8-0002a5d5c51b",
  425. ],
  426. rssi=-2,
  427. )
  428. result = parse_advertisement_data(ble_device, adv_data)
  429. assert result == SwitchBotAdvertisement(
  430. address="aa:bb:cc:dd:ee:ff",
  431. data={
  432. "rawAdvData": b"c\xc0d\x00\x12\x04",
  433. "data": {
  434. "calibration": True,
  435. "battery": 100,
  436. "inMotion": False,
  437. "position": 100,
  438. "lightLevel": 1,
  439. "deviceChain": 2,
  440. },
  441. "isEncrypted": False,
  442. "model": "c",
  443. "modelFriendlyName": "Curtain",
  444. "modelName": SwitchbotModel.CURTAIN,
  445. },
  446. device=ble_device,
  447. rssi=-2,
  448. active=True,
  449. )
  450. def test_contact_sensor_mfr():
  451. """Test parsing adv data from new bot firmware."""
  452. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  453. adv_data = generate_advertisement_data(
  454. manufacturer_data={2409: b"\xcb9\xcd\xc4=FA,\x00F\x01\x8f\xc4"},
  455. service_data={
  456. "0000fd3d-0000-1000-8000-00805f9b34fb": b"d\x00\xda\x04\x00F\x01\x8f\xc4"
  457. },
  458. tx_power=-127,
  459. rssi=-70,
  460. )
  461. result = parse_advertisement_data(ble_device, adv_data)
  462. assert result == SwitchBotAdvertisement(
  463. address="aa:bb:cc:dd:ee:ff",
  464. data={
  465. "data": {
  466. "battery": 90,
  467. "button_count": 4,
  468. "contact_open": True,
  469. "contact_timeout": True,
  470. "is_light": False,
  471. "motion_detected": False,
  472. "tested": False,
  473. },
  474. "isEncrypted": False,
  475. "model": "d",
  476. "modelFriendlyName": "Contact Sensor",
  477. "modelName": SwitchbotModel.CONTACT_SENSOR,
  478. "rawAdvData": b"d\x00\xda\x04\x00F\x01\x8f\xc4",
  479. },
  480. device=ble_device,
  481. rssi=-70,
  482. active=True,
  483. )
  484. def test_contact_sensor_mfr_no_service_data():
  485. """Test contact sensor with passive data only."""
  486. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  487. adv_data = generate_advertisement_data(
  488. manufacturer_data={2409: b"\xcb9\xcd\xc4=FA,\x00F\x01\x8f\xc4"},
  489. service_data={},
  490. tx_power=-127,
  491. rssi=-70,
  492. )
  493. result = parse_advertisement_data(ble_device, adv_data)
  494. assert result == SwitchBotAdvertisement(
  495. address="aa:bb:cc:dd:ee:ff",
  496. data={
  497. "data": {
  498. "battery": None,
  499. "button_count": 4,
  500. "contact_open": True,
  501. "contact_timeout": True,
  502. "is_light": False,
  503. "motion_detected": False,
  504. "tested": None,
  505. },
  506. "isEncrypted": False,
  507. "model": "d",
  508. "modelFriendlyName": "Contact Sensor",
  509. "modelName": SwitchbotModel.CONTACT_SENSOR,
  510. "rawAdvData": None,
  511. },
  512. device=ble_device,
  513. rssi=-70,
  514. active=False,
  515. )
  516. def test_contact_sensor_srv():
  517. """Test parsing adv data from new bot firmware."""
  518. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  519. adv_data = generate_advertisement_data(
  520. service_data={
  521. "0000fd3d-0000-1000-8000-00805f9b34fb": b"d\x00\xda\x04\x00F\x01\x8f\xc4"
  522. },
  523. tx_power=-127,
  524. rssi=-70,
  525. )
  526. result = parse_advertisement_data(ble_device, adv_data)
  527. assert result == SwitchBotAdvertisement(
  528. address="aa:bb:cc:dd:ee:ff",
  529. data={
  530. "data": {
  531. "battery": 90,
  532. "button_count": 4,
  533. "contact_open": True,
  534. "contact_timeout": True,
  535. "is_light": False,
  536. "motion_detected": False,
  537. "tested": False,
  538. },
  539. "isEncrypted": False,
  540. "model": "d",
  541. "modelFriendlyName": "Contact Sensor",
  542. "modelName": SwitchbotModel.CONTACT_SENSOR,
  543. "rawAdvData": b"d\x00\xda\x04\x00F\x01\x8f\xc4",
  544. },
  545. device=ble_device,
  546. rssi=-70,
  547. active=True,
  548. )
  549. def test_contact_sensor_open():
  550. """Test parsing mfr adv data from new bot firmware."""
  551. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  552. adv_data = generate_advertisement_data(
  553. manufacturer_data={2409: b"\xcb9\xcd\xc4=F\x84\x9c\x00\x17\x00QD"},
  554. service_data={
  555. "0000fd3d-0000-1000-8000-00805f9b34fb": b"d@\xda\x02\x00\x17\x00QD"
  556. },
  557. tx_power=-127,
  558. rssi=-59,
  559. )
  560. result = parse_advertisement_data(ble_device, adv_data)
  561. assert result == SwitchBotAdvertisement(
  562. address="aa:bb:cc:dd:ee:ff",
  563. data={
  564. "data": {
  565. "battery": 90,
  566. "button_count": 4,
  567. "contact_open": True,
  568. "contact_timeout": False,
  569. "is_light": False,
  570. "motion_detected": True,
  571. "tested": False,
  572. },
  573. "isEncrypted": False,
  574. "model": "d",
  575. "modelFriendlyName": "Contact Sensor",
  576. "modelName": SwitchbotModel.CONTACT_SENSOR,
  577. "rawAdvData": b"d@\xda\x02\x00\x17\x00QD",
  578. },
  579. device=ble_device,
  580. rssi=-59,
  581. active=True,
  582. )
  583. def test_contact_sensor_closed():
  584. """Test parsing mfr adv data from new bot firmware."""
  585. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  586. adv_data = generate_advertisement_data(
  587. manufacturer_data={2409: b"\xcb9\xcd\xc4=F\x89\x8c\x00+\x00\x19\x84"},
  588. service_data={
  589. "0000fd3d-0000-1000-8000-00805f9b34fb": b"d@\xda\x00\x00+\x00\x19\x84"
  590. },
  591. tx_power=-127,
  592. rssi=-50,
  593. )
  594. result = parse_advertisement_data(ble_device, adv_data)
  595. assert result == SwitchBotAdvertisement(
  596. address="aa:bb:cc:dd:ee:ff",
  597. data={
  598. "data": {
  599. "battery": 90,
  600. "button_count": 4,
  601. "contact_open": False,
  602. "contact_timeout": False,
  603. "is_light": False,
  604. "motion_detected": True,
  605. "tested": False,
  606. },
  607. "isEncrypted": False,
  608. "model": "d",
  609. "modelFriendlyName": "Contact Sensor",
  610. "modelName": SwitchbotModel.CONTACT_SENSOR,
  611. "rawAdvData": b"d@\xda\x00\x00+\x00\x19\x84",
  612. },
  613. device=ble_device,
  614. rssi=-50,
  615. active=True,
  616. )
  617. def test_switchbot_passive():
  618. """Test parsing switchbot as passive."""
  619. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  620. adv_data = generate_advertisement_data(
  621. manufacturer_data={89: bytes.fromhex("d51cfb397856")},
  622. service_data={},
  623. tx_power=-127,
  624. rssi=-50,
  625. )
  626. result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.BOT)
  627. assert result == SwitchBotAdvertisement(
  628. address="aa:bb:cc:dd:ee:ff",
  629. data={
  630. "data": {
  631. "battery": None,
  632. "switchMode": None,
  633. "isOn": None,
  634. },
  635. "isEncrypted": False,
  636. "model": "H",
  637. "modelFriendlyName": "Bot",
  638. "modelName": SwitchbotModel.BOT,
  639. "rawAdvData": None,
  640. },
  641. device=ble_device,
  642. rssi=-50,
  643. active=False,
  644. )
  645. def test_bulb_active():
  646. """Test parsing bulb as active."""
  647. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  648. adv_data = generate_advertisement_data(
  649. manufacturer_data={2409: b"\x84\xf7\x03\xb4\xcbz\x03\xe4!\x00\x00"},
  650. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"u\x00d"},
  651. tx_power=-127,
  652. rssi=-50,
  653. )
  654. result = parse_advertisement_data(ble_device, adv_data)
  655. assert result == SwitchBotAdvertisement(
  656. address="aa:bb:cc:dd:ee:ff",
  657. data={
  658. "data": {
  659. "brightness": 100,
  660. "color_mode": 1,
  661. "delay": False,
  662. "isOn": True,
  663. "loop_index": 0,
  664. "preset": False,
  665. "sequence_number": 3,
  666. "speed": 0,
  667. },
  668. "isEncrypted": False,
  669. "model": "u",
  670. "modelFriendlyName": "Color Bulb",
  671. "modelName": SwitchbotModel.COLOR_BULB,
  672. "rawAdvData": b"u\x00d",
  673. },
  674. device=ble_device,
  675. rssi=-50,
  676. active=True,
  677. )
  678. def test_lightstrip_passive():
  679. """Test parsing lightstrip as passive."""
  680. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  681. adv_data = generate_advertisement_data(
  682. manufacturer_data={
  683. 2409: b"`U\xf9(\xe5\x96\x00d\x02\xb0\x00\x00\x00\x00\x00\x00"
  684. },
  685. service_data={},
  686. tx_power=-127,
  687. rssi=-50,
  688. )
  689. result = parse_advertisement_data(ble_device, adv_data)
  690. assert result == SwitchBotAdvertisement(
  691. address="aa:bb:cc:dd:ee:ff",
  692. data={
  693. "data": {
  694. "brightness": 100,
  695. "color_mode": 2,
  696. "delay": False,
  697. "isOn": False,
  698. "loop_index": 0,
  699. "preset": False,
  700. "sequence_number": 0,
  701. "speed": 48,
  702. },
  703. "isEncrypted": False,
  704. "model": "r",
  705. "modelFriendlyName": "Light Strip",
  706. "modelName": SwitchbotModel.LIGHT_STRIP,
  707. "rawAdvData": None,
  708. },
  709. device=ble_device,
  710. rssi=-50,
  711. active=False,
  712. )
  713. def test_wosensor_passive_and_active():
  714. """Test parsing wosensor as passive with active data as well."""
  715. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  716. adv_data = generate_advertisement_data(
  717. manufacturer_data={2409: b"\xd7\xc1}]\xebC\xde\x03\x06\x985"},
  718. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"T\x00\xe4\x06\x985"},
  719. tx_power=-127,
  720. rssi=-50,
  721. )
  722. result = parse_advertisement_data(ble_device, adv_data)
  723. assert result == SwitchBotAdvertisement(
  724. address="aa:bb:cc:dd:ee:ff",
  725. data={
  726. "data": {
  727. "battery": 100,
  728. "fahrenheit": False,
  729. "humidity": 53,
  730. "temp": {"c": 24.6, "f": 76.28},
  731. "temperature": 24.6,
  732. },
  733. "isEncrypted": False,
  734. "model": "T",
  735. "modelFriendlyName": "Meter",
  736. "modelName": SwitchbotModel.METER,
  737. "rawAdvData": b"T\x00\xe4\x06\x985",
  738. },
  739. device=ble_device,
  740. rssi=-50,
  741. active=True,
  742. )
  743. def test_wosensor_active():
  744. """Test parsing wosensor with active data as well."""
  745. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  746. adv_data = generate_advertisement_data(
  747. manufacturer_data={},
  748. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"T\x00\xe4\x06\x985"},
  749. tx_power=-127,
  750. rssi=-50,
  751. )
  752. result = parse_advertisement_data(ble_device, adv_data)
  753. assert result == SwitchBotAdvertisement(
  754. address="aa:bb:cc:dd:ee:ff",
  755. data={
  756. "data": {
  757. "battery": 100,
  758. "fahrenheit": False,
  759. "humidity": 53,
  760. "temp": {"c": 24.6, "f": 76.28},
  761. "temperature": 24.6,
  762. },
  763. "isEncrypted": False,
  764. "model": "T",
  765. "modelFriendlyName": "Meter",
  766. "modelName": SwitchbotModel.METER,
  767. "rawAdvData": b"T\x00\xe4\x06\x985",
  768. },
  769. device=ble_device,
  770. rssi=-50,
  771. active=True,
  772. )
  773. def test_wosensor_passive_only():
  774. """Test parsing wosensor with only passive data."""
  775. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  776. adv_data = generate_advertisement_data(
  777. manufacturer_data={2409: b"\xd7\xc1}]\xebC\xde\x03\x06\x985"},
  778. service_data={},
  779. tx_power=-127,
  780. rssi=-50,
  781. )
  782. result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.METER)
  783. assert result == SwitchBotAdvertisement(
  784. address="aa:bb:cc:dd:ee:ff",
  785. data={
  786. "data": {
  787. "battery": None,
  788. "fahrenheit": False,
  789. "humidity": 53,
  790. "temp": {"c": 24.6, "f": 76.28},
  791. "temperature": 24.6,
  792. },
  793. "isEncrypted": False,
  794. "model": "T",
  795. "modelFriendlyName": "Meter",
  796. "modelName": SwitchbotModel.METER,
  797. "rawAdvData": None,
  798. },
  799. device=ble_device,
  800. rssi=-50,
  801. active=False,
  802. )
  803. def test_woiosensor_passive_and_active():
  804. """Test parsing woiosensor as passive with active data as well."""
  805. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  806. adv_data = generate_advertisement_data(
  807. manufacturer_data={2409: b"\xaa\xbb\xcc\xdd\xee\xff\xe0\x0f\x06\x985\x00"},
  808. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"w\x00\xe4"},
  809. tx_power=-127,
  810. rssi=-50,
  811. )
  812. result = parse_advertisement_data(ble_device, adv_data)
  813. assert result == SwitchBotAdvertisement(
  814. address="aa:bb:cc:dd:ee:ff",
  815. data={
  816. "data": {
  817. "battery": 100,
  818. "fahrenheit": False,
  819. "humidity": 53,
  820. "temp": {"c": 24.6, "f": 76.28},
  821. "temperature": 24.6,
  822. },
  823. "isEncrypted": False,
  824. "model": "w",
  825. "modelFriendlyName": "Indoor/Outdoor Meter",
  826. "modelName": SwitchbotModel.IO_METER,
  827. "rawAdvData": b"w\x00\xe4",
  828. },
  829. device=ble_device,
  830. rssi=-50,
  831. active=True,
  832. )
  833. def test_woiosensor_passive_only():
  834. """Test parsing woiosensor with only passive data."""
  835. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  836. adv_data = generate_advertisement_data(
  837. manufacturer_data={2409: b"\xaa\xbb\xcc\xdd\xee\xff\xe0\x0f\x06\x985\x00"},
  838. service_data={},
  839. tx_power=-127,
  840. rssi=-50,
  841. )
  842. result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.IO_METER)
  843. assert result == SwitchBotAdvertisement(
  844. address="aa:bb:cc:dd:ee:ff",
  845. data={
  846. "data": {
  847. "battery": None,
  848. "fahrenheit": False,
  849. "humidity": 53,
  850. "temp": {"c": 24.6, "f": 76.28},
  851. "temperature": 24.6,
  852. },
  853. "isEncrypted": False,
  854. "model": "w",
  855. "modelFriendlyName": "Indoor/Outdoor Meter",
  856. "modelName": SwitchbotModel.IO_METER,
  857. "rawAdvData": None,
  858. },
  859. device=ble_device,
  860. rssi=-50,
  861. active=False,
  862. )
  863. def test_motion_sensor_clear():
  864. """Test parsing motion sensor with clear data."""
  865. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  866. adv_data = generate_advertisement_data(
  867. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIj\x1c\x00f"},
  868. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x00f\x01"},
  869. tx_power=-127,
  870. rssi=-87,
  871. )
  872. result = parse_advertisement_data(
  873. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  874. )
  875. assert result == SwitchBotAdvertisement(
  876. address="aa:bb:cc:dd:ee:ff",
  877. data={
  878. "data": {
  879. "battery": 98,
  880. "iot": 0,
  881. "is_light": False,
  882. "led": 0,
  883. "light_intensity": 1,
  884. "motion_detected": False,
  885. "sense_distance": 0,
  886. "tested": False,
  887. },
  888. "isEncrypted": False,
  889. "model": "s",
  890. "modelFriendlyName": "Motion Sensor",
  891. "modelName": SwitchbotModel.MOTION_SENSOR,
  892. "rawAdvData": b"s\x00\xe2\x00f\x01",
  893. },
  894. device=ble_device,
  895. rssi=-87,
  896. active=True,
  897. )
  898. def test_motion_sensor_clear_passive():
  899. """Test parsing motion sensor with clear data."""
  900. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  901. adv_data = generate_advertisement_data(
  902. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIj\x1c\x00f"},
  903. service_data={},
  904. tx_power=-127,
  905. rssi=-87,
  906. )
  907. result = parse_advertisement_data(
  908. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  909. )
  910. assert result == SwitchBotAdvertisement(
  911. address="aa:bb:cc:dd:ee:ff",
  912. data={
  913. "data": {
  914. "battery": None,
  915. "iot": None,
  916. "is_light": False,
  917. "led": None,
  918. "light_intensity": None,
  919. "motion_detected": False,
  920. "sense_distance": None,
  921. "tested": None,
  922. },
  923. "isEncrypted": False,
  924. "model": "s",
  925. "modelFriendlyName": "Motion Sensor",
  926. "modelName": SwitchbotModel.MOTION_SENSOR,
  927. "rawAdvData": None,
  928. },
  929. device=ble_device,
  930. rssi=-87,
  931. active=False,
  932. )
  933. def test_motion_sensor_motion():
  934. """Test parsing motion sensor with motion data."""
  935. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  936. adv_data = generate_advertisement_data(
  937. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
  938. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s@\xe2\x008\x01"},
  939. tx_power=-127,
  940. rssi=-87,
  941. )
  942. result = parse_advertisement_data(
  943. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  944. )
  945. assert result == SwitchBotAdvertisement(
  946. address="aa:bb:cc:dd:ee:ff",
  947. data={
  948. "data": {
  949. "battery": 98,
  950. "iot": 0,
  951. "is_light": False,
  952. "led": 0,
  953. "light_intensity": 1,
  954. "motion_detected": True,
  955. "sense_distance": 0,
  956. "tested": False,
  957. },
  958. "isEncrypted": False,
  959. "model": "s",
  960. "modelFriendlyName": "Motion Sensor",
  961. "modelName": SwitchbotModel.MOTION_SENSOR,
  962. "rawAdvData": b"s@\xe2\x008\x01",
  963. },
  964. device=ble_device,
  965. rssi=-87,
  966. active=True,
  967. )
  968. def test_motion_sensor_motion_passive():
  969. """Test parsing motion sensor with motion data."""
  970. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  971. adv_data = generate_advertisement_data(
  972. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
  973. service_data={},
  974. tx_power=-127,
  975. rssi=-87,
  976. )
  977. result = parse_advertisement_data(
  978. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  979. )
  980. assert result == SwitchBotAdvertisement(
  981. address="aa:bb:cc:dd:ee:ff",
  982. data={
  983. "data": {
  984. "battery": None,
  985. "iot": None,
  986. "is_light": False,
  987. "led": None,
  988. "light_intensity": None,
  989. "motion_detected": True,
  990. "sense_distance": None,
  991. "tested": None,
  992. },
  993. "isEncrypted": False,
  994. "model": "s",
  995. "modelFriendlyName": "Motion Sensor",
  996. "modelName": SwitchbotModel.MOTION_SENSOR,
  997. "rawAdvData": None,
  998. },
  999. device=ble_device,
  1000. rssi=-87,
  1001. active=False,
  1002. )
  1003. def test_motion_sensor_is_light_passive():
  1004. """Test parsing motion sensor with motion data."""
  1005. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1006. adv_data = generate_advertisement_data(
  1007. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIs,\x04g"},
  1008. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x04g\x02"},
  1009. tx_power=-127,
  1010. rssi=-93,
  1011. )
  1012. result = parse_advertisement_data(
  1013. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  1014. )
  1015. assert result == SwitchBotAdvertisement(
  1016. address="aa:bb:cc:dd:ee:ff",
  1017. data={
  1018. "data": {
  1019. "battery": 98,
  1020. "iot": 0,
  1021. "is_light": True,
  1022. "led": 0,
  1023. "light_intensity": 2,
  1024. "motion_detected": False,
  1025. "sense_distance": 0,
  1026. "tested": False,
  1027. },
  1028. "isEncrypted": False,
  1029. "model": "s",
  1030. "modelFriendlyName": "Motion Sensor",
  1031. "modelName": SwitchbotModel.MOTION_SENSOR,
  1032. "rawAdvData": b"s\x00\xe2\x04g\x02",
  1033. },
  1034. device=ble_device,
  1035. rssi=-93,
  1036. active=True,
  1037. )
  1038. def test_motion_sensor_is_light_active():
  1039. """Test parsing motion sensor with motion data."""
  1040. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1041. adv_data = generate_advertisement_data(
  1042. manufacturer_data={},
  1043. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s\x00\xe2\x04g\x02"},
  1044. tx_power=-127,
  1045. rssi=-93,
  1046. )
  1047. result = parse_advertisement_data(
  1048. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  1049. )
  1050. assert result == SwitchBotAdvertisement(
  1051. address="aa:bb:cc:dd:ee:ff",
  1052. data={
  1053. "data": {
  1054. "battery": 98,
  1055. "iot": 0,
  1056. "is_light": True,
  1057. "led": 0,
  1058. "light_intensity": 2,
  1059. "motion_detected": False,
  1060. "sense_distance": 0,
  1061. "tested": False,
  1062. },
  1063. "isEncrypted": False,
  1064. "model": "s",
  1065. "modelFriendlyName": "Motion Sensor",
  1066. "modelName": SwitchbotModel.MOTION_SENSOR,
  1067. "rawAdvData": b"s\x00\xe2\x04g\x02",
  1068. },
  1069. device=ble_device,
  1070. rssi=-93,
  1071. active=True,
  1072. )
  1073. def test_motion_with_light_detected():
  1074. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1075. adv_data = generate_advertisement_data(
  1076. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIvl\x00,"},
  1077. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"s@\xe2\x00,\x02"},
  1078. tx_power=-127,
  1079. rssi=-84,
  1080. )
  1081. result = parse_advertisement_data(
  1082. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  1083. )
  1084. assert result == SwitchBotAdvertisement(
  1085. address="aa:bb:cc:dd:ee:ff",
  1086. data={
  1087. "data": {
  1088. "battery": 98,
  1089. "iot": 0,
  1090. "is_light": True,
  1091. "led": 0,
  1092. "light_intensity": 2,
  1093. "motion_detected": True,
  1094. "sense_distance": 0,
  1095. "tested": False,
  1096. },
  1097. "isEncrypted": False,
  1098. "model": "s",
  1099. "modelFriendlyName": "Motion Sensor",
  1100. "modelName": SwitchbotModel.MOTION_SENSOR,
  1101. "rawAdvData": b"s@\xe2\x00,\x02",
  1102. },
  1103. device=ble_device,
  1104. rssi=-84,
  1105. active=True,
  1106. )
  1107. def test_motion_sensor_motion_passive():
  1108. """Test parsing motion sensor with motion data."""
  1109. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1110. adv_data = generate_advertisement_data(
  1111. manufacturer_data={2409: b"\xc0!\x9a\xe8\xbcIi\\\x008"},
  1112. service_data={},
  1113. tx_power=-127,
  1114. rssi=-87,
  1115. )
  1116. result = parse_advertisement_data(
  1117. ble_device, adv_data, SwitchbotModel.MOTION_SENSOR
  1118. )
  1119. assert result == SwitchBotAdvertisement(
  1120. address="aa:bb:cc:dd:ee:ff",
  1121. data={
  1122. "data": {
  1123. "battery": None,
  1124. "iot": None,
  1125. "is_light": False,
  1126. "led": None,
  1127. "light_intensity": None,
  1128. "motion_detected": True,
  1129. "sense_distance": None,
  1130. "tested": None,
  1131. },
  1132. "isEncrypted": False,
  1133. "model": "s",
  1134. "modelFriendlyName": "Motion Sensor",
  1135. "modelName": SwitchbotModel.MOTION_SENSOR,
  1136. "rawAdvData": None,
  1137. },
  1138. device=ble_device,
  1139. rssi=-87,
  1140. active=False,
  1141. )
  1142. def test_parsing_lock_active():
  1143. """Test parsing lock with active data."""
  1144. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1145. adv_data = generate_advertisement_data(
  1146. manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00 "},
  1147. service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"o\x80d"},
  1148. rssi=-67,
  1149. )
  1150. result = parse_advertisement_data(ble_device, adv_data)
  1151. assert result == SwitchBotAdvertisement(
  1152. address="aa:bb:cc:dd:ee:ff",
  1153. data={
  1154. "data": {
  1155. "auto_lock_paused": False,
  1156. "battery": 100,
  1157. "calibration": True,
  1158. "door_open": False,
  1159. "double_lock_mode": False,
  1160. "status": LockStatus.LOCKED,
  1161. "unclosed_alarm": False,
  1162. "unlocked_alarm": False,
  1163. "update_from_secondary_lock": False,
  1164. },
  1165. "isEncrypted": False,
  1166. "model": "o",
  1167. "modelFriendlyName": "Lock",
  1168. "modelName": SwitchbotModel.LOCK,
  1169. "rawAdvData": b"o\x80d",
  1170. },
  1171. device=ble_device,
  1172. rssi=-67,
  1173. active=True,
  1174. )
  1175. def test_parsing_lock_passive():
  1176. """Test parsing lock with active data."""
  1177. ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
  1178. adv_data = generate_advertisement_data(
  1179. manufacturer_data={2409: b"\xf1\t\x9fE\x1a]\x07\x83\x00 "}, rssi=-67
  1180. )
  1181. result = parse_advertisement_data(ble_device, adv_data, SwitchbotModel.LOCK)
  1182. assert result == SwitchBotAdvertisement(
  1183. address="aa:bb:cc:dd:ee:ff",
  1184. data={
  1185. "data": {
  1186. "auto_lock_paused": False,
  1187. "battery": None,
  1188. "calibration": True,
  1189. "door_open": False,
  1190. "double_lock_mode": False,
  1191. "status": LockStatus.LOCKED,
  1192. "unclosed_alarm": False,
  1193. "unlocked_alarm": False,
  1194. "update_from_secondary_lock": False,
  1195. },
  1196. "isEncrypted": False,
  1197. "model": "o",
  1198. "modelFriendlyName": "Lock",
  1199. "modelName": SwitchbotModel.LOCK,
  1200. "rawAdvData": None,
  1201. },
  1202. device=ble_device,
  1203. rssi=-67,
  1204. active=False,
  1205. )