Bump broadlink from 0.16.0 to 0.17.0 (#47779)

This commit is contained in:
Felipe Martins Diel 2021-03-12 02:34:56 -03:00 committed by GitHub
parent 92852b9c10
commit 9ca0cd5464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 167 deletions

View File

@ -39,11 +39,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_set_device(self, device, raise_on_progress=True):
"""Define a device for the config flow."""
supported_types = {
device_type
for device_types in DOMAINS_AND_TYPES
for device_type in device_types[1]
}
supported_types = set.union(*DOMAINS_AND_TYPES.values())
if device.type not in supported_types:
_LOGGER.error(
"Unsupported device: %s. If it worked before, please open "

View File

@ -5,11 +5,26 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
DOMAIN = "broadlink"
DOMAINS_AND_TYPES = (
(REMOTE_DOMAIN, ("RM2", "RM4")),
(SENSOR_DOMAIN, ("A1", "RM2", "RM4")),
(SWITCH_DOMAIN, ("BG1", "MP1", "RM2", "RM4", "SP1", "SP2", "SP4", "SP4B")),
)
DOMAINS_AND_TYPES = {
REMOTE_DOMAIN: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
SENSOR_DOMAIN: {"A1", "RM4MINI", "RM4PRO", "RMPRO"},
SWITCH_DOMAIN: {
"BG1",
"MP1",
"RM4MINI",
"RM4PRO",
"RMMINI",
"RMMINIB",
"RMPRO",
"SP1",
"SP2",
"SP2S",
"SP3",
"SP3S",
"SP4",
"SP4B",
},
}
DEFAULT_PORT = 80
DEFAULT_TIMEOUT = 5

View File

@ -22,9 +22,9 @@ from .updater import get_update_manager
_LOGGER = logging.getLogger(__name__)
def get_domains(device_type):
def get_domains(dev_type):
"""Return the domains available for a device type."""
return {domain for domain, types in DOMAINS_AND_TYPES if device_type in types}
return {d for d, t in DOMAINS_AND_TYPES.items() if dev_type in t}
class BroadlinkDevice:

View File

@ -2,7 +2,7 @@
"domain": "broadlink",
"name": "Broadlink",
"documentation": "https://www.home-assistant.io/integrations/broadlink",
"requirements": ["broadlink==0.16.0"],
"requirements": ["broadlink==0.17.0"],
"codeowners": ["@danielhiversen", "@felipediel"],
"config_flow": true
}

View File

@ -26,6 +26,8 @@ from homeassistant.components.remote import (
DOMAIN as RM_DOMAIN,
PLATFORM_SCHEMA,
SERVICE_DELETE_COMMAND,
SERVICE_LEARN_COMMAND,
SERVICE_SEND_COMMAND,
SUPPORT_DELETE_COMMAND,
SUPPORT_LEARN_COMMAND,
RemoteEntity,
@ -129,6 +131,7 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
self._codes = {}
self._flags = defaultdict(int)
self._state = True
self._lock = asyncio.Lock()
@property
def name(self):
@ -171,39 +174,44 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
"sw_version": self._device.fw_version,
}
def get_code(self, command, device):
"""Return a code and a boolean indicating a toggle command.
def _extract_codes(self, commands, device=None):
"""Extract a list of codes.
If the command starts with `b64:`, extract the code from it.
Otherwise, extract the code from the dictionary, using the device
and command as keys.
Otherwise, extract the code from storage, using the command and
device as keys.
You need to change the flag whenever a toggle command is sent
successfully. Use `self._flags[device] ^= 1`.
The codes are returned in sublists. For toggle commands, the
sublist contains two codes that must be sent alternately with
each call.
"""
if command.startswith("b64:"):
code, is_toggle_cmd = command[4:], False
code_list = []
for cmd in commands:
if cmd.startswith("b64:"):
codes = [cmd[4:]]
else:
if device is None:
raise KeyError("You need to specify a device")
try:
code = self._codes[device][command]
except KeyError as err:
raise KeyError("Command not found") from err
# For toggle commands, alternate between codes in a list.
if isinstance(code, list):
code = code[self._flags[device]]
is_toggle_cmd = True
else:
is_toggle_cmd = False
if device is None:
raise ValueError("You need to specify a device")
try:
return data_packet(code), is_toggle_cmd
except ValueError as err:
raise ValueError("Invalid code") from err
try:
codes = self._codes[device][cmd]
except KeyError as err:
raise ValueError(f"Command not found: {repr(cmd)}") from err
if isinstance(codes, list):
codes = codes[:]
else:
codes = [codes]
for idx, code in enumerate(codes):
try:
codes[idx] = data_packet(code)
except ValueError as err:
raise ValueError(f"Invalid code: {repr(code)}") from err
code_list.append(codes)
return code_list
@callback
def get_codes(self):
@ -261,44 +269,50 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
device = kwargs.get(ATTR_DEVICE)
repeat = kwargs[ATTR_NUM_REPEATS]
delay = kwargs[ATTR_DELAY_SECS]
service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}"
if not self._state:
_LOGGER.warning(
"remote.send_command canceled: %s entity is turned off", self.entity_id
"%s canceled: %s entity is turned off", service, self.entity_id
)
return
should_delay = False
try:
code_list = self._extract_codes(commands, device)
except ValueError as err:
_LOGGER.error("Failed to call %s: %s", service, err)
raise
for _, cmd in product(range(repeat), commands):
if should_delay:
rf_flags = {0xB2, 0xD7}
if not hasattr(self._device.api, "sweep_frequency") and any(
c[0] in rf_flags for codes in code_list for c in codes
):
err_msg = f"{self.entity_id} doesn't support sending RF commands"
_LOGGER.error("Failed to call %s: %s", service, err_msg)
raise ValueError(err_msg)
at_least_one_sent = False
for _, codes in product(range(repeat), code_list):
if at_least_one_sent:
await asyncio.sleep(delay)
try:
code, is_toggle_cmd = self.get_code(cmd, device)
except (KeyError, ValueError) as err:
_LOGGER.error("Failed to send '%s': %s", cmd, err)
should_delay = False
continue
if len(codes) > 1:
code = codes[self._flags[device]]
else:
code = codes[0]
try:
await self._device.async_request(self._device.api.send_data, code)
except (AuthorizationError, NetworkTimeoutError, OSError) as err:
_LOGGER.error("Failed to send '%s': %s", cmd, err)
except (BroadlinkException, OSError) as err:
_LOGGER.error("Error during %s: %s", service, err)
break
except BroadlinkException as err:
_LOGGER.error("Failed to send '%s': %s", cmd, err)
should_delay = False
continue
should_delay = True
if is_toggle_cmd:
if len(codes) > 1:
self._flags[device] ^= 1
at_least_one_sent = True
self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY)
if at_least_one_sent:
self._flag_storage.async_delay_save(self.get_flags, FLAG_SAVE_DELAY)
async def async_learn_command(self, **kwargs):
"""Learn a list of commands from a remote."""
@ -307,39 +321,47 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
command_type = kwargs[ATTR_COMMAND_TYPE]
device = kwargs[ATTR_DEVICE]
toggle = kwargs[ATTR_ALTERNATIVE]
service = f"{RM_DOMAIN}.{SERVICE_LEARN_COMMAND}"
if not self._state:
_LOGGER.warning(
"remote.learn_command canceled: %s entity is turned off", self.entity_id
"%s canceled: %s entity is turned off", service, self.entity_id
)
return
if command_type == COMMAND_TYPE_IR:
learn_command = self._async_learn_ir_command
else:
learn_command = self._async_learn_rf_command
async with self._lock:
if command_type == COMMAND_TYPE_IR:
learn_command = self._async_learn_ir_command
should_store = False
elif hasattr(self._device.api, "sweep_frequency"):
learn_command = self._async_learn_rf_command
for command in commands:
try:
code = await learn_command(command)
if toggle:
code = [code, await learn_command(command)]
else:
err_msg = f"{self.entity_id} doesn't support learning RF commands"
_LOGGER.error("Failed to call %s: %s", service, err_msg)
raise ValueError(err_msg)
except (AuthorizationError, NetworkTimeoutError, OSError) as err:
_LOGGER.error("Failed to learn '%s': %s", command, err)
break
should_store = False
except BroadlinkException as err:
_LOGGER.error("Failed to learn '%s': %s", command, err)
continue
for command in commands:
try:
code = await learn_command(command)
if toggle:
code = [code, await learn_command(command)]
self._codes.setdefault(device, {}).update({command: code})
should_store = True
except (AuthorizationError, NetworkTimeoutError, OSError) as err:
_LOGGER.error("Failed to learn '%s': %s", command, err)
break
if should_store:
await self._code_storage.async_save(self._codes)
except BroadlinkException as err:
_LOGGER.error("Failed to learn '%s': %s", command, err)
continue
self._codes.setdefault(device, {}).update({command: code})
should_store = True
if should_store:
await self._code_storage.async_save(self._codes)
async def _async_learn_ir_command(self, command):
"""Learn an infrared command."""

View File

@ -109,7 +109,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Broadlink switch."""
device = hass.data[DOMAIN].devices[config_entry.entry_id]
if device.api.type in {"RM2", "RM4"}:
if device.api.type in {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}:
platform_data = hass.data[DOMAIN].platforms.get(SWITCH_DOMAIN, {})
user_defined_switches = platform_data.get(device.api.mac, {})
switches = [
@ -119,12 +119,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
elif device.api.type == "SP1":
switches = [BroadlinkSP1Switch(device)]
elif device.api.type == "SP2":
elif device.api.type in {"SP2", "SP2S", "SP3", "SP3S", "SP4", "SP4B"}:
switches = [BroadlinkSP2Switch(device)]
elif device.api.type in {"SP4", "SP4B"}:
switches = [BroadlinkSP4Switch(device)]
elif device.api.type == "BG1":
switches = [BroadlinkBG1Slot(device, slot) for slot in range(1, 3)]
@ -143,7 +140,6 @@ class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC):
self._command_on = command_on
self._command_off = command_off
self._coordinator = device.update_manager.coordinator
self._device_class = None
self._state = None
@property
@ -174,7 +170,7 @@ class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC):
@property
def device_class(self):
"""Return device class."""
return self._device_class
return DEVICE_CLASS_SWITCH
@property
def device_info(self):
@ -254,7 +250,6 @@ class BroadlinkSP1Switch(BroadlinkSwitch):
def __init__(self, device):
"""Initialize the switch."""
super().__init__(device, 1, 0)
self._device_class = DEVICE_CLASS_OUTLET
@property
def unique_id(self):
@ -277,10 +272,8 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
def __init__(self, device, *args, **kwargs):
"""Initialize the switch."""
super().__init__(device, *args, **kwargs)
self._state = self._coordinator.data["state"]
self._load_power = self._coordinator.data["load_power"]
if device.api.model == "SC1":
self._device_class = DEVICE_CLASS_SWITCH
self._state = self._coordinator.data["pwr"]
self._load_power = self._coordinator.data.get("power")
@property
def assumed_state(self):
@ -292,33 +285,12 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
"""Return the current power usage in Watt."""
return self._load_power
@callback
def update_data(self):
"""Update data."""
if self._coordinator.last_update_success:
self._state = self._coordinator.data["state"]
self._load_power = self._coordinator.data["load_power"]
self.async_write_ha_state()
class BroadlinkSP4Switch(BroadlinkSP1Switch):
"""Representation of a Broadlink SP4 switch."""
def __init__(self, device, *args, **kwargs):
"""Initialize the switch."""
super().__init__(device, *args, **kwargs)
self._state = self._coordinator.data["pwr"]
@property
def assumed_state(self):
"""Return True if unable to access real state of the switch."""
return False
@callback
def update_data(self):
"""Update data."""
if self._coordinator.last_update_success:
self._state = self._coordinator.data["pwr"]
self._load_power = self._coordinator.data.get("power")
self.async_write_ha_state()
@ -330,7 +302,6 @@ class BroadlinkMP1Slot(BroadlinkSwitch):
super().__init__(device, 1, 0)
self._slot = slot
self._state = self._coordinator.data[f"s{slot}"]
self._device_class = DEVICE_CLASS_OUTLET
@property
def unique_id(self):
@ -374,7 +345,6 @@ class BroadlinkBG1Slot(BroadlinkSwitch):
super().__init__(device, 1, 0)
self._slot = slot
self._state = self._coordinator.data[f"pwr{slot}"]
self._device_class = DEVICE_CLASS_OUTLET
@property
def unique_id(self):
@ -391,6 +361,11 @@ class BroadlinkBG1Slot(BroadlinkSwitch):
"""Return True if unable to access real state of the switch."""
return False
@property
def device_class(self):
"""Return device class."""
return DEVICE_CLASS_OUTLET
@callback
def update_data(self):
"""Update data."""

View File

@ -1,17 +1,9 @@
"""Support for fetching data from Broadlink devices."""
from abc import ABC, abstractmethod
from datetime import timedelta
from functools import partial
import logging
import broadlink as blk
from broadlink.exceptions import (
AuthorizationError,
BroadlinkException,
CommandNotSupportedError,
NetworkTimeoutError,
StorageError,
)
from broadlink.exceptions import AuthorizationError, BroadlinkException
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt
@ -21,17 +13,20 @@ _LOGGER = logging.getLogger(__name__)
def get_update_manager(device):
"""Return an update manager for a given Broadlink device."""
if device.api.model.startswith("RM mini"):
return BroadlinkRMMini3UpdateManager(device)
update_managers = {
"A1": BroadlinkA1UpdateManager,
"BG1": BroadlinkBG1UpdateManager,
"MP1": BroadlinkMP1UpdateManager,
"RM2": BroadlinkRMUpdateManager,
"RM4": BroadlinkRMUpdateManager,
"RM4MINI": BroadlinkRMUpdateManager,
"RM4PRO": BroadlinkRMUpdateManager,
"RMMINI": BroadlinkRMUpdateManager,
"RMMINIB": BroadlinkRMUpdateManager,
"RMPRO": BroadlinkRMUpdateManager,
"SP1": BroadlinkSP1UpdateManager,
"SP2": BroadlinkSP2UpdateManager,
"SP2S": BroadlinkSP2UpdateManager,
"SP3": BroadlinkSP2UpdateManager,
"SP3S": BroadlinkSP2UpdateManager,
"SP4": BroadlinkSP4UpdateManager,
"SP4B": BroadlinkSP4UpdateManager,
}
@ -114,28 +109,18 @@ class BroadlinkMP1UpdateManager(BroadlinkUpdateManager):
return await self.device.async_request(self.device.api.check_power)
class BroadlinkRMMini3UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink RM mini 3 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
hello = partial(
blk.discover,
discover_ip_address=self.device.api.host[0],
timeout=self.device.api.timeout,
)
devices = await self.device.hass.async_add_executor_job(hello)
if not devices:
raise NetworkTimeoutError("The device is offline")
return {}
class BroadlinkRMUpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink RM2 and RM4 devices."""
"""Manages updates for Broadlink remotes."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.check_sensors)
device = self.device
if hasattr(device.api, "check_sensors"):
return await device.async_request(device.api.check_sensors)
await device.async_request(device.api.update)
return {}
class BroadlinkSP1UpdateManager(BroadlinkUpdateManager):
@ -151,14 +136,14 @@ class BroadlinkSP2UpdateManager(BroadlinkUpdateManager):
async def async_fetch_data(self):
"""Fetch data from the device."""
device = self.device
data = {}
data["state"] = await self.device.async_request(self.device.api.check_power)
try:
data["load_power"] = await self.device.async_request(
self.device.api.get_energy
)
except (CommandNotSupportedError, StorageError):
data["load_power"] = None
data["pwr"] = await device.async_request(device.api.check_power)
if hasattr(device.api, "get_energy"):
data["power"] = await device.async_request(device.api.get_energy)
return data

View File

@ -381,7 +381,7 @@ boto3==1.9.252
bravia-tv==1.0.8
# homeassistant.components.broadlink
broadlink==0.16.0
broadlink==0.17.0
# homeassistant.components.brother
brother==0.2.1

View File

@ -211,7 +211,7 @@ bond-api==0.1.11
bravia-tv==1.0.8
# homeassistant.components.broadlink
broadlink==0.16.0
broadlink==0.17.0
# homeassistant.components.brother
brother==0.2.1

View File

@ -12,7 +12,7 @@ BROADLINK_DEVICES = {
"34ea34befc25",
"RM mini 3",
"Broadlink",
"RM2",
"RMMINI",
0x2737,
57,
8,
@ -22,7 +22,7 @@ BROADLINK_DEVICES = {
"34ea34b43b5a",
"RM mini 3",
"Broadlink",
"RM4",
"RMMINIB",
0x5F36,
44017,
10,
@ -32,7 +32,7 @@ BROADLINK_DEVICES = {
"34ea34b43d22",
"RM pro",
"Broadlink",
"RM2",
"RMPRO",
0x2787,
20025,
7,
@ -42,7 +42,7 @@ BROADLINK_DEVICES = {
"34ea34c43f31",
"RM4 pro",
"Broadlink",
"RM4",
"RM4PRO",
0x6026,
52,
4,
@ -62,7 +62,7 @@ BROADLINK_DEVICES = {
"34ea34b61d2c",
"LB1",
"Broadlink",
"SmartBulb",
"LB1",
0x504E,
57,
5,
@ -96,9 +96,6 @@ class BroadlinkDevice:
with patch(
"homeassistant.components.broadlink.device.blk.gendevice",
return_value=mock_api,
), patch(
"homeassistant.components.broadlink.updater.blk.discover",
return_value=[mock_api],
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()