test_adv_parser.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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={2409: 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. )