-
-
Notifications
You must be signed in to change notification settings - Fork 29.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement config flow in the Broadlink integration
- Loading branch information
1 parent
2883aac
commit 41fe1e6
Showing
18 changed files
with
1,052 additions
and
773 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.