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): async def async_set_device(self, device, raise_on_progress=True):
"""Define a device for the config flow.""" """Define a device for the config flow."""
supported_types = { supported_types = set.union(*DOMAINS_AND_TYPES.values())
device_type
for device_types in DOMAINS_AND_TYPES
for device_type in device_types[1]
}
if device.type not in supported_types: if device.type not in supported_types:
_LOGGER.error( _LOGGER.error(
"Unsupported device: %s. If it worked before, please open " "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" DOMAIN = "broadlink"
DOMAINS_AND_TYPES = ( DOMAINS_AND_TYPES = {
(REMOTE_DOMAIN, ("RM2", "RM4")), REMOTE_DOMAIN: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
(SENSOR_DOMAIN, ("A1", "RM2", "RM4")), SENSOR_DOMAIN: {"A1", "RM4MINI", "RM4PRO", "RMPRO"},
(SWITCH_DOMAIN, ("BG1", "MP1", "RM2", "RM4", "SP1", "SP2", "SP4", "SP4B")), SWITCH_DOMAIN: {
) "BG1",
"MP1",
"RM4MINI",
"RM4PRO",
"RMMINI",
"RMMINIB",
"RMPRO",
"SP1",
"SP2",
"SP2S",
"SP3",
"SP3S",
"SP4",
"SP4B",
},
}
DEFAULT_PORT = 80 DEFAULT_PORT = 80
DEFAULT_TIMEOUT = 5 DEFAULT_TIMEOUT = 5

View File

@ -22,9 +22,9 @@ from .updater import get_update_manager
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_domains(device_type): def get_domains(dev_type):
"""Return the domains available for a device 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: class BroadlinkDevice:

View File

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

View File

@ -26,6 +26,8 @@ from homeassistant.components.remote import (
DOMAIN as RM_DOMAIN, DOMAIN as RM_DOMAIN,
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
SERVICE_DELETE_COMMAND, SERVICE_DELETE_COMMAND,
SERVICE_LEARN_COMMAND,
SERVICE_SEND_COMMAND,
SUPPORT_DELETE_COMMAND, SUPPORT_DELETE_COMMAND,
SUPPORT_LEARN_COMMAND, SUPPORT_LEARN_COMMAND,
RemoteEntity, RemoteEntity,
@ -129,6 +131,7 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
self._codes = {} self._codes = {}
self._flags = defaultdict(int) self._flags = defaultdict(int)
self._state = True self._state = True
self._lock = asyncio.Lock()
@property @property
def name(self): def name(self):
@ -171,39 +174,44 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
"sw_version": self._device.fw_version, "sw_version": self._device.fw_version,
} }
def get_code(self, command, device): def _extract_codes(self, commands, device=None):
"""Return a code and a boolean indicating a toggle command. """Extract a list of codes.
If the command starts with `b64:`, extract the code from it. If the command starts with `b64:`, extract the code from it.
Otherwise, extract the code from the dictionary, using the device Otherwise, extract the code from storage, using the command and
and command as keys. device as keys.
You need to change the flag whenever a toggle command is sent The codes are returned in sublists. For toggle commands, the
successfully. Use `self._flags[device] ^= 1`. sublist contains two codes that must be sent alternately with
each call.
""" """
if command.startswith("b64:"): code_list = []
code, is_toggle_cmd = command[4:], False 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: else:
is_toggle_cmd = False if device is None:
raise ValueError("You need to specify a device")
try: try:
return data_packet(code), is_toggle_cmd codes = self._codes[device][cmd]
except ValueError as err: except KeyError as err:
raise ValueError("Invalid code") from 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 @callback
def get_codes(self): def get_codes(self):
@ -261,44 +269,50 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
device = kwargs.get(ATTR_DEVICE) device = kwargs.get(ATTR_DEVICE)
repeat = kwargs[ATTR_NUM_REPEATS] repeat = kwargs[ATTR_NUM_REPEATS]
delay = kwargs[ATTR_DELAY_SECS] delay = kwargs[ATTR_DELAY_SECS]
service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}"
if not self._state: if not self._state:
_LOGGER.warning( _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 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): rf_flags = {0xB2, 0xD7}
if should_delay: 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) await asyncio.sleep(delay)
try: if len(codes) > 1:
code, is_toggle_cmd = self.get_code(cmd, device) code = codes[self._flags[device]]
else:
except (KeyError, ValueError) as err: code = codes[0]
_LOGGER.error("Failed to send '%s': %s", cmd, err)
should_delay = False
continue
try: try:
await self._device.async_request(self._device.api.send_data, code) await self._device.async_request(self._device.api.send_data, code)
except (BroadlinkException, OSError) as err:
except (AuthorizationError, NetworkTimeoutError, OSError) as err: _LOGGER.error("Error during %s: %s", service, err)
_LOGGER.error("Failed to send '%s': %s", cmd, err)
break break
except BroadlinkException as err: if len(codes) > 1:
_LOGGER.error("Failed to send '%s': %s", cmd, err)
should_delay = False
continue
should_delay = True
if is_toggle_cmd:
self._flags[device] ^= 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): async def async_learn_command(self, **kwargs):
"""Learn a list of commands from a remote.""" """Learn a list of commands from a remote."""
@ -307,39 +321,47 @@ class BroadlinkRemote(RemoteEntity, RestoreEntity):
command_type = kwargs[ATTR_COMMAND_TYPE] command_type = kwargs[ATTR_COMMAND_TYPE]
device = kwargs[ATTR_DEVICE] device = kwargs[ATTR_DEVICE]
toggle = kwargs[ATTR_ALTERNATIVE] toggle = kwargs[ATTR_ALTERNATIVE]
service = f"{RM_DOMAIN}.{SERVICE_LEARN_COMMAND}"
if not self._state: if not self._state:
_LOGGER.warning( _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 return
if command_type == COMMAND_TYPE_IR: async with self._lock:
learn_command = self._async_learn_ir_command if command_type == COMMAND_TYPE_IR:
else: learn_command = self._async_learn_ir_command
learn_command = self._async_learn_rf_command
should_store = False elif hasattr(self._device.api, "sweep_frequency"):
learn_command = self._async_learn_rf_command
for command in commands: else:
try: err_msg = f"{self.entity_id} doesn't support learning RF commands"
code = await learn_command(command) _LOGGER.error("Failed to call %s: %s", service, err_msg)
if toggle: raise ValueError(err_msg)
code = [code, await learn_command(command)]
except (AuthorizationError, NetworkTimeoutError, OSError) as err: should_store = False
_LOGGER.error("Failed to learn '%s': %s", command, err)
break
except BroadlinkException as err: for command in commands:
_LOGGER.error("Failed to learn '%s': %s", command, err) try:
continue code = await learn_command(command)
if toggle:
code = [code, await learn_command(command)]
self._codes.setdefault(device, {}).update({command: code}) except (AuthorizationError, NetworkTimeoutError, OSError) as err:
should_store = True _LOGGER.error("Failed to learn '%s': %s", command, err)
break
if should_store: except BroadlinkException as err:
await self._code_storage.async_save(self._codes) _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): async def _async_learn_ir_command(self, command):
"""Learn an infrared 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.""" """Set up the Broadlink switch."""
device = hass.data[DOMAIN].devices[config_entry.entry_id] 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, {}) platform_data = hass.data[DOMAIN].platforms.get(SWITCH_DOMAIN, {})
user_defined_switches = platform_data.get(device.api.mac, {}) user_defined_switches = platform_data.get(device.api.mac, {})
switches = [ switches = [
@ -119,12 +119,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
elif device.api.type == "SP1": elif device.api.type == "SP1":
switches = [BroadlinkSP1Switch(device)] switches = [BroadlinkSP1Switch(device)]
elif device.api.type == "SP2": elif device.api.type in {"SP2", "SP2S", "SP3", "SP3S", "SP4", "SP4B"}:
switches = [BroadlinkSP2Switch(device)] switches = [BroadlinkSP2Switch(device)]
elif device.api.type in {"SP4", "SP4B"}:
switches = [BroadlinkSP4Switch(device)]
elif device.api.type == "BG1": elif device.api.type == "BG1":
switches = [BroadlinkBG1Slot(device, slot) for slot in range(1, 3)] 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_on = command_on
self._command_off = command_off self._command_off = command_off
self._coordinator = device.update_manager.coordinator self._coordinator = device.update_manager.coordinator
self._device_class = None
self._state = None self._state = None
@property @property
@ -174,7 +170,7 @@ class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC):
@property @property
def device_class(self): def device_class(self):
"""Return device class.""" """Return device class."""
return self._device_class return DEVICE_CLASS_SWITCH
@property @property
def device_info(self): def device_info(self):
@ -254,7 +250,6 @@ class BroadlinkSP1Switch(BroadlinkSwitch):
def __init__(self, device): def __init__(self, device):
"""Initialize the switch.""" """Initialize the switch."""
super().__init__(device, 1, 0) super().__init__(device, 1, 0)
self._device_class = DEVICE_CLASS_OUTLET
@property @property
def unique_id(self): def unique_id(self):
@ -277,10 +272,8 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
def __init__(self, device, *args, **kwargs): def __init__(self, device, *args, **kwargs):
"""Initialize the switch.""" """Initialize the switch."""
super().__init__(device, *args, **kwargs) super().__init__(device, *args, **kwargs)
self._state = self._coordinator.data["state"] self._state = self._coordinator.data["pwr"]
self._load_power = self._coordinator.data["load_power"] self._load_power = self._coordinator.data.get("power")
if device.api.model == "SC1":
self._device_class = DEVICE_CLASS_SWITCH
@property @property
def assumed_state(self): def assumed_state(self):
@ -292,33 +285,12 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
"""Return the current power usage in Watt.""" """Return the current power usage in Watt."""
return self._load_power 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 @callback
def update_data(self): def update_data(self):
"""Update data.""" """Update data."""
if self._coordinator.last_update_success: if self._coordinator.last_update_success:
self._state = self._coordinator.data["pwr"] self._state = self._coordinator.data["pwr"]
self._load_power = self._coordinator.data.get("power")
self.async_write_ha_state() self.async_write_ha_state()
@ -330,7 +302,6 @@ class BroadlinkMP1Slot(BroadlinkSwitch):
super().__init__(device, 1, 0) super().__init__(device, 1, 0)
self._slot = slot self._slot = slot
self._state = self._coordinator.data[f"s{slot}"] self._state = self._coordinator.data[f"s{slot}"]
self._device_class = DEVICE_CLASS_OUTLET
@property @property
def unique_id(self): def unique_id(self):
@ -374,7 +345,6 @@ class BroadlinkBG1Slot(BroadlinkSwitch):
super().__init__(device, 1, 0) super().__init__(device, 1, 0)
self._slot = slot self._slot = slot
self._state = self._coordinator.data[f"pwr{slot}"] self._state = self._coordinator.data[f"pwr{slot}"]
self._device_class = DEVICE_CLASS_OUTLET
@property @property
def unique_id(self): def unique_id(self):
@ -391,6 +361,11 @@ class BroadlinkBG1Slot(BroadlinkSwitch):
"""Return True if unable to access real state of the switch.""" """Return True if unable to access real state of the switch."""
return False return False
@property
def device_class(self):
"""Return device class."""
return DEVICE_CLASS_OUTLET
@callback @callback
def update_data(self): def update_data(self):
"""Update data.""" """Update data."""

View File

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

View File

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

View File

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

View File

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