Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f1b6202
Implement config flow in the Broadlink integration
felipediel Jun 22, 2020
e2901e8
General improvements to the Broadlink config flow
felipediel Jun 24, 2020
9a1464c
Remove unnecessary else after return
felipediel Jun 24, 2020
96c8015
Fix translations
felipediel Jun 24, 2020
11bfdec
Rename device to device_entry
felipediel Jun 24, 2020
b8ce6ff
Add tests for the config flow
felipediel Jun 29, 2020
d566786
Improve docstrings
felipediel Jun 30, 2020
323fb10
Test we do not accept more than one config entry per device
felipediel Jun 30, 2020
bb850ad
Improve helpers
felipediel Jul 1, 2020
0cde740
Allow empty packets
felipediel Jul 1, 2020
8151803
Allow multiple config files for switches related to the same device
felipediel Jul 2, 2020
87f3b62
Rename mock_device to mock_api
felipediel Jul 2, 2020
1aef65a
General improvements
felipediel Jul 3, 2020
4efec87
Make new attempts before marking the device as unavailable
felipediel Jul 6, 2020
ce46729
Let the name be the template for the entity_id
felipediel Jul 6, 2020
ac2ccdc
Handle OSError
felipediel Jul 8, 2020
0cb66a9
Test network unavailable in the configuration flow
felipediel Jul 8, 2020
12db167
Rename lock attribute
felipediel Jul 31, 2020
aa4f1df
Update manifest.json
felipediel Jul 31, 2020
ea2b774
Import devices from platforms
felipediel Aug 1, 2020
36144db
Test import flow
felipediel Aug 2, 2020
d15108b
Add deprecation warnings
felipediel Aug 4, 2020
ccaec4f
General improvements
felipediel Aug 8, 2020
42e7afc
Rename deprecate to discontinue
felipediel Aug 8, 2020
3839cae
Test device setup
felipediel Aug 8, 2020
4ff7793
Add type attribute to mock api
felipediel Aug 8, 2020
feccee3
Test we handle an update failure at startup
felipediel Aug 8, 2020
a6a148d
Remove BroadlinkDevice from tests
felipediel Aug 10, 2020
7aa943d
Remove device.py from .coveragerc
felipediel Aug 10, 2020
adb4c9f
Add tests for the config flow
felipediel Aug 10, 2020
76f756f
Add tests for the device
felipediel Aug 11, 2020
82ad876
Test device registry and update listener
felipediel Aug 11, 2020
dfbb1bf
Test MAC address validation
felipediel Aug 15, 2020
9a5588d
Add tests for the device
felipediel Aug 16, 2020
a64bd8c
Extract domains and types to a helper function
felipediel Aug 16, 2020
005be1f
Do not patch integration details
felipediel Aug 16, 2020
b9dc11f
Add tests for the device
felipediel Aug 16, 2020
96a6ac5
Set device classes where appropriate
felipediel Aug 17, 2020
2b261cc
Set an appropriate connection class
felipediel Aug 18, 2020
21caee7
Do not set device class for custom switches
felipediel Aug 18, 2020
e91d4eb
Fix tests and improve code readability
felipediel Aug 19, 2020
cf5e598
Use RM4 to test authentication errors
felipediel Aug 20, 2020
84ef4ff
Handle BroadlinkException in the authentication
felipediel Aug 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ 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
135 changes: 22 additions & 113 deletions homeassistant/components/broadlink/__init__.py
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()
Loading