test_adv_parser.py 34 KB

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