adv_parser.py 34 KB

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