forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
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 (home-assistant#36914
) * Implement config flow in the Broadlink integration * General improvements to the Broadlink config flow * Remove unnecessary else after return * Fix translations * Rename device to device_entry * Add tests for the config flow * Improve docstrings * Test we do not accept more than one config entry per device * Improve helpers * Allow empty packets * Allow multiple config files for switches related to the same device * Rename mock_device to mock_api * General improvements * Make new attempts before marking the device as unavailable * Let the name be the template for the entity_id * Handle OSError * Test network unavailable in the configuration flow * Rename lock attribute * Update manifest.json * Import devices from platforms * Test import flow * Add deprecation warnings * General improvements * Rename deprecate to discontinue * Test device setup * Add type attribute to mock api * Test we handle an update failure at startup * Remove BroadlinkDevice from tests * Remove device.py from .coveragerc * Add tests for the config flow * Add tests for the device * Test device registry and update listener * Test MAC address validation * Add tests for the device * Extract domains and types to a helper function * Do not patch integration details * Add tests for the device * Set device classes where appropriate * Set an appropriate connection class * Do not set device class for custom switches * Fix tests and improve code readability * Use RM4 to test authentication errors * Handle BroadlinkException in the authentication
- Loading branch information
1 parent
be7d999
commit 048ac87
Showing
21 changed files
with
2,468 additions
and
766 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,34 @@ | ||
"""The broadlink component.""" | ||
import asyncio | ||
from base64 import b64decode, b64encode | ||
from binascii import unhexlify | ||
"""The Broadlink integration.""" | ||
from dataclasses import dataclass, field | ||
import logging | ||
import re | ||
|
||
from broadlink.exceptions import BroadlinkException, ReadError, StorageError | ||
import voluptuous as vol | ||
from .const import DOMAIN | ||
from .device import BroadlinkDevice | ||
|
||
from homeassistant.const import CONF_HOST | ||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.util.dt import utcnow | ||
LOGGER = logging.getLogger(__name__) | ||
|
||
from .const import CONF_PACKET, DOMAIN, LEARNING_TIMEOUT, SERVICE_LEARN, SERVICE_SEND | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
@dataclass | ||
class BroadlinkData: | ||
"""Class for sharing data within the Broadlink integration.""" | ||
|
||
DEFAULT_RETRY = 3 | ||
devices: dict = field(default_factory=dict) | ||
platforms: dict = field(default_factory=dict) | ||
|
||
|
||
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) | ||
async def async_setup(hass, config): | ||
"""Set up the Broadlink integration.""" | ||
hass.data[DOMAIN] = BroadlinkData() | ||
return True | ||
|
||
|
||
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 | ||
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() | ||
|
||
|
||
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) | ||
|
||
|
||
SERVICE_SEND_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_HOST): cv.string, | ||
vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]), | ||
} | ||
) | ||
|
||
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" | ||
) | ||
|
||
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.