2019-03-17 03:44:05 +00:00
|
|
|
"""The broadlink component."""
|
2019-04-12 18:11:36 +00:00
|
|
|
import asyncio
|
|
|
|
from base64 import b64decode, b64encode
|
2019-12-02 21:20:36 +00:00
|
|
|
from binascii import unhexlify
|
2019-12-09 10:48:52 +00:00
|
|
|
from datetime import timedelta
|
2019-04-12 18:11:36 +00:00
|
|
|
import logging
|
2019-12-02 21:20:36 +00:00
|
|
|
import re
|
2019-04-12 18:11:36 +00:00
|
|
|
|
2020-05-13 08:36:32 +00:00
|
|
|
from broadlink.exceptions import BroadlinkException, ReadError
|
2019-04-12 18:11:36 +00:00
|
|
|
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, SERVICE_LEARN, SERVICE_SEND
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
DEFAULT_RETRY = 3
|
|
|
|
|
|
|
|
|
|
|
|
def data_packet(value):
|
|
|
|
"""Decode a data packet given for broadlink."""
|
2019-04-26 02:33:05 +00:00
|
|
|
value = cv.string(value)
|
|
|
|
extra = len(value) % 4
|
|
|
|
if extra > 0:
|
2019-07-31 19:25:30 +00:00
|
|
|
value = value + ("=" * (4 - extra))
|
2019-04-26 02:33:05 +00:00
|
|
|
return b64decode(value)
|
2019-04-12 18:11:36 +00:00
|
|
|
|
|
|
|
|
2019-12-02 21:20:36 +00:00
|
|
|
def hostname(value):
|
|
|
|
"""Validate a hostname."""
|
|
|
|
host = str(value).lower()
|
|
|
|
if len(host) > 253:
|
|
|
|
raise ValueError
|
|
|
|
if host[-1] == ".":
|
|
|
|
host = host[:-1]
|
|
|
|
allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$")
|
|
|
|
if not all(allowed.match(elem) for elem in host.split(".")):
|
|
|
|
raise ValueError
|
|
|
|
return host
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_SEND_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet]),
|
|
|
|
}
|
|
|
|
)
|
2019-04-12 18:11:36 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_LEARN_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
|
2019-04-12 18:11:36 +00:00
|
|
|
|
|
|
|
|
2020-05-13 08:36:32 +00:00
|
|
|
async def async_setup_service(hass, host, device):
|
2019-04-12 18:11:36 +00:00
|
|
|
"""Register a device for given host for use in services."""
|
|
|
|
hass.data.setdefault(DOMAIN, {})[host] = device
|
|
|
|
|
2020-01-29 21:59:45 +00:00
|
|
|
if hass.services.has_service(DOMAIN, SERVICE_LEARN):
|
|
|
|
return
|
|
|
|
|
2020-05-13 08:36:32 +00:00
|
|
|
async def async_learn_command(call):
|
2020-01-29 21:59:45 +00:00
|
|
|
"""Learn a packet from remote."""
|
|
|
|
|
2020-02-09 00:17:41 +00:00
|
|
|
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
2020-01-29 21:59:45 +00:00
|
|
|
|
2020-05-13 08:36:32 +00:00
|
|
|
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
|
2020-01-29 21:59:45 +00:00
|
|
|
|
|
|
|
_LOGGER.info("Press the key you want Home Assistant to learn")
|
|
|
|
start_time = utcnow()
|
|
|
|
while (utcnow() - start_time) < timedelta(seconds=20):
|
2020-05-13 08:36:32 +00:00
|
|
|
try:
|
|
|
|
packet = await device.async_request(device.api.check_data)
|
|
|
|
except ReadError:
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
except BroadlinkException as err_msg:
|
|
|
|
_LOGGER.error("Failed to learn: %s", err_msg)
|
|
|
|
return
|
|
|
|
else:
|
2020-01-29 21:59:45 +00:00
|
|
|
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"
|
|
|
|
)
|
2019-04-12 18:11:36 +00:00
|
|
|
return
|
2020-05-13 08:36:32 +00:00
|
|
|
_LOGGER.error("Failed to learn: No signal received")
|
2020-01-29 21:59:45 +00:00
|
|
|
hass.components.persistent_notification.async_create(
|
|
|
|
"No signal was received", title="Broadlink switch"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-04-12 18:11:36 +00:00
|
|
|
|
2020-01-29 21:59:45 +00:00
|
|
|
hass.services.async_register(
|
2020-05-13 08:36:32 +00:00
|
|
|
DOMAIN, SERVICE_LEARN, async_learn_command, schema=SERVICE_LEARN_SCHEMA
|
2020-01-29 21:59:45 +00:00
|
|
|
)
|
|
|
|
|
2020-05-13 08:36:32 +00:00
|
|
|
async def async_send_packet(call):
|
2020-01-29 21:59:45 +00:00
|
|
|
"""Send a packet."""
|
|
|
|
device = hass.data[DOMAIN][call.data[CONF_HOST]]
|
|
|
|
packets = call.data[CONF_PACKET]
|
|
|
|
for packet in packets:
|
2020-05-13 08:36:32 +00:00
|
|
|
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
|
2020-01-29 21:59:45 +00:00
|
|
|
|
|
|
|
hass.services.async_register(
|
2020-05-13 08:36:32 +00:00
|
|
|
DOMAIN, SERVICE_SEND, async_send_packet, schema=SERVICE_SEND_SCHEMA
|
2020-01-29 21:59:45 +00:00
|
|
|
)
|