Advertisement
kahthan

ha broadlink mp1 support

Feb 20th, 2017
626
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.89 KB | None | 0 0
  1. """
  2. Support for Broadlink RM devices.
  3.  
  4. For more details about this platform, please refer to the documentation at
  5. https://home-assistant.io/components/switch.broadlink/
  6. """
  7. from datetime import timedelta
  8. from base64 import b64encode, b64decode
  9. import asyncio
  10. import binascii
  11. import logging
  12. import socket
  13. import voluptuous as vol
  14.  
  15. import homeassistant.loader as loader
  16. from homeassistant.util.dt import utcnow
  17. from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
  18. from homeassistant.const import (CONF_FRIENDLY_NAME, CONF_SWITCHES,
  19. CONF_COMMAND_OFF, CONF_COMMAND_ON,
  20. CONF_TIMEOUT, CONF_HOST, CONF_MAC,
  21. CONF_TYPE)
  22. import homeassistant.helpers.config_validation as cv
  23.  
  24. REQUIREMENTS = ['broadlink==0.3']
  25.  
  26. _LOGGER = logging.getLogger(__name__)
  27.  
  28. DOMAIN = "broadlink"
  29. DEFAULT_NAME = 'Broadlink switch'
  30. DEFAULT_TIMEOUT = 10
  31. DEFAULT_RETRY = 3
  32. SERVICE_LEARN = "learn_command"
  33. SERVICE_SEND = "send_packet"
  34.  
  35. RM_TYPES = ["rm", "rm2", "rm_mini", "rm_pro_phicomm", "rm2_home_plus",
  36. "rm2_home_plus_gdt", "rm2_pro_plus", "rm2_pro_plus2",
  37. "rm2_pro_plus_bl", "rm_mini_shate"]
  38. SP1_TYPES = ["sp1"]
  39. MP1_TYPES = ["mp1-1","mp1-2","mp1-3","mp1-4"]
  40. SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"]
  41.  
  42. SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES
  43.  
  44. SWITCH_SCHEMA = vol.Schema({
  45. vol.Optional(CONF_COMMAND_OFF, default=None): cv.string,
  46. vol.Optional(CONF_COMMAND_ON, default=None): cv.string,
  47. vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string,
  48. })
  49.  
  50. PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
  51. vol.Optional(CONF_SWITCHES, default={}):
  52. vol.Schema({cv.slug: SWITCH_SCHEMA}),
  53. vol.Required(CONF_HOST): cv.string,
  54. vol.Required(CONF_MAC): cv.string,
  55. vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string,
  56. vol.Optional(CONF_TYPE, default=SWITCH_TYPES[0]): vol.In(SWITCH_TYPES),
  57. vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int
  58. })
  59.  
  60.  
  61. def setup_platform(hass, config, add_devices, discovery_info=None):
  62. """Setup Broadlink switches."""
  63. import broadlink
  64. devices = config.get(CONF_SWITCHES, {})
  65. ip_addr = config.get(CONF_HOST)
  66. friendly_name = config.get(CONF_FRIENDLY_NAME)
  67. mac_addr = binascii.unhexlify(
  68. config.get(CONF_MAC).encode().replace(b':', b''))
  69. switch_type = config.get(CONF_TYPE)
  70.  
  71. persistent_notification = loader.get_component('persistent_notification')
  72.  
  73. @asyncio.coroutine
  74. def _learn_command(call):
  75. try:
  76. auth = yield from hass.loop.run_in_executor(None,
  77. broadlink_device.auth)
  78. except socket.timeout:
  79. _LOGGER.error("Failed to connect to device, timeout.")
  80. return
  81. if not auth:
  82. _LOGGER.error("Failed to connect to device.")
  83. return
  84.  
  85. yield from hass.loop.run_in_executor(None,
  86. broadlink_device.enter_learning)
  87.  
  88. _LOGGER.info("Press the key you want HASS to learn")
  89. start_time = utcnow()
  90. while (utcnow() - start_time) < timedelta(seconds=20):
  91. packet = yield from hass.loop.run_in_executor(None,
  92. broadlink_device.
  93. check_data)
  94. if packet:
  95. log_msg = 'Recieved packet is: {}'.\
  96. format(b64encode(packet).decode('utf8'))
  97. _LOGGER.info(log_msg)
  98. persistent_notification.async_create(hass, log_msg,
  99. title='Broadlink switch')
  100. return
  101. yield from asyncio.sleep(1, loop=hass.loop)
  102. _LOGGER.error('Did not received any signal.')
  103. persistent_notification.async_create(hass,
  104. "Did not received any signal",
  105. title='Broadlink switch')
  106.  
  107. @asyncio.coroutine
  108. def _send_packet(call):
  109. packets = call.data.get('packet', [])
  110. for packet in packets:
  111. for retry in range(DEFAULT_RETRY):
  112. try:
  113. payload = b64decode(packet)
  114. yield from hass.loop.run_in_executor(
  115. None, broadlink_device.send_data, payload)
  116. break
  117. except (socket.timeout, ValueError):
  118. try:
  119. yield from hass.loop.run_in_executor(
  120. None, broadlink_device.auth)
  121. except socket.timeout:
  122. if retry == DEFAULT_RETRY-1:
  123. _LOGGER.error("Failed to send packet to device.")
  124.  
  125. if switch_type in RM_TYPES:
  126. broadlink_device = broadlink.rm((ip_addr, 80), mac_addr)
  127. hass.services.register(DOMAIN, SERVICE_LEARN + '_' +
  128. ip_addr.replace('.', '_'), _learn_command)
  129. hass.services.register(DOMAIN, SERVICE_SEND + '_' +
  130. ip_addr.replace('.', '_'), _send_packet)
  131. switches = []
  132. for object_id, device_config in devices.items():
  133. switches.append(
  134. BroadlinkRMSwitch(
  135. device_config.get(CONF_FRIENDLY_NAME, object_id),
  136. broadlink_device,
  137. device_config.get(CONF_COMMAND_ON),
  138. device_config.get(CONF_COMMAND_OFF)
  139. )
  140. )
  141. elif switch_type in SP1_TYPES:
  142. broadlink_device = broadlink.sp1((ip_addr, 80), mac_addr)
  143. switches = [BroadlinkSP1Switch(friendly_name, broadlink_device)]
  144. elif switch_type in SP2_TYPES:
  145. broadlink_device = broadlink.sp2((ip_addr, 80), mac_addr)
  146. switches = [BroadlinkSP2Switch(friendly_name, broadlink_device)]
  147. elif switch_type in MP1_TYPES:
  148. if switch_type == "mp1-1":
  149. broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr)
  150. switches = [BroadlinkMP11Switch(friendly_name, broadlink_device)]
  151. if switch_type == "mp1-2":
  152. broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr)
  153. switches = [BroadlinkMP12Switch(friendly_name, broadlink_device)]
  154. if switch_type == "mp1-3":
  155. broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr)
  156. switches = [BroadlinkMP13Switch(friendly_name, broadlink_device)]
  157. if switch_type == "mp1-4":
  158. broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr)
  159. switches = [BroadlinkMP14Switch(friendly_name, broadlink_device)]
  160. broadlink_device.timeout = config.get(CONF_TIMEOUT)
  161. try:
  162. broadlink_device.auth()
  163. except socket.timeout:
  164. _LOGGER.error("Failed to connect to device.")
  165.  
  166. add_devices(switches)
  167.  
  168.  
  169. class BroadlinkRMSwitch(SwitchDevice):
  170. """Representation of an Broadlink switch."""
  171.  
  172. def __init__(self, friendly_name, device, command_on, command_off):
  173. """Initialize the switch."""
  174. self._name = friendly_name
  175. self._state = False
  176. self._command_on = b64decode(command_on) if command_on else None
  177. self._command_off = b64decode(command_off) if command_off else None
  178. self._device = device
  179.  
  180. @property
  181. def name(self):
  182. """Return the name of the switch."""
  183. return self._name
  184.  
  185. @property
  186. def assumed_state(self):
  187. """Return true if unable to access real state of entity."""
  188. return True
  189.  
  190. @property
  191. def should_poll(self):
  192. """No polling needed."""
  193. return False
  194.  
  195. @property
  196. def is_on(self):
  197. """Return true if device is on."""
  198. return self._state
  199.  
  200. def turn_on(self, **kwargs):
  201. """Turn the device on."""
  202. if self._sendpacket(self._command_on):
  203. self._state = True
  204. self.update_ha_state()
  205.  
  206. def turn_off(self, **kwargs):
  207. """Turn the device off."""
  208. if self._sendpacket(self._command_off):
  209. self._state = False
  210. self.update_ha_state()
  211.  
  212. def _sendpacket(self, packet, retry=2):
  213. """Send packet to device."""
  214. if packet is None:
  215. _LOGGER.debug("Empty packet.")
  216. return True
  217. try:
  218. self._device.send_data(packet)
  219. except (socket.timeout, ValueError) as error:
  220. if retry < 1:
  221. _LOGGER.error(error)
  222. return False
  223. if not self._auth():
  224. return False
  225. return self._sendpacket(packet, max(0, retry-1))
  226. return True
  227.  
  228. def _auth(self, retry=2):
  229. try:
  230. auth = self._device.auth()
  231. except socket.timeout:
  232. auth = False
  233. if not auth and retry > 0:
  234. return self._auth(max(0, retry-1))
  235. return auth
  236.  
  237.  
  238. class BroadlinkSP1Switch(BroadlinkRMSwitch):
  239. """Representation of an Broadlink switch."""
  240.  
  241. def __init__(self, friendly_name, device):
  242. """Initialize the switch."""
  243. super().__init__(friendly_name, device, None, None)
  244. self._command_on = 1
  245. self._command_off = 0
  246.  
  247. def _sendpacket(self, packet, retry=2):
  248. """Send packet to device."""
  249. try:
  250. self._device.set_power(packet)
  251. except (socket.timeout, ValueError) as error:
  252. if retry < 1:
  253. _LOGGER.error(error)
  254. return False
  255. if not self._auth():
  256. return False
  257. return self._sendpacket(packet, max(0, retry-1))
  258. return True
  259.  
  260. class BroadlinkSP2Switch(BroadlinkSP1Switch):
  261. """Representation of an Broadlink switch."""
  262.  
  263. def __init__(self, friendly_name, device):
  264. """Initialize the switch."""
  265. super().__init__(friendly_name, device)
  266.  
  267. @property
  268. def assumed_state(self):
  269. """Return true if unable to access real state of entity."""
  270. return False
  271.  
  272. @property
  273. def should_poll(self):
  274. """Polling needed."""
  275. return True
  276.  
  277. def update(self):
  278. """Synchronize state with switch."""
  279. self._update()
  280.  
  281. def _update(self, retry=2):
  282. try:
  283. state = self._device.check_power()
  284. except (socket.timeout, ValueError) as error:
  285. if retry < 1:
  286. _LOGGER.error(error)
  287. return
  288. if not self._auth():
  289. return
  290. return self._update(max(0, retry-1))
  291. if state is None and retry > 0:
  292. return self._update(max(0, retry-1))
  293. self._state = state
  294.  
  295. class BroadlinkMP11Switch(BroadlinkSP1Switch):
  296. """Representation of an Broadlink switch."""
  297.  
  298. def __init__(self, friendly_name, device):
  299. """Initialize the switch."""
  300. super().__init__(friendly_name, device)
  301. self._command_on = 1
  302. self._command_off = 0
  303.  
  304. @property
  305. def assumed_state(self):
  306. """Return true if unable to access real state of entity."""
  307. return False
  308.  
  309. def _sendpacket(self, packet, retry=2):
  310. """Send packet to device."""
  311. try:
  312. self._device.set_power(1, packet)
  313. except (socket.timeout, ValueError) as error:
  314. if retry < 1:
  315. _LOGGER.error(error)
  316. return False
  317. if not self._auth():
  318. return False
  319. return self._sendpacket(packet, max(0, retry-1))
  320. return True
  321.  
  322. @property
  323. def should_poll(self):
  324. """Polling needed."""
  325. return True
  326.  
  327. def update(self):
  328. """Synchronize state with switch."""
  329. self._update()
  330.  
  331. def _update(self, retry=2):
  332. try:
  333. state1 = self._device.check_power()
  334. state = state1['s1']
  335. except (socket.timeout, ValueError) as error:
  336. if retry < 1:
  337. _LOGGER.error(error)
  338. return
  339. if not self._auth():
  340. return
  341. return self._update(max(0, retry-1))
  342. if state is None and retry > 0:
  343. return self._update(max(0, retry-1))
  344. self._state = state
  345.  
  346. class BroadlinkMP12Switch(BroadlinkSP1Switch):
  347. """Representation of an Broadlink switch."""
  348.  
  349. def __init__(self, friendly_name, device):
  350. """Initialize the switch."""
  351. super().__init__(friendly_name, device)
  352. self._command_on = 1
  353. self._command_off = 0
  354.  
  355. @property
  356. def assumed_state(self):
  357. """Return true if unable to access real state of entity."""
  358. return False
  359.  
  360. def _sendpacket(self, packet, retry=2):
  361. """Send packet to device."""
  362. try:
  363. self._device.set_power(2, packet)
  364. except (socket.timeout, ValueError) as error:
  365. if retry < 1:
  366. _LOGGER.error(error)
  367. return False
  368. if not self._auth():
  369. return False
  370. return self._sendpacket(packet, max(0, retry-1))
  371. return True
  372.  
  373. @property
  374. def should_poll(self):
  375. """Polling needed."""
  376. return True
  377.  
  378. def update(self):
  379. """Synchronize state with switch."""
  380. self._update()
  381.  
  382. def _update(self, retry=2):
  383. try:
  384. state2 = self._device.check_power()
  385. state = state2['s2']
  386. except (socket.timeout, ValueError) as error:
  387. if retry < 1:
  388. _LOGGER.error(error)
  389. return
  390. if not self._auth():
  391. return
  392. return self._update(max(0, retry-1))
  393. if state is None and retry > 0:
  394. return self._update(max(0, retry-1))
  395. self._state = state
  396.  
  397. class BroadlinkMP13Switch(BroadlinkSP1Switch):
  398. """Representation of an Broadlink switch."""
  399.  
  400. def __init__(self, friendly_name, device):
  401. """Initialize the switch."""
  402. super().__init__(friendly_name, device)
  403. self._command_on = 1
  404. self._command_off = 0
  405.  
  406. @property
  407. def assumed_state(self):
  408. """Return true if unable to access real state of entity."""
  409. return False
  410.  
  411. def _sendpacket(self, packet, retry=2):
  412. """Send packet to device."""
  413. try:
  414. self._device.set_power(3, packet)
  415. except (socket.timeout, ValueError) as error:
  416. if retry < 1:
  417. _LOGGER.error(error)
  418. return False
  419. if not self._auth():
  420. return False
  421. return self._sendpacket(packet, max(0, retry-1))
  422. return True
  423.  
  424. @property
  425. def should_poll(self):
  426. """Polling needed."""
  427. return True
  428.  
  429. def update(self):
  430. """Synchronize state with switch."""
  431. self._update()
  432.  
  433. def _update(self, retry=2):
  434. try:
  435. state3 = self._device.check_power()
  436. state = state3['s3']
  437. except (socket.timeout, ValueError) as error:
  438. if retry < 1:
  439. _LOGGER.error(error)
  440. return
  441. if not self._auth():
  442. return
  443. return self._update(max(0, retry-1))
  444. if state is None and retry > 0:
  445. return self._update(max(0, retry-1))
  446. self._state = state
  447.  
  448. class BroadlinkMP14Switch(BroadlinkSP1Switch):
  449. """Representation of an Broadlink switch."""
  450.  
  451. def __init__(self, friendly_name, device):
  452. """Initialize the switch."""
  453. super().__init__(friendly_name, device)
  454. self._command_on = 1
  455. self._command_off = 0
  456.  
  457. @property
  458. def assumed_state(self):
  459. """Return true if unable to access real state of entity."""
  460. return False
  461.  
  462. def _sendpacket(self, packet, retry=2):
  463. """Send packet to device."""
  464. try:
  465. self._device.set_power(4, packet)
  466. except (socket.timeout, ValueError) as error:
  467. if retry < 1:
  468. _LOGGER.error(error)
  469. return False
  470. if not self._auth():
  471. return False
  472. return self._sendpacket(packet, max(0, retry-1))
  473. return True
  474.  
  475. @property
  476. def should_poll(self):
  477. """Polling needed."""
  478. return True
  479.  
  480. def update(self):
  481. """Synchronize state with switch."""
  482. self._update()
  483.  
  484. def _update(self, retry=2):
  485. try:
  486. state4 = self._device.check_power()
  487. state = state4['s4']
  488. except (socket.timeout, ValueError) as error:
  489. if retry < 1:
  490. _LOGGER.error(error)
  491. return
  492. if not self._auth():
  493. return
  494. return self._update(max(0, retry-1))
  495. if state is None and retry > 0:
  496. return self._update(max(0, retry-1))
  497. self._state = state
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement