Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """Component to embed Broadlink devices."""
- import logging
- from base64 import b64decode, b64encode
- import socket
- import voluptuous as vol
- from homeassistant.const import (CONF_NAME, CONF_DOMAIN, CONF_HOST, CONF_MAC,
- CONF_TYPE, CONF_DISCOVERY, CONF_COMMAND_ON,
- CONF_COMMAND_OFF, CONF_COMMAND, CONF_TIMEOUT,
- ATTR_PACKET, ATTR_CONFIG, ATTR_ENTITY_ID,
- ENTITY_ID_FORMAT, STATE_UNAVAILABLE)
- from homeassistant import config_entries
- from homeassistant.util import slugify
- from homeassistant.helpers import config_entry_flow
- from homeassistant.helpers.entity import ToggleEntity
- import homeassistant.helpers.config_validation as cv
- import homeassistant.helpers.device_registry as dr
- REQUIREMENTS = ['broadlink==0.9.0']
- _LOGGER = logging.getLogger(__name__)
- DOMAIN = 'broadlink'
- CONF_SP = 'sp'
- CONF_MP = 'mp'
- CONF_RM = 'rm'
- CONF_SLOTS = 'slots'
- DEFAULT_TIMEOUT = 3 # In seconds
- DEFAULT_DOMAIN = 'switch'
- DEFAULT_SLOT_ENTITIES = [{CONF_NAME: name, CONF_DOMAIN: DEFAULT_DOMAIN}
- for name in map('slot_{}'.format, range(4))]
- MODELS = {
- 0x0000: "SmartPlug 1",
- 0x2711: "SmartPlug 2",
- 0x2719: "Honeywell SmartPlug 2",
- 0x7919: "Honeywell SmartPlug 2",
- 0x271a: "Honeywell SmartPlug 2",
- 0x791a: "Honeywell SmartPlug 2",
- 0x2720: "SmartPlug Mini",
- 0x753e: "SmartPlug 3",
- 0x7D00: "OEM branded SmartPlug 3",
- 0x947a: "SmartPlug 3S",
- 0x9479: "SmartPlug 3S",
- 0x2728: "SmartPlug Mini 2",
- 0x2733: "OEM branded SmartPlug Mini",
- 0x273e: "OEM branded SmartPlug Mini",
- 0x7530: "OEM branded SmartPlug Mini 2",
- 0x7918: "OEM branded SmartPlug Mini 2",
- 0x2736: "SmartPlug Mini Plus",
- 0x2712: "Remote 2",
- 0x2737: "Remote Mini",
- 0x273d: "Remote Pro Phicomm",
- 0x2783: "Remote 2 Home Plus",
- 0x277c: "Remote 2 Home Plus GDT",
- 0x272a: "Remote 2 Pro Plus",
- 0x2787: "Remote 2 Pro Plus 2",
- 0x279d: "Remote 2 Pro Plus 3",
- 0x27a9: "Remote 2 Pro Plus_300",
- 0x278b: "Remote 2 Pro Plus BL",
- 0x2797: "Remote 2 Pro Plus HYC",
- 0x27a1: "Remote 2 Pro Plus R1",
- 0x27a6: "Remote 2 Pro PP",
- 0x278f: "Remote Mini Shate",
- 0x27c2: "Remote Mini 3",
- 0x2714: "A1",
- 0x4EB5: "MP1",
- 0x4EF7: "Honyar oem mp1",
- 0x4EAD: "Hysen controller",
- 0x2722: "SmartOne Alarm Kit",
- 0x4E4D: "Dooya DT360E (DOOYA_CURTAIN_V2)",
- }
- RM_TYPES = ['rm', 'rm2', 'rm_mini', 'rm_pro_phicomm', 'rm2_home_plus',
- 'rm2_home_plus_gdt', 'rm2_pro_plus', 'rm2_pro_plus2',
- 'rm2_pro_plus_bl', 'rm_mini_shate']
- SP1_TYPES = ['sp1']
- SP2_TYPES = ['sp2', 'honeywell_sp2', 'sp3', 'spmini2', 'spminiplus']
- MP_TYPES = ['mp1']
- SP_TYPES = SP1_TYPES + SP2_TYPES
- BROADLINK_ENTITY = {
- vol.Required(CONF_NAME): cv.string,
- vol.Optional(CONF_DOMAIN, default=DEFAULT_DOMAIN): cv.string
- }
- BROADLINK_DEVICE = {
- vol.Required(CONF_HOST): cv.string,
- vol.Required(CONF_MAC): cv.string,
- vol.Required(CONF_TYPE): cv.string
- }
- BROADLINK_COMMANDS = {
- vol.Optional(CONF_COMMAND_ON, default=None): cv.string,
- vol.Optional(CONF_COMMAND_OFF, default=None): cv.string
- }
- BROADLINK_SP_SCHEMA = vol.Schema({
- **BROADLINK_DEVICE,
- **BROADLINK_ENTITY
- })
- BROADLINK_MP_SCHEMA = vol.Schema({
- **BROADLINK_DEVICE,
- vol.Optional(CONF_SLOTS, default=DEFAULT_SLOT_ENTITIES):
- vol.All(cv.ensure_list, [vol.Schema(BROADLINK_ENTITY)])
- })
- BROADLINK_RM_SCHEMA = vol.Schema({
- **BROADLINK_DEVICE,
- vol.Optional(CONF_COMMAND, default=[]):
- vol.All(cv.ensure_list, [vol.Schema({
- **BROADLINK_ENTITY, **BROADLINK_COMMANDS
- })])
- })
- CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Optional(CONF_SP, default=[]): vol.All(cv.ensure_list,
- [BROADLINK_SP_SCHEMA]),
- vol.Optional(CONF_MP, default=[]): vol.All(cv.ensure_list,
- [BROADLINK_MP_SCHEMA]),
- vol.Optional(CONF_RM, default=[]): vol.All(cv.ensure_list,
- [BROADLINK_RM_SCHEMA]),
- vol.Optional('discovery', default=True): cv.boolean,
- vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int
- }),
- })
- STATE_LEARNING = 'learning'
- SERVICE_LEARN = 'learn'
- SERVICE_SEND = 'send'
- RM_LEARN_SERVICE_SCHEMA = vol.Schema({
- vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
- })
- RM_SEND_SERVICE_SCHEMA = vol.Schema({
- vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
- vol.Required(ATTR_PACKET): cv.string,
- })
- async def _async_has_devices(hass):
- """Return if there are devices that can be discovered."""
- import broadlink
- def discover():
- devs = broadlink.discover(timeout=1)
- return devs
- return await hass.async_add_executor_job(discover)
- async def async_setup(hass, config):
- """Set up the Broadlink component."""
- conf = config.get(DOMAIN)
- hass.data[DOMAIN] = {}
- hass.data[DOMAIN][ATTR_CONFIG] = conf
- if conf is not None:
- hass.async_create_task(hass.config_entries.flow.async_init(
- DOMAIN, context={'source': config_entries.SOURCE_IMPORT}))
- component.async_register_entity_service(
- SERVICE_LEARN, RM_LEARN_SERVICE_SCHEMA,
- 'learn'
- )
- component.async_register_entity_service(
- SERVICE_SEND, RM_SEND_SERVICE_SCHEMA,
- 'send'
- )
- return True
- async def async_setup_entry(hass, config_entry):
- """Set up Broadlink from a config entry."""
- devices = []
- config_data = hass.data[DOMAIN].get(ATTR_CONFIG)
- # If discovery is defined and not disabled, discover devices
- # If initialized from configure integrations, there's no config
- # so we default here to True
- if config_data is None or config_data[CONF_DISCOVERY]:
- devices = await _async_has_devices(hass)
- _LOGGER.info("Discovered {} Broadlink device(s)", len(devices))
- for device in devices:
- conf_device = {
- CONF_HOST: device.host,
- CONF_MAC: device.mac,
- CONF_TYPE: device.type.lower()
- }
- if device.type.lower() in SP_TYPES:
- config_data[CONF_SP] = conf_device
- if device.type.lower() in MP_TYPES:
- config_data[CONF_MP] = conf_device
- if device.type.lower() in RM_TYPES:
- config_data[CONF_RM] = conf_device
- _LOGGER.debug("Succesfully added {}@{}",
- device.type, device.host)
- forward_setup = hass.config_entries.async_forward_entry_setup
- hass.async_create_task(forward_setup(config_entry, 'light'))
- hass.async_create_task(forward_setup(config_entry, 'switch'))
- return True
- async def async_unload_entry(hass, entry):
- """Unload a config entry."""
- forward_unload = hass.config_entries.async_forward_entry_unload
- remove_lights = remove_switches = False
- remove_lights = await forward_unload(entry, 'light')
- remove_switches = await forward_unload(entry, 'switch')
- if remove_lights or remove_switches:
- hass.data[DOMAIN].clear()
- return True
- # We were not able to unload the platforms, either because there
- # were none or one of the forward_unloads failed.
- return False
- async def _learn(hass, call):
- """Handle a learn command."""
- entity_id = call.data.get(ATTR_ENTITY_ID, [])
- # FIXME: get device ?
- device = None
- packet = device.learn()
- if packet:
- log_msg = "Received packet from {} is: {}".\
- format(entity_id, packet)
- _LOGGER.info(log_msg)
- hass.components.persistent_notification.async_create(
- log_msg, title='Broadlink')
- else:
- hass.components.persistent_notification.async_create(
- "Did not received any signal", title='Broadlink')
- async def _send(hass, call):
- """Send a packet."""
- entity_id = call.data.get(ATTR_ENTITY_ID, [])
- # FIXME: get device ?
- device = None
- packets = call.data.get(ATTR_PACKET, [])
- for packet in packets:
- device.send(packet)
- class BroadlinkDevice(ToggleEntity):
- """Representation of an generic Broadlink device."""
- def __init__(self, name, device, command_on=1, command_off=0, slot=None):
- """Initialize the device."""
- self.entity_id = ENTITY_ID_FORMAT.format(slugify(name))
- self._name = name
- self._state = False
- if isinstance(command_on, str):
- self._command_on = b64decode(command_on)
- self._command_off = (b64decode(command_off) if command_off
- else self._command_on)
- elif isinstance(command_off, str):
- self._command_off = 0
- self._device = device
- self._slot = slot
- if slot and self._device.type in MP_TYPES:
- slot = 1
- @property
- def name(self):
- """Return the name of the device."""
- return self._name
- @property
- def unique_id(self):
- """Return a unique ID."""
- return self._device.mac
- @property
- def device_info(self):
- """Return information about the device."""
- return {
- "name": self.name,
- "model": MODELS[self._device.devtype],
- "manufacturer": 'Broadlink',
- "connections": {
- (dr.CONNECTION_NETWORK_MAC, self._device.mac),
- (dr.CONNECTION_NETWORK_IP, self._device.host[0]),
- },
- }
- @property
- def state(self):
- """Defaulting to power"""
- return self._state
- def learn(self):
- """Start learning if device type is RM"""
- if self._device.type in RM_TYPES:
- self._auth()
- self._state = STATE_LEARNING
- self._device.enter_learning()
- _LOGGER.info("Press the key you want Home Assistant to learn")
- self.update()
- packet = self._state
- if packet:
- return b64encode(packet).decode('utf8')
- _LOGGER.error("Did not received any signal")
- self.schedule_update_ha_state()
- return None
- def turn_on(self, **kwargs):
- """Turn the device on."""
- if self._sendpacket(self._command_on):
- self._state = True
- self.schedule_update_ha_state()
- def turn_off(self, **kwargs):
- """Turn the device off."""
- if self._sendpacket(self._command_off):
- self._state = False
- self.schedule_update_ha_state()
- def send(self, packet, retry=5):
- """Send packet to device."""
- if packet is None:
- _LOGGER.warn("Empty packet")
- return True
- try:
- self._send(packet)
- except (socket.timeout, ValueError) as error:
- if retry < 1:
- _LOGGER.error("Error during sending a packet: {}", error)
- return False
- if not self._auth():
- return False
- return self._sendpacket(packet, retry-1)
- return True
- def _send(self, packet):
- """Defaulting to power"""
- self._auth()
- if self._device.type in MP_TYPES:
- self._state = self._device.set_power(self._slot, packet)
- elif self._device.type in RM_TYPES:
- self._state = self._device.send_data(packet)
- else:
- self._state = self._device.set_power(packet)
- def auth(self, retry=5):
- try:
- auth = self._device.auth()
- except socket.timeout:
- auth = False
- if retry < 1:
- _LOGGER.error("Timeout during authorization")
- if not auth and retry > 0:
- return self._auth(retry-1)
- return auth
- def update(self):
- """Defaulting to power"""
- self._auth()
- state = STATE_UNAVAILABLE
- if self._device.type in MP_TYPES:
- state = self._device.check_power(self._slot)
- elif self._device.type in RM_TYPES:
- state = self._device.check_data()
- else:
- state = self._device.check_power()
- self._state = state
- config_entry_flow.register_discovery_flow(DOMAIN,
- 'Broadlink',
- _async_has_devices,
- config_entries.CONN_CLASS_LOCAL_POLL)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement