Skip to content

Commit

Permalink
Implement config flow in the Broadlink integration
Browse files Browse the repository at this point in the history
  • Loading branch information
felipediel committed Jun 22, 2020
1 parent 2883aac commit 41fe1e6
Show file tree
Hide file tree
Showing 18 changed files with 1,052 additions and 773 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ omit =
homeassistant/components/braviatv/__init__.py
homeassistant/components/braviatv/const.py
homeassistant/components/braviatv/media_player.py
homeassistant/components/broadlink/__init__.py
homeassistant/components/broadlink/const.py
homeassistant/components/broadlink/device.py
homeassistant/components/broadlink/remote.py
homeassistant/components/broadlink/sensor.py
homeassistant/components/broadlink/switch.py
homeassistant/components/broadlink/updater.py
homeassistant/components/brottsplatskartan/sensor.py
homeassistant/components/browser/*
homeassistant/components/brunt/cover.py
Expand Down
163 changes: 55 additions & 108 deletions homeassistant/components/broadlink/__init__.py
Original file line number Diff line number Diff line change
@@ -1,125 +1,72 @@
"""The broadlink component."""
import asyncio
from base64 import b64decode, b64encode
from binascii import unhexlify
"""The Broadlink integration."""
from collections import namedtuple
import logging
import re

from broadlink.exceptions import BroadlinkException, ReadError, StorageError
import voluptuous as vol

from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.util.dt import utcnow

from .const import CONF_PACKET, DOMAIN, LEARNING_TIMEOUT, SERVICE_LEARN, SERVICE_SEND

_LOGGER = logging.getLogger(__name__)
from homeassistant import config_entries
from homeassistant.const import (
CONF_DEVICES,
CONF_HOST,
CONF_MAC,
CONF_TIMEOUT,
CONF_TYPE,
)
from homeassistant.helpers import config_validation as cv

DEFAULT_RETRY = 3
from .const import DEFAULT_TIMEOUT, DOMAIN
from .device import BroadlinkDevice
from .helpers import mac_address

LOGGER = logging.getLogger(__name__)

def data_packet(value):
"""Decode a data packet given for broadlink."""
value = cv.string(value)
extra = len(value) % 4
if extra > 0:
value = value + ("=" * (4 - extra))
return b64decode(value)
DEVICE_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Inclusive(CONF_MAC, "manual_config"): mac_address,
vol.Inclusive(CONF_TYPE, "manual_config"): cv.positive_int,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)

DOMAIN_SCHEMA = vol.Schema(
{vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_SCHEMA])}
)

def hostname(value):
"""Validate a hostname."""
host = str(value)
if len(host) > 253:
raise ValueError
if host[-1] == ".":
host = host[:-1]
allowed = re.compile(r"(?![_-])[a-z\d_-]{1,63}(?<![_-])$", flags=re.IGNORECASE)
if not all(allowed.match(elem) for elem in host.split(".")):
raise ValueError
return host
CONFIG_SCHEMA = vol.Schema({DOMAIN: DOMAIN_SCHEMA}, extra=vol.ALLOW_EXTRA)


def mac_address(value):
"""Validate and coerce a 48-bit MAC address."""
mac = str(value).lower()
if len(mac) == 17:
mac = mac[0:2] + mac[3:5] + mac[6:8] + mac[9:11] + mac[12:14] + mac[15:17]
elif len(mac) == 14:
mac = mac[0:2] + mac[2:4] + mac[5:7] + mac[7:9] + mac[10:12] + mac[12:14]
elif len(mac) != 12:
raise ValueError
return unhexlify(mac)
async def async_setup(hass, config):
"""Set up the Broadlink integration."""
config = config.get(DOMAIN, {})
devices = config.get(CONF_DEVICES, {})

SharedData = namedtuple("SharedData", ["devices", "platforms"])
hass.data[DOMAIN] = SharedData({}, {})

SERVICE_SEND_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]),
configured_hosts = {
entry.data.get(CONF_HOST) for entry in hass.config_entries.async_entries(DOMAIN)
}
)

SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})


async def async_setup_service(hass, host, device):
"""Register a device for given host for use in services."""
hass.data.setdefault(DOMAIN, {})[host] = device

if hass.services.has_service(DOMAIN, SERVICE_LEARN):
return

async def async_learn_command(call):
"""Learn a packet from remote."""

device = hass.data[DOMAIN][call.data[CONF_HOST]]

try:
await device.async_request(device.api.enter_learning)
except BroadlinkException as err_msg:
_LOGGER.error("Failed to enter learning mode: %s", err_msg)
return

_LOGGER.info("Press the key you want Home Assistant to learn")
start_time = utcnow()
while (utcnow() - start_time) < LEARNING_TIMEOUT:
await asyncio.sleep(1)
try:
packet = await device.async_request(device.api.check_data)
except (ReadError, StorageError):
continue
except BroadlinkException as err_msg:
_LOGGER.error("Failed to learn: %s", err_msg)
return
else:
data = b64encode(packet).decode("utf8")
log_msg = f"Received packet is: {data}"
_LOGGER.info(log_msg)
hass.components.persistent_notification.async_create(
log_msg, title="Broadlink switch"
)
return
_LOGGER.error("Failed to learn: No signal received")
hass.components.persistent_notification.async_create(
"No signal was received", title="Broadlink switch"
for device in devices:
if device[CONF_HOST] in configured_hosts:
continue

import_device = hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=device,
)
hass.async_create_task(import_device)

return True


async def async_setup_entry(hass, entry):
"""Set up a Broadlink device from a config entry."""
device = BroadlinkDevice(hass, entry)
return await device.async_setup()


hass.services.async_register(
DOMAIN, SERVICE_LEARN, async_learn_command, schema=SERVICE_LEARN_SCHEMA
)

async def async_send_packet(call):
"""Send a packet."""
device = hass.data[DOMAIN][call.data[CONF_HOST]]
packets = call.data[CONF_PACKET]
for packet in packets:
try:
await device.async_request(device.api.send_data, packet)
except BroadlinkException as err_msg:
_LOGGER.error("Failed to send packet: %s", err_msg)
return

hass.services.async_register(
DOMAIN, SERVICE_SEND, async_send_packet, schema=SERVICE_SEND_SCHEMA
)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
device = hass.data[DOMAIN].devices.pop(entry.entry_id)
return await device.async_unload()
Loading

0 comments on commit 41fe1e6

Please sign in to comment.