adv_parser.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. """Library to handle connection with Switchbot."""
  2. from __future__ import annotations
  3. import logging
  4. from collections import defaultdict
  5. from collections.abc import Callable
  6. from functools import lru_cache
  7. from typing import Any, TypedDict
  8. from bleak.backends.device import BLEDevice
  9. from bleak.backends.scanner import AdvertisementData
  10. from .adv_parsers.air_purifier import process_air_purifier
  11. from .adv_parsers.art_frame import process_art_frame
  12. from .adv_parsers.blind_tilt import process_woblindtilt
  13. from .adv_parsers.bot import process_wohand
  14. from .adv_parsers.bulb import process_color_bulb
  15. from .adv_parsers.ceiling_light import process_woceiling
  16. from .adv_parsers.climate_panel import process_climate_panel
  17. from .adv_parsers.contact import process_wocontact
  18. from .adv_parsers.curtain import process_wocurtain
  19. from .adv_parsers.fan import (
  20. process_circulator_fan_pro,
  21. process_fan,
  22. process_standing_fan,
  23. )
  24. from .adv_parsers.hub2 import process_wohub2
  25. from .adv_parsers.hub3 import process_hub3
  26. from .adv_parsers.hub4 import process_hub4
  27. from .adv_parsers.hubmini_matter import process_hubmini_matter
  28. from .adv_parsers.humidifier import process_evaporative_humidifier, process_wohumidifier
  29. from .adv_parsers.keypad import process_wokeypad
  30. from .adv_parsers.keypad_vision import process_keypad_vision, process_keypad_vision_pro
  31. from .adv_parsers.leak import process_leak
  32. from .adv_parsers.light_strip import (
  33. process_candle_warmer_lamp,
  34. process_light,
  35. process_rgbic_light,
  36. process_rgbicww_ceiling_light,
  37. process_wostrip,
  38. )
  39. from .adv_parsers.lock import (
  40. process_lock2,
  41. process_locklite,
  42. process_wolock,
  43. process_wolock_pro,
  44. )
  45. from .adv_parsers.meter import process_wosensorth, process_wosensorth_c
  46. from .adv_parsers.motion import process_wopresence
  47. from .adv_parsers.plug import process_woplugmini
  48. from .adv_parsers.presence_sensor import process_presence_sensor
  49. from .adv_parsers.relay_switch import (
  50. process_garage_door_opener,
  51. process_relay_switch_1pm,
  52. process_relay_switch_2pm,
  53. process_relay_switch_common_data,
  54. )
  55. from .adv_parsers.remote import process_woremote, process_wouniversal_remote
  56. from .adv_parsers.roller_shade import process_worollershade
  57. from .adv_parsers.smart_thermostat_radiator import process_smart_thermostat_radiator
  58. from .adv_parsers.vacuum import process_vacuum, process_vacuum_k
  59. from .adv_parsers.weather_station import process_weather_station
  60. from .const import SwitchbotModel
  61. from .models import SwitchBotAdvertisement
  62. from .utils import format_mac_upper
  63. _LOGGER = logging.getLogger(__name__)
  64. SERVICE_DATA_ORDER = (
  65. "0000fd3d-0000-1000-8000-00805f9b34fb",
  66. "00000d00-0000-1000-8000-00805f9b34fb",
  67. )
  68. MFR_DATA_ORDER = (2409, 741, 89)
  69. _MODEL_TO_MAC_CACHE: dict[str, SwitchbotModel] = {}
  70. class SwitchbotSupportedType(TypedDict):
  71. """Supported type of Switchbot."""
  72. modelName: SwitchbotModel
  73. modelFriendlyName: str
  74. func: Callable[[bytes | None, bytes | None], dict[str, bool | int | str | None]]
  75. manufacturer_id: int | None
  76. manufacturer_data_length: int | None
  77. SUPPORTED_TYPES: dict[str | bytes, SwitchbotSupportedType] = {
  78. "d": {
  79. "modelName": SwitchbotModel.CONTACT_SENSOR,
  80. "modelFriendlyName": "Contact Sensor",
  81. "func": process_wocontact,
  82. "manufacturer_id": 2409,
  83. },
  84. "D": {
  85. "modelName": SwitchbotModel.CONTACT_SENSOR,
  86. "modelFriendlyName": "Contact Sensor",
  87. "func": process_wocontact,
  88. "manufacturer_id": 2409,
  89. },
  90. "H": {
  91. "modelName": SwitchbotModel.BOT,
  92. "modelFriendlyName": "Bot",
  93. "func": process_wohand,
  94. "manufacturer_id": 89,
  95. },
  96. "s": {
  97. "modelName": SwitchbotModel.MOTION_SENSOR,
  98. "modelFriendlyName": "Motion Sensor",
  99. "func": process_wopresence,
  100. "manufacturer_id": 2409,
  101. },
  102. "S": {
  103. "modelName": SwitchbotModel.MOTION_SENSOR,
  104. "modelFriendlyName": "Motion Sensor",
  105. "func": process_wopresence,
  106. "manufacturer_id": 2409,
  107. },
  108. "r": {
  109. "modelName": SwitchbotModel.LIGHT_STRIP,
  110. "modelFriendlyName": "Light Strip",
  111. "func": process_wostrip,
  112. "manufacturer_id": 2409,
  113. },
  114. "R": {
  115. "modelName": SwitchbotModel.LIGHT_STRIP,
  116. "modelFriendlyName": "Light Strip",
  117. "func": process_wostrip,
  118. "manufacturer_id": 2409,
  119. },
  120. "{": {
  121. "modelName": SwitchbotModel.CURTAIN_3,
  122. "modelFriendlyName": "Curtain 3",
  123. "func": process_wocurtain,
  124. "manufacturer_id": 2409,
  125. },
  126. "[": {
  127. "modelName": SwitchbotModel.CURTAIN_3,
  128. "modelFriendlyName": "Curtain 3",
  129. "func": process_wocurtain,
  130. "manufacturer_id": 2409,
  131. },
  132. "c": {
  133. "modelName": SwitchbotModel.CURTAIN,
  134. "modelFriendlyName": "Curtain",
  135. "func": process_wocurtain,
  136. "manufacturer_id": 2409,
  137. },
  138. "C": {
  139. "modelName": SwitchbotModel.CURTAIN,
  140. "modelFriendlyName": "Curtain",
  141. "func": process_wocurtain,
  142. "manufacturer_id": 2409,
  143. },
  144. "w": {
  145. "modelName": SwitchbotModel.INDOOR_OUTDOOR_THERMO_HYGROMETER,
  146. "modelFriendlyName": "Indoor/Outdoor Thermo-Hygrometer",
  147. "func": process_wosensorth,
  148. "manufacturer_id": 2409,
  149. },
  150. "W": {
  151. "modelName": SwitchbotModel.INDOOR_OUTDOOR_THERMO_HYGROMETER,
  152. "modelFriendlyName": "Indoor/Outdoor Thermo-Hygrometer",
  153. "func": process_wosensorth,
  154. "manufacturer_id": 2409,
  155. },
  156. "i": {
  157. "modelName": SwitchbotModel.METER_PLUS,
  158. "modelFriendlyName": "Meter Plus",
  159. "func": process_wosensorth,
  160. "manufacturer_id": 2409,
  161. },
  162. "I": {
  163. "modelName": SwitchbotModel.METER_PLUS,
  164. "modelFriendlyName": "Meter Plus",
  165. "func": process_wosensorth,
  166. "manufacturer_id": 2409,
  167. },
  168. "T": {
  169. "modelName": SwitchbotModel.METER,
  170. "modelFriendlyName": "Meter",
  171. "func": process_wosensorth,
  172. "manufacturer_id": 2409,
  173. },
  174. "t": {
  175. "modelName": SwitchbotModel.METER,
  176. "modelFriendlyName": "Meter",
  177. "func": process_wosensorth,
  178. "manufacturer_id": 2409,
  179. },
  180. "4": {
  181. "modelName": SwitchbotModel.METER_PRO,
  182. "modelFriendlyName": "Meter Pro",
  183. "func": process_wosensorth,
  184. "manufacturer_id": 2409,
  185. },
  186. b"\x14": {
  187. "modelName": SwitchbotModel.METER_PRO,
  188. "modelFriendlyName": "Meter Pro",
  189. "func": process_wosensorth,
  190. "manufacturer_id": 2409,
  191. },
  192. "5": {
  193. "modelName": SwitchbotModel.METER_PRO_CO2,
  194. "modelFriendlyName": "Meter Pro CO2",
  195. "func": process_wosensorth_c,
  196. "manufacturer_id": 2409,
  197. },
  198. b"\x15": {
  199. "modelName": SwitchbotModel.METER_PRO_CO2,
  200. "modelFriendlyName": "Meter Pro CO2",
  201. "func": process_wosensorth_c,
  202. "manufacturer_id": 2409,
  203. },
  204. "v": {
  205. "modelName": SwitchbotModel.HUB2,
  206. "modelFriendlyName": "Hub 2",
  207. "func": process_wohub2,
  208. "manufacturer_id": 2409,
  209. },
  210. "V": {
  211. "modelName": SwitchbotModel.HUB2,
  212. "modelFriendlyName": "Hub 2",
  213. "func": process_wohub2,
  214. "manufacturer_id": 2409,
  215. },
  216. "g": {
  217. "modelName": SwitchbotModel.PLUG_MINI_US,
  218. "modelFriendlyName": "Plug Mini (US)",
  219. "func": process_woplugmini,
  220. "manufacturer_id": 2409,
  221. },
  222. "G": {
  223. "modelName": SwitchbotModel.PLUG_MINI_US,
  224. "modelFriendlyName": "Plug Mini (US)",
  225. "func": process_woplugmini,
  226. "manufacturer_id": 2409,
  227. },
  228. "j": {
  229. "modelName": SwitchbotModel.PLUG_MINI_JP,
  230. "modelFriendlyName": "Plug Mini (JP)",
  231. "func": process_woplugmini,
  232. "manufacturer_id": 2409,
  233. },
  234. "J": {
  235. "modelName": SwitchbotModel.PLUG_MINI_JP,
  236. "modelFriendlyName": "Plug Mini (JP)",
  237. "func": process_woplugmini,
  238. "manufacturer_id": 2409,
  239. },
  240. "u": {
  241. "modelName": SwitchbotModel.COLOR_BULB,
  242. "modelFriendlyName": "Color Bulb",
  243. "func": process_color_bulb,
  244. "manufacturer_id": 2409,
  245. },
  246. "U": {
  247. "modelName": SwitchbotModel.COLOR_BULB,
  248. "modelFriendlyName": "Color Bulb",
  249. "func": process_color_bulb,
  250. "manufacturer_id": 2409,
  251. },
  252. "q": {
  253. "modelName": SwitchbotModel.CEILING_LIGHT,
  254. "modelFriendlyName": "Ceiling Light",
  255. "func": process_woceiling,
  256. "manufacturer_id": 2409,
  257. },
  258. "Q": {
  259. "modelName": SwitchbotModel.CEILING_LIGHT,
  260. "modelFriendlyName": "Ceiling Light",
  261. "func": process_woceiling,
  262. "manufacturer_id": 2409,
  263. },
  264. "n": {
  265. "modelName": SwitchbotModel.CEILING_LIGHT_PRO,
  266. "modelFriendlyName": "Ceiling Light Pro",
  267. "func": process_woceiling,
  268. "manufacturer_id": 2409,
  269. },
  270. "N": {
  271. "modelName": SwitchbotModel.CEILING_LIGHT_PRO,
  272. "modelFriendlyName": "Ceiling Light Pro",
  273. "func": process_woceiling,
  274. "manufacturer_id": 2409,
  275. },
  276. "e": {
  277. "modelName": SwitchbotModel.HUMIDIFIER,
  278. "modelFriendlyName": "Humidifier",
  279. "func": process_wohumidifier,
  280. "manufacturer_id": 741,
  281. "manufacturer_data_length": 6,
  282. },
  283. "E": {
  284. "modelName": SwitchbotModel.HUMIDIFIER,
  285. "modelFriendlyName": "Humidifier",
  286. "func": process_wohumidifier,
  287. "manufacturer_id": 741,
  288. "manufacturer_data_length": 6,
  289. },
  290. "#": {
  291. "modelName": SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  292. "modelFriendlyName": "Evaporative Humidifier",
  293. "func": process_evaporative_humidifier,
  294. "manufacturer_id": 2409,
  295. },
  296. b"\x03": {
  297. "modelName": SwitchbotModel.EVAPORATIVE_HUMIDIFIER,
  298. "modelFriendlyName": "Evaporative Humidifier",
  299. "func": process_evaporative_humidifier,
  300. "manufacturer_id": 2409,
  301. },
  302. "o": {
  303. "modelName": SwitchbotModel.LOCK,
  304. "modelFriendlyName": "Lock",
  305. "func": process_wolock,
  306. "manufacturer_id": 2409,
  307. },
  308. "O": {
  309. "modelName": SwitchbotModel.LOCK,
  310. "modelFriendlyName": "Lock",
  311. "func": process_wolock,
  312. "manufacturer_id": 2409,
  313. },
  314. "$": {
  315. "modelName": SwitchbotModel.LOCK_PRO,
  316. "modelFriendlyName": "Lock Pro",
  317. "func": process_wolock_pro,
  318. "manufacturer_id": 2409,
  319. },
  320. b"\x04": {
  321. "modelName": SwitchbotModel.LOCK_PRO,
  322. "modelFriendlyName": "Lock Pro",
  323. "func": process_wolock_pro,
  324. "manufacturer_id": 2409,
  325. },
  326. "x": {
  327. "modelName": SwitchbotModel.BLIND_TILT,
  328. "modelFriendlyName": "Blind Tilt",
  329. "func": process_woblindtilt,
  330. "manufacturer_id": 2409,
  331. },
  332. "X": {
  333. "modelName": SwitchbotModel.BLIND_TILT,
  334. "modelFriendlyName": "Blind Tilt",
  335. "func": process_woblindtilt,
  336. "manufacturer_id": 2409,
  337. },
  338. "&": {
  339. "modelName": SwitchbotModel.LEAK,
  340. "modelFriendlyName": "Leak Detector",
  341. "func": process_leak,
  342. "manufacturer_id": 2409,
  343. },
  344. b"\x06": {
  345. "modelName": SwitchbotModel.LEAK,
  346. "modelFriendlyName": "Leak Detector",
  347. "func": process_leak,
  348. "manufacturer_id": 2409,
  349. },
  350. "y": {
  351. "modelName": SwitchbotModel.KEYPAD,
  352. "modelFriendlyName": "Keypad",
  353. "func": process_wokeypad,
  354. "manufacturer_id": 2409,
  355. },
  356. "Y": {
  357. "modelName": SwitchbotModel.KEYPAD,
  358. "modelFriendlyName": "Keypad",
  359. "func": process_wokeypad,
  360. "manufacturer_id": 2409,
  361. },
  362. "<": {
  363. "modelName": SwitchbotModel.RELAY_SWITCH_1PM,
  364. "modelFriendlyName": "Relay Switch 1PM",
  365. "func": process_relay_switch_1pm,
  366. "manufacturer_id": 2409,
  367. },
  368. b"\x1c": {
  369. "modelName": SwitchbotModel.RELAY_SWITCH_1PM,
  370. "modelFriendlyName": "Relay Switch 1PM",
  371. "func": process_relay_switch_1pm,
  372. "manufacturer_id": 2409,
  373. },
  374. ";": {
  375. "modelName": SwitchbotModel.RELAY_SWITCH_1,
  376. "modelFriendlyName": "Relay Switch 1",
  377. "func": process_relay_switch_common_data,
  378. "manufacturer_id": 2409,
  379. },
  380. b"\x1b": {
  381. "modelName": SwitchbotModel.RELAY_SWITCH_1,
  382. "modelFriendlyName": "Relay Switch 1",
  383. "func": process_relay_switch_common_data,
  384. "manufacturer_id": 2409,
  385. },
  386. "b": {
  387. "modelName": SwitchbotModel.REMOTE,
  388. "modelFriendlyName": "Remote",
  389. "func": process_woremote,
  390. "manufacturer_id": 89,
  391. },
  392. "B": {
  393. "modelName": SwitchbotModel.REMOTE,
  394. "modelFriendlyName": "Remote",
  395. "func": process_woremote,
  396. "manufacturer_id": 89,
  397. },
  398. "\x07": {
  399. "modelName": SwitchbotModel.UNIVERSAL_REMOTE,
  400. "modelFriendlyName": "Universal Remote",
  401. "func": process_wouniversal_remote,
  402. "manufacturer_id": 2409,
  403. },
  404. "'": {
  405. "modelName": SwitchbotModel.UNIVERSAL_REMOTE,
  406. "modelFriendlyName": "Universal Remote",
  407. "func": process_wouniversal_remote,
  408. "manufacturer_id": 2409,
  409. },
  410. ",": {
  411. "modelName": SwitchbotModel.ROLLER_SHADE,
  412. "modelFriendlyName": "Roller Shade",
  413. "func": process_worollershade,
  414. "manufacturer_id": 2409,
  415. },
  416. b"\x0c": {
  417. "modelName": SwitchbotModel.ROLLER_SHADE,
  418. "modelFriendlyName": "Roller Shade",
  419. "func": process_worollershade,
  420. "manufacturer_id": 2409,
  421. },
  422. "%": {
  423. "modelName": SwitchbotModel.HUBMINI_MATTER,
  424. "modelFriendlyName": "HubMini Matter",
  425. "func": process_hubmini_matter,
  426. "manufacturer_id": 2409,
  427. },
  428. b"\x05": {
  429. "modelName": SwitchbotModel.HUBMINI_MATTER,
  430. "modelFriendlyName": "HubMini Matter",
  431. "func": process_hubmini_matter,
  432. "manufacturer_id": 2409,
  433. },
  434. "~": {
  435. "modelName": SwitchbotModel.CIRCULATOR_FAN,
  436. "modelFriendlyName": "Circulator Fan",
  437. "func": process_fan,
  438. "manufacturer_id": 2409,
  439. },
  440. "^": {
  441. "modelName": SwitchbotModel.CIRCULATOR_FAN,
  442. "modelFriendlyName": "Circulator Fan",
  443. "func": process_fan,
  444. "manufacturer_id": 2409,
  445. },
  446. ".": {
  447. "modelName": SwitchbotModel.K20_VACUUM,
  448. "modelFriendlyName": "K20 Vacuum",
  449. "func": process_vacuum,
  450. "manufacturer_id": 2409,
  451. },
  452. b"\x0f": {
  453. "modelName": SwitchbotModel.K20_VACUUM,
  454. "modelFriendlyName": "K20 Vacuum",
  455. "func": process_vacuum,
  456. "manufacturer_id": 2409,
  457. },
  458. "z": {
  459. "modelName": SwitchbotModel.S10_VACUUM,
  460. "modelFriendlyName": "S10 Vacuum",
  461. "func": process_vacuum,
  462. "manufacturer_id": 2409,
  463. },
  464. "Z": {
  465. "modelName": SwitchbotModel.S10_VACUUM,
  466. "modelFriendlyName": "S10 Vacuum",
  467. "func": process_vacuum,
  468. "manufacturer_id": 2409,
  469. },
  470. "3": {
  471. "modelName": SwitchbotModel.K10_PRO_COMBO_VACUUM,
  472. "modelFriendlyName": "K10+ Pro Combo Vacuum",
  473. "func": process_vacuum,
  474. "manufacturer_id": 2409,
  475. },
  476. b"\x13": {
  477. "modelName": SwitchbotModel.K10_PRO_COMBO_VACUUM,
  478. "modelFriendlyName": "K10+ Pro Combo Vacuum",
  479. "func": process_vacuum,
  480. "manufacturer_id": 2409,
  481. },
  482. "}": {
  483. "modelName": SwitchbotModel.K10_VACUUM,
  484. "modelFriendlyName": "K10+ Vacuum",
  485. "func": process_vacuum_k,
  486. "manufacturer_id": 2409,
  487. },
  488. "]": {
  489. "modelName": SwitchbotModel.K10_VACUUM,
  490. "modelFriendlyName": "K10+ Vacuum",
  491. "func": process_vacuum_k,
  492. "manufacturer_id": 2409,
  493. },
  494. "(": {
  495. "modelName": SwitchbotModel.K10_PRO_VACUUM,
  496. "modelFriendlyName": "K10+ Pro Vacuum",
  497. "func": process_vacuum_k,
  498. "manufacturer_id": 2409,
  499. },
  500. b"\x08": {
  501. "modelName": SwitchbotModel.K10_PRO_VACUUM,
  502. "modelFriendlyName": "K10+ Pro Vacuum",
  503. "func": process_vacuum_k,
  504. "manufacturer_id": 2409,
  505. },
  506. "*": {
  507. "modelName": SwitchbotModel.AIR_PURIFIER_US,
  508. "modelFriendlyName": "Air Purifier US",
  509. "func": process_air_purifier,
  510. "manufacturer_id": 2409,
  511. },
  512. b"\x0a": {
  513. "modelName": SwitchbotModel.AIR_PURIFIER_US,
  514. "modelFriendlyName": "Air Purifier US",
  515. "func": process_air_purifier,
  516. "manufacturer_id": 2409,
  517. },
  518. "+": {
  519. "modelName": SwitchbotModel.AIR_PURIFIER_JP,
  520. "modelFriendlyName": "Air Purifier JP",
  521. "func": process_air_purifier,
  522. "manufacturer_id": 2409,
  523. },
  524. b"\x0b": {
  525. "modelName": SwitchbotModel.AIR_PURIFIER_JP,
  526. "modelFriendlyName": "Air Purifier JP",
  527. "func": process_air_purifier,
  528. "manufacturer_id": 2409,
  529. },
  530. "7": {
  531. "modelName": SwitchbotModel.AIR_PURIFIER_TABLE_US,
  532. "modelFriendlyName": "Air Purifier Table US",
  533. "func": process_air_purifier,
  534. "manufacturer_id": 2409,
  535. },
  536. b"\x17": {
  537. "modelName": SwitchbotModel.AIR_PURIFIER_TABLE_US,
  538. "modelFriendlyName": "Air Purifier Table US",
  539. "func": process_air_purifier,
  540. "manufacturer_id": 2409,
  541. },
  542. "8": {
  543. "modelName": SwitchbotModel.AIR_PURIFIER_TABLE_JP,
  544. "modelFriendlyName": "Air Purifier Table JP",
  545. "func": process_air_purifier,
  546. "manufacturer_id": 2409,
  547. },
  548. b"\x18": {
  549. "modelName": SwitchbotModel.AIR_PURIFIER_TABLE_JP,
  550. "modelFriendlyName": "Air Purifier Table JP",
  551. "func": process_air_purifier,
  552. "manufacturer_id": 2409,
  553. },
  554. b"\x00\x10\xb9\x40": {
  555. "modelName": SwitchbotModel.HUB3,
  556. "modelFriendlyName": "Hub3",
  557. "func": process_hub3,
  558. "manufacturer_id": 2409,
  559. },
  560. b"\x01\x10\xb9\x40": {
  561. "modelName": SwitchbotModel.HUB3,
  562. "modelFriendlyName": "Hub3",
  563. "func": process_hub3,
  564. "manufacturer_id": 2409,
  565. },
  566. b"\x00\x12\x82\x48": {
  567. "modelName": SwitchbotModel.HUB4,
  568. "modelFriendlyName": "Hub4",
  569. "func": process_hub4,
  570. "manufacturer_id": 2409,
  571. },
  572. b"\x01\x12\x82\x48": {
  573. "modelName": SwitchbotModel.HUB4,
  574. "modelFriendlyName": "Hub4",
  575. "func": process_hub4,
  576. "manufacturer_id": 2409,
  577. },
  578. "-": {
  579. "modelName": SwitchbotModel.LOCK_LITE,
  580. "modelFriendlyName": "Lock Lite",
  581. "func": process_locklite,
  582. "manufacturer_id": 2409,
  583. },
  584. b"\x0d": {
  585. "modelName": SwitchbotModel.LOCK_LITE,
  586. "modelFriendlyName": "Lock Lite",
  587. "func": process_locklite,
  588. "manufacturer_id": 2409,
  589. },
  590. b"\x00\x10\xa5\xb8": {
  591. "modelName": SwitchbotModel.LOCK_ULTRA,
  592. "modelFriendlyName": "Lock Ultra",
  593. "func": process_lock2,
  594. "manufacturer_id": 2409,
  595. },
  596. b"\x01\x10\xa5\xb8": {
  597. "modelName": SwitchbotModel.LOCK_ULTRA,
  598. "modelFriendlyName": "Lock Ultra",
  599. "func": process_lock2,
  600. "manufacturer_id": 2409,
  601. },
  602. ">": {
  603. "modelName": SwitchbotModel.GARAGE_DOOR_OPENER,
  604. "modelFriendlyName": "Garage Door Opener",
  605. "func": process_garage_door_opener,
  606. "manufacturer_id": 2409,
  607. },
  608. b"\x1e": {
  609. "modelName": SwitchbotModel.GARAGE_DOOR_OPENER,
  610. "modelFriendlyName": "Garage Door Opener",
  611. "func": process_garage_door_opener,
  612. "manufacturer_id": 2409,
  613. },
  614. "=": {
  615. "modelName": SwitchbotModel.RELAY_SWITCH_2PM,
  616. "modelFriendlyName": "Relay Switch 2PM",
  617. "func": process_relay_switch_2pm,
  618. "manufacturer_id": 2409,
  619. },
  620. b"\x1d": {
  621. "modelName": SwitchbotModel.RELAY_SWITCH_2PM,
  622. "modelFriendlyName": "Relay Switch 2PM",
  623. "func": process_relay_switch_2pm,
  624. "manufacturer_id": 2409,
  625. },
  626. b"\x00\x10\xd0\xb0": {
  627. "modelName": SwitchbotModel.FLOOR_LAMP,
  628. "modelFriendlyName": "Floor Lamp",
  629. "func": process_light,
  630. "manufacturer_id": 2409,
  631. },
  632. b"\x01\x10\xd0\xb0": {
  633. "modelName": SwitchbotModel.FLOOR_LAMP,
  634. "modelFriendlyName": "Floor Lamp",
  635. "func": process_light,
  636. "manufacturer_id": 2409,
  637. },
  638. b"\x00\x11\x22\xb8": {
  639. "modelName": SwitchbotModel.CANDLE_WARMER_LAMP,
  640. "modelFriendlyName": "Candle Warmer Lamp",
  641. "func": process_candle_warmer_lamp,
  642. "manufacturer_id": 2409,
  643. },
  644. b"\x01\x11\x22\xb8": {
  645. "modelName": SwitchbotModel.CANDLE_WARMER_LAMP,
  646. "modelFriendlyName": "Candle Warmer Lamp",
  647. "func": process_candle_warmer_lamp,
  648. "manufacturer_id": 2409,
  649. },
  650. b"\x00\x10\xd0\xb1": {
  651. "modelName": SwitchbotModel.STRIP_LIGHT_3,
  652. "modelFriendlyName": "Strip Light 3",
  653. "func": process_light,
  654. "manufacturer_id": 2409,
  655. },
  656. b"\x01\x10\xd0\xb1": {
  657. "modelName": SwitchbotModel.STRIP_LIGHT_3,
  658. "modelFriendlyName": "Strip Light 3",
  659. "func": process_light,
  660. "manufacturer_id": 2409,
  661. },
  662. "?": {
  663. "modelName": SwitchbotModel.PLUG_MINI_EU,
  664. "modelFriendlyName": "Plug Mini (EU)",
  665. "func": process_relay_switch_1pm,
  666. "manufacturer_id": 2409,
  667. },
  668. b"\x1f": {
  669. "modelName": SwitchbotModel.PLUG_MINI_EU,
  670. "modelFriendlyName": "Plug Mini (EU)",
  671. "func": process_relay_switch_1pm,
  672. "manufacturer_id": 2409,
  673. },
  674. b"\x00\x10\xd0\xb3": {
  675. "modelName": SwitchbotModel.RGBICWW_STRIP_LIGHT,
  676. "modelFriendlyName": "RGBICWW Strip Light",
  677. "func": process_rgbic_light,
  678. "manufacturer_id": 2409,
  679. },
  680. b"\x01\x10\xd0\xb3": {
  681. "modelName": SwitchbotModel.RGBICWW_STRIP_LIGHT,
  682. "modelFriendlyName": "RGBICWW Strip Light",
  683. "func": process_rgbic_light,
  684. "manufacturer_id": 2409,
  685. },
  686. b"\x00\x10\xd0\xb4": {
  687. "modelName": SwitchbotModel.RGBICWW_FLOOR_LAMP,
  688. "modelFriendlyName": "RGBICWW Floor Lamp",
  689. "func": process_rgbic_light,
  690. "manufacturer_id": 2409,
  691. },
  692. b"\x01\x10\xd0\xb4": {
  693. "modelName": SwitchbotModel.RGBICWW_FLOOR_LAMP,
  694. "modelFriendlyName": "RGBICWW Floor Lamp",
  695. "func": process_rgbic_light,
  696. "manufacturer_id": 2409,
  697. },
  698. b"\x00\x11\xbe\xf8": {
  699. "modelName": SwitchbotModel.RGBICWW_LIGHT_BARS,
  700. "modelFriendlyName": "RGBICWW Light Bars",
  701. "func": process_rgbic_light,
  702. "manufacturer_id": 2409,
  703. },
  704. b"\x01\x11\xbe\xf8": {
  705. "modelName": SwitchbotModel.RGBICWW_LIGHT_BARS,
  706. "modelFriendlyName": "RGBICWW Light Bars",
  707. "func": process_rgbic_light,
  708. "manufacturer_id": 2409,
  709. },
  710. b"\x00\x11\xbb\x10": {
  711. "modelName": SwitchbotModel.RGBICWW_CEILING_LIGHT,
  712. "modelFriendlyName": "RGBICWW Ceiling Light",
  713. "func": process_rgbicww_ceiling_light,
  714. "manufacturer_id": 2409,
  715. },
  716. b"\x01\x11\xbb\x10": {
  717. "modelName": SwitchbotModel.RGBICWW_CEILING_LIGHT,
  718. "modelFriendlyName": "RGBICWW Ceiling Light",
  719. "func": process_rgbicww_ceiling_light,
  720. "manufacturer_id": 2409,
  721. },
  722. b"\x00\x11\xb3@": {
  723. "modelName": SwitchbotModel.CIRCULATOR_FAN_PRO,
  724. "modelFriendlyName": "Circulator Fan Pro",
  725. "func": process_circulator_fan_pro,
  726. "manufacturer_id": 2409,
  727. },
  728. b"\x01\x11\xb3@": {
  729. "modelName": SwitchbotModel.CIRCULATOR_FAN_PRO,
  730. "modelFriendlyName": "Circulator Fan Pro",
  731. "func": process_circulator_fan_pro,
  732. "manufacturer_id": 2409,
  733. },
  734. b"\x00\x10\xd0\xb7": {
  735. "modelName": SwitchbotModel.PERMANENT_OUTDOOR_LIGHT,
  736. "modelFriendlyName": "Permanent Outdoor Light",
  737. "func": process_rgbic_light,
  738. "manufacturer_id": 2409,
  739. },
  740. b"\x01\x10\xd0\xb7": {
  741. "modelName": SwitchbotModel.PERMANENT_OUTDOOR_LIGHT,
  742. "modelFriendlyName": "Permanent Outdoor Light",
  743. "func": process_rgbic_light,
  744. "manufacturer_id": 2409,
  745. },
  746. b"\x00\x10\xd0\xb5": {
  747. "modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT,
  748. "modelFriendlyName": "RGBIC Neon Wire Rope Light",
  749. "func": process_wostrip,
  750. "manufacturer_id": 2409,
  751. },
  752. b"\x00\x10\xd0\xb6": {
  753. "modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT,
  754. "modelFriendlyName": "RGBIC Neon Rope Light",
  755. "func": process_wostrip,
  756. "manufacturer_id": 2409,
  757. },
  758. b"\x01\x10\xd0\xb5": {
  759. "modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT,
  760. "modelFriendlyName": "RGBIC Neon Wire Rope Light",
  761. "func": process_wostrip,
  762. "manufacturer_id": 2409,
  763. },
  764. b"\x01\x10\xd0\xb6": {
  765. "modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT,
  766. "modelFriendlyName": "RGBIC Neon Rope Light",
  767. "func": process_wostrip,
  768. "manufacturer_id": 2409,
  769. },
  770. b"\x00\x10\xfb\xa8": {
  771. "modelName": SwitchbotModel.K11_VACUUM,
  772. "modelFriendlyName": "K11+ Vacuum",
  773. "func": process_vacuum,
  774. "manufacturer_id": 2409,
  775. },
  776. b"\x01\x10\xfb\xa8": {
  777. "modelName": SwitchbotModel.K11_VACUUM,
  778. "modelFriendlyName": "K11+ Vacuum",
  779. "func": process_vacuum,
  780. "manufacturer_id": 2409,
  781. },
  782. b"\x00\x10\xf3\xd8": {
  783. "modelName": SwitchbotModel.CLIMATE_PANEL,
  784. "modelFriendlyName": "Climate Panel",
  785. "func": process_climate_panel,
  786. "manufacturer_id": 2409,
  787. },
  788. b"\x01\x10\xf3\xd8": {
  789. "modelName": SwitchbotModel.CLIMATE_PANEL,
  790. "modelFriendlyName": "Climate Panel",
  791. "func": process_climate_panel,
  792. "manufacturer_id": 2409,
  793. },
  794. b"\x00\x116@": {
  795. "modelName": SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
  796. "modelFriendlyName": "Smart Thermostat Radiator",
  797. "func": process_smart_thermostat_radiator,
  798. "manufacturer_id": 2409,
  799. },
  800. b"\x01\x116@": {
  801. "modelName": SwitchbotModel.SMART_THERMOSTAT_RADIATOR,
  802. "modelFriendlyName": "Smart Thermostat Radiator",
  803. "func": process_smart_thermostat_radiator,
  804. "manufacturer_id": 2409,
  805. },
  806. b"\x00\x10\xe0P": {
  807. "modelName": SwitchbotModel.S20_VACUUM,
  808. "modelFriendlyName": "S20 Vacuum",
  809. "func": process_vacuum,
  810. "manufacturer_id": 2409,
  811. },
  812. b"\x01\x10\xe0P": {
  813. "modelName": SwitchbotModel.S20_VACUUM,
  814. "modelFriendlyName": "S20 Vacuum",
  815. "func": process_vacuum,
  816. "manufacturer_id": 2409,
  817. },
  818. b"\x00\x10\xcc\xc8": {
  819. "modelName": SwitchbotModel.PRESENCE_SENSOR,
  820. "modelFriendlyName": "Presence Sensor",
  821. "func": process_presence_sensor,
  822. "manufacturer_id": 2409,
  823. },
  824. b"\x01\x10\xcc\xc8": {
  825. "modelName": SwitchbotModel.PRESENCE_SENSOR,
  826. "modelFriendlyName": "Presence Sensor",
  827. "func": process_presence_sensor,
  828. "manufacturer_id": 2409,
  829. },
  830. b"\x00\x11>\x10": {
  831. "modelName": SwitchbotModel.ART_FRAME,
  832. "modelFriendlyName": "Art Frame",
  833. "func": process_art_frame,
  834. "manufacturer_id": 2409,
  835. },
  836. b"\x01\x11>\x10": {
  837. "modelName": SwitchbotModel.ART_FRAME,
  838. "modelFriendlyName": "Art Frame",
  839. "func": process_art_frame,
  840. "manufacturer_id": 2409,
  841. },
  842. b"\x00\x11\x03x": {
  843. "modelName": SwitchbotModel.KEYPAD_VISION,
  844. "modelFriendlyName": "Keypad Vision",
  845. "func": process_keypad_vision,
  846. "manufacturer_id": 2409,
  847. },
  848. b"\x01\x11\x03x": {
  849. "modelName": SwitchbotModel.KEYPAD_VISION,
  850. "modelFriendlyName": "Keypad Vision",
  851. "func": process_keypad_vision,
  852. "manufacturer_id": 2409,
  853. },
  854. b"\x00\x11Q\x98": {
  855. "modelName": SwitchbotModel.KEYPAD_VISION_PRO,
  856. "modelFriendlyName": "Keypad Vision Pro",
  857. "func": process_keypad_vision_pro,
  858. "manufacturer_id": 2409,
  859. },
  860. b"\x01\x11Q\x98": {
  861. "modelName": SwitchbotModel.KEYPAD_VISION_PRO,
  862. "modelFriendlyName": "Keypad Vision Pro",
  863. "func": process_keypad_vision_pro,
  864. "manufacturer_id": 2409,
  865. },
  866. b"\x00\x11\x69\x09": {
  867. "modelName": SwitchbotModel.LOCK_VISION_PRO,
  868. "modelFriendlyName": "Lock Vision Pro",
  869. "func": process_lock2,
  870. "manufacturer_id": 2409,
  871. },
  872. b"\x01\x11\x69\x09": {
  873. "modelName": SwitchbotModel.LOCK_VISION_PRO,
  874. "modelFriendlyName": "Lock Vision Pro",
  875. "func": process_lock2,
  876. "manufacturer_id": 2409,
  877. },
  878. b"\x00\x11\x69\x08": {
  879. "modelName": SwitchbotModel.LOCK_VISION,
  880. "modelFriendlyName": "Lock Vision",
  881. "func": process_locklite,
  882. "manufacturer_id": 2409,
  883. },
  884. b"\x01\x11\x69\x08": {
  885. "modelName": SwitchbotModel.LOCK_VISION,
  886. "modelFriendlyName": "Lock Vision",
  887. "func": process_locklite,
  888. "manufacturer_id": 2409,
  889. },
  890. b"\x00\x10\xff\x90": {
  891. "modelName": SwitchbotModel.LOCK_PRO_WIFI,
  892. "modelFriendlyName": "Lock Pro Wifi",
  893. "func": process_wolock_pro,
  894. "manufacturer_id": 2409,
  895. },
  896. b"\x01\x10\xff\x90": {
  897. "modelName": SwitchbotModel.LOCK_PRO_WIFI,
  898. "modelFriendlyName": "Lock Pro Wifi",
  899. "func": process_wolock_pro,
  900. "manufacturer_id": 2409,
  901. },
  902. b"\x00\x11\x9f\xb8": {
  903. "modelName": SwitchbotModel.LOCK_ULTRA_MAX,
  904. "modelFriendlyName": "Lock Ultra Max",
  905. "func": process_lock2,
  906. "manufacturer_id": 2409,
  907. },
  908. b"\x01\x11\x9f\xb8": {
  909. "modelName": SwitchbotModel.LOCK_ULTRA_MAX,
  910. "modelFriendlyName": "Lock Ultra Max",
  911. "func": process_lock2,
  912. "manufacturer_id": 2409,
  913. },
  914. b"\x00\x11\x07\x60": {
  915. "modelName": SwitchbotModel.STANDING_FAN,
  916. "modelFriendlyName": "Standing Fan",
  917. "func": process_standing_fan,
  918. "manufacturer_id": 2409,
  919. },
  920. b"\x01\x11\x07\x60": {
  921. "modelName": SwitchbotModel.STANDING_FAN,
  922. "modelFriendlyName": "Standing Fan",
  923. "func": process_standing_fan,
  924. "manufacturer_id": 2409,
  925. },
  926. b"\x00\x10\x53\xb0": {
  927. "modelName": SwitchbotModel.WEATHER_STATION,
  928. "modelFriendlyName": "Weather Station",
  929. "func": process_weather_station,
  930. "manufacturer_id": 2409,
  931. },
  932. b"\x01\x10\x53\xb0": {
  933. "modelName": SwitchbotModel.WEATHER_STATION,
  934. "modelFriendlyName": "Weather Station",
  935. "func": process_weather_station,
  936. "manufacturer_id": 2409,
  937. },
  938. }
  939. _SWITCHBOT_MODEL_TO_CHAR: defaultdict[SwitchbotModel, list[str | bytes]] = defaultdict(
  940. list
  941. )
  942. for model_chr, model_data in SUPPORTED_TYPES.items():
  943. _SWITCHBOT_MODEL_TO_CHAR[model_data["modelName"]].append(model_chr)
  944. MODELS_BY_MANUFACTURER_DATA: dict[int, list[tuple[str, SwitchbotSupportedType]]] = {
  945. mfr_id: [] for mfr_id in MFR_DATA_ORDER
  946. }
  947. for model_chr, model in SUPPORTED_TYPES.items():
  948. if "manufacturer_id" in model:
  949. mfr_id = model["manufacturer_id"]
  950. MODELS_BY_MANUFACTURER_DATA[mfr_id].append((model_chr, model))
  951. def parse_advertisement_data(
  952. device: BLEDevice,
  953. advertisement_data: AdvertisementData,
  954. model: SwitchbotModel | None = None,
  955. ) -> SwitchBotAdvertisement | None:
  956. """Parse advertisement data."""
  957. upper_mac = format_mac_upper(device.address)
  958. if model is None and upper_mac in _MODEL_TO_MAC_CACHE:
  959. model = _MODEL_TO_MAC_CACHE[upper_mac]
  960. service_data = advertisement_data.service_data
  961. _service_data = None
  962. for uuid in SERVICE_DATA_ORDER:
  963. if uuid in service_data:
  964. _service_data = service_data[uuid]
  965. break
  966. _mfr_data = None
  967. _mfr_id = None
  968. for mfr_id in MFR_DATA_ORDER:
  969. if mfr_id in advertisement_data.manufacturer_data:
  970. _mfr_id = mfr_id
  971. _mfr_data = advertisement_data.manufacturer_data[mfr_id]
  972. break
  973. if _mfr_data is None and _service_data is None:
  974. return None
  975. try:
  976. data = _parse_data(
  977. _service_data,
  978. _mfr_data,
  979. _mfr_id,
  980. model,
  981. )
  982. except Exception: # pylint: disable=broad-except
  983. _LOGGER.exception("Failed to parse advertisement data: %s", advertisement_data)
  984. return None
  985. if not data:
  986. return None
  987. return SwitchBotAdvertisement(
  988. device.address, data, device, advertisement_data.rssi, bool(_service_data)
  989. )
  990. def _find_model_from_service_data(_service_data: bytes) -> str | bytes | None:
  991. """Find model from service data."""
  992. char_model = chr(_service_data[0] & 0b01111111)
  993. if char_model in SUPPORTED_TYPES:
  994. return char_model
  995. byte_model = bytes([_service_data[0] & 0b01111111])
  996. if byte_model in SUPPORTED_TYPES:
  997. return byte_model
  998. return None
  999. def _find_model_from_switchbot_model(
  1000. _switchbot_model: SwitchbotModel,
  1001. ) -> str | bytes | None:
  1002. """Find model from switchbot model."""
  1003. if _switchbot_model in _SWITCHBOT_MODEL_TO_CHAR:
  1004. return _SWITCHBOT_MODEL_TO_CHAR[_switchbot_model][0]
  1005. return None
  1006. def _find_model_from_manufacturer_data(
  1007. _mfr_id: int, _mfr_data: bytes | None
  1008. ) -> str | bytes | None:
  1009. """Find model from manufacturer data."""
  1010. if _mfr_id not in MODELS_BY_MANUFACTURER_DATA or _mfr_data is None:
  1011. return None
  1012. for model_chr, model_data in MODELS_BY_MANUFACTURER_DATA[_mfr_id]:
  1013. expected_length = model_data.get("manufacturer_data_length")
  1014. if expected_length is not None and expected_length == len(_mfr_data):
  1015. return model_chr
  1016. return None
  1017. def _find_model_from_service_data_suffix(_service_data: bytes) -> str | bytes | None:
  1018. """Find model from service data suffix."""
  1019. if len(_service_data) <= 5:
  1020. return None
  1021. for s in (_service_data[-4:], _service_data[-5:-1]):
  1022. if s in SUPPORTED_TYPES:
  1023. return s
  1024. return None
  1025. def build_advertisement_data(
  1026. _model: str | bytes, _service_data: bytes | None, _mfr_data: bytes | None
  1027. ) -> dict[str, Any]:
  1028. """Build advertisement data dictionary."""
  1029. _isEncrypted = bool(_service_data[0] & 0b10000000) if _service_data else False
  1030. data = {
  1031. "rawAdvData": _service_data,
  1032. "data": {},
  1033. "model": _model,
  1034. "isEncrypted": _isEncrypted,
  1035. }
  1036. type_data = SUPPORTED_TYPES.get(_model)
  1037. if type_data:
  1038. model_data = type_data["func"](_service_data, _mfr_data)
  1039. if model_data:
  1040. data.update(
  1041. {
  1042. "modelFriendlyName": type_data["modelFriendlyName"],
  1043. "modelName": type_data["modelName"],
  1044. "data": model_data,
  1045. }
  1046. )
  1047. return data
  1048. @lru_cache(maxsize=128)
  1049. def _parse_data(
  1050. _service_data: bytes | None,
  1051. _mfr_data: bytes | None,
  1052. _mfr_id: int | None = None,
  1053. _switchbot_model: SwitchbotModel | None = None,
  1054. ) -> dict[str, Any] | None:
  1055. """Parse advertisement data."""
  1056. _model = None
  1057. if _service_data:
  1058. _model = _find_model_from_service_data(_service_data)
  1059. if not _model and _switchbot_model:
  1060. _model = _find_model_from_switchbot_model(_switchbot_model)
  1061. if not _model and _mfr_id:
  1062. _model = _find_model_from_manufacturer_data(_mfr_id, _mfr_data)
  1063. if not _model and _service_data:
  1064. _model = _find_model_from_service_data_suffix(_service_data)
  1065. if not _model:
  1066. return None
  1067. return build_advertisement_data(_model, _service_data, _mfr_data)
  1068. def populate_model_to_mac_cache(mac: str, model: SwitchbotModel) -> None:
  1069. """Populate the model to MAC address cache."""
  1070. _MODEL_TO_MAC_CACHE[mac] = model