mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add support to the new Broadlink RM Mini 3 and RM4 Series (#32523)
* Add device type * Use snake_case for devtype Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com> * Validate device type as positive int * Add device type 0x5f36 to switch * Use default type for sensors * Add RM4 to switch platform * Use snake_case for devtype * Support multiple types of remote * Validate ip address * Improve code readability * Add const.py to .coveragerc * Use None for unknown device types * Fix sensors and standardize platform schemas * Fix if statement Co-authored-by: springstan <46536646+springstan@users.noreply.github.com>
This commit is contained in:
parent
37463d655a
commit
a365f456fc
@ -84,6 +84,7 @@ omit =
|
||||
homeassistant/components/braviatv/__init__.py
|
||||
homeassistant/components/braviatv/const.py
|
||||
homeassistant/components/braviatv/media_player.py
|
||||
homeassistant/components/broadlink/const.py
|
||||
homeassistant/components/broadlink/remote.py
|
||||
homeassistant/components/broadlink/sensor.py
|
||||
homeassistant/components/broadlink/switch.py
|
||||
|
@ -1,7 +1,38 @@
|
||||
"""Constants for broadlink platform."""
|
||||
CONF_PACKET = "packet"
|
||||
|
||||
DEFAULT_LEARNING_TIMEOUT = 20
|
||||
DEFAULT_NAME = "Broadlink"
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_RETRY = 3
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
DOMAIN = "broadlink"
|
||||
|
||||
SERVICE_LEARN = "learn"
|
||||
SERVICE_SEND = "send"
|
||||
|
||||
A1_TYPES = ["a1"]
|
||||
MP1_TYPES = ["mp1"]
|
||||
RM_TYPES = [
|
||||
"rm",
|
||||
"rm2",
|
||||
"rm_mini",
|
||||
"rm_mini_shate",
|
||||
"rm_pro_phicomm",
|
||||
"rm2_home_plus",
|
||||
"rm2_home_plus_gdt",
|
||||
"rm2_pro_plus",
|
||||
"rm2_pro_plus2",
|
||||
"rm2_pro_plus_bl",
|
||||
]
|
||||
RM4_TYPES = [
|
||||
"rm_mini3_newblackbean",
|
||||
"rm_mini3_redbean",
|
||||
"rm4_mini",
|
||||
"rm4_pro",
|
||||
"rm4c_mini",
|
||||
"rm4c_pro",
|
||||
]
|
||||
SP1_TYPES = ["sp1"]
|
||||
SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"]
|
||||
|
@ -8,7 +8,7 @@ from ipaddress import ip_address
|
||||
from itertools import product
|
||||
import logging
|
||||
|
||||
import broadlink
|
||||
import broadlink as blk
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.remote import (
|
||||
@ -24,7 +24,7 @@ from homeassistant.components.remote import (
|
||||
SUPPORT_LEARN_COMMAND,
|
||||
RemoteDevice,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -32,21 +32,26 @@ from homeassistant.helpers.storage import Store
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import DOMAIN, data_packet, hostname, mac_address
|
||||
from .const import (
|
||||
DEFAULT_LEARNING_TIMEOUT,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_RETRY,
|
||||
DEFAULT_TIMEOUT,
|
||||
RM4_TYPES,
|
||||
RM_TYPES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_LEARNING_TIMEOUT = 20
|
||||
DEFAULT_NAME = "Broadlink"
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_RETRY = 3
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=2)
|
||||
|
||||
CODE_STORAGE_VERSION = 1
|
||||
FLAG_STORAGE_VERSION = 1
|
||||
FLAG_SAVE_DELAY = 15
|
||||
|
||||
DEVICE_TYPES = RM_TYPES + RM4_TYPES
|
||||
|
||||
MINIMUM_SERVICE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_COMMAND): vol.All(
|
||||
@ -72,6 +77,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): vol.All(vol.Any(hostname, ip_address), cv.string),
|
||||
vol.Required(CONF_MAC): mac_address,
|
||||
vol.Optional(CONF_TYPE, default=DEVICE_TYPES[0]): vol.In(DEVICE_TYPES),
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
@ -82,6 +88,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
"""Set up the Broadlink remote."""
|
||||
host = config[CONF_HOST]
|
||||
mac_addr = config[CONF_MAC]
|
||||
model = config[CONF_TYPE]
|
||||
timeout = config[CONF_TIMEOUT]
|
||||
name = config[CONF_NAME]
|
||||
unique_id = f"remote_{hexlify(mac_addr).decode('utf-8')}"
|
||||
@ -91,7 +98,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
return
|
||||
hass.data[DOMAIN][COMPONENT].append(unique_id)
|
||||
|
||||
api = broadlink.rm((host, DEFAULT_PORT), mac_addr, None)
|
||||
if model in RM_TYPES:
|
||||
api = blk.rm((host, DEFAULT_PORT), mac_addr, None)
|
||||
else:
|
||||
api = blk.rm4((host, DEFAULT_PORT), mac_addr, None)
|
||||
api.timeout = timeout
|
||||
code_storage = Store(hass, CODE_STORAGE_VERSION, f"broadlink_{unique_id}_codes")
|
||||
flag_storage = Store(hass, FLAG_STORAGE_VERSION, f"broadlink_{unique_id}_flags")
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""Support for the Broadlink RM2 Pro (only temperature) and A1 devices."""
|
||||
import binascii
|
||||
from datetime import timedelta
|
||||
from ipaddress import ip_address
|
||||
import logging
|
||||
|
||||
import broadlink
|
||||
import broadlink as blk
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TYPE,
|
||||
TEMP_CELSIUS,
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
@ -21,10 +22,18 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import hostname, mac_address
|
||||
from .const import (
|
||||
A1_TYPES,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TIMEOUT,
|
||||
RM4_TYPES,
|
||||
RM_TYPES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEVICE_DEFAULT_NAME = "Broadlink sensor"
|
||||
DEFAULT_TIMEOUT = 10
|
||||
SCAN_INTERVAL = timedelta(seconds=300)
|
||||
|
||||
SENSOR_TYPES = {
|
||||
@ -35,14 +44,17 @@ SENSOR_TYPES = {
|
||||
"noise": ["Noise", " "],
|
||||
}
|
||||
|
||||
DEVICE_TYPES = A1_TYPES + RM_TYPES + RM4_TYPES
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): vol.Coerce(str),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): vol.Coerce(str),
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
||||
),
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_MAC): cv.string,
|
||||
vol.Required(CONF_HOST): vol.All(vol.Any(hostname, ip_address), cv.string),
|
||||
vol.Required(CONF_MAC): mac_address,
|
||||
vol.Optional(CONF_TYPE, default=DEVICE_TYPES[0]): vol.In(DEVICE_TYPES),
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
}
|
||||
)
|
||||
@ -50,13 +62,22 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Broadlink device sensors."""
|
||||
host = config.get(CONF_HOST)
|
||||
mac = config.get(CONF_MAC).encode().replace(b":", b"")
|
||||
mac_addr = binascii.unhexlify(mac)
|
||||
name = config.get(CONF_NAME)
|
||||
timeout = config.get(CONF_TIMEOUT)
|
||||
host = config[CONF_HOST]
|
||||
mac_addr = config[CONF_MAC]
|
||||
model = config[CONF_TYPE]
|
||||
name = config[CONF_NAME]
|
||||
timeout = config[CONF_TIMEOUT]
|
||||
update_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
||||
broadlink_data = BroadlinkData(update_interval, host, mac_addr, timeout)
|
||||
|
||||
if model in RM4_TYPES:
|
||||
api = blk.rm4((host, DEFAULT_PORT), mac_addr, None)
|
||||
check_sensors = api.check_sensors
|
||||
else:
|
||||
api = blk.a1((host, DEFAULT_PORT), mac_addr, None)
|
||||
check_sensors = api.check_sensors_raw
|
||||
|
||||
api.timeout = timeout
|
||||
broadlink_data = BroadlinkData(api, check_sensors, update_interval)
|
||||
dev = []
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]:
|
||||
dev.append(BroadlinkSensor(name, broadlink_data, variable))
|
||||
@ -109,13 +130,11 @@ class BroadlinkSensor(Entity):
|
||||
class BroadlinkData:
|
||||
"""Representation of a Broadlink data object."""
|
||||
|
||||
def __init__(self, interval, ip_addr, mac_addr, timeout):
|
||||
def __init__(self, api, check_sensors, interval):
|
||||
"""Initialize the data object."""
|
||||
self.api = api
|
||||
self.check_sensors = check_sensors
|
||||
self.data = None
|
||||
self.ip_addr = ip_addr
|
||||
self.mac_addr = mac_addr
|
||||
self.timeout = timeout
|
||||
self._connect()
|
||||
self._schema = vol.Schema(
|
||||
{
|
||||
vol.Optional("temperature"): vol.Range(min=-50, max=150),
|
||||
@ -129,14 +148,9 @@ class BroadlinkData:
|
||||
if not self._auth():
|
||||
_LOGGER.warning("Failed to connect to device")
|
||||
|
||||
def _connect(self):
|
||||
|
||||
self._device = broadlink.a1((self.ip_addr, 80), self.mac_addr, None)
|
||||
self._device.timeout = self.timeout
|
||||
|
||||
def _update(self, retry=3):
|
||||
try:
|
||||
data = self._device.check_sensors_raw()
|
||||
data = self.check_sensors()
|
||||
if data is not None:
|
||||
self.data = self._schema(data)
|
||||
return
|
||||
@ -152,10 +166,9 @@ class BroadlinkData:
|
||||
|
||||
def _auth(self, retry=3):
|
||||
try:
|
||||
auth = self._device.auth()
|
||||
auth = self.api.auth()
|
||||
except OSError:
|
||||
auth = False
|
||||
if not auth and retry > 0:
|
||||
self._connect()
|
||||
return self._auth(retry - 1)
|
||||
return auth
|
||||
|
@ -1,10 +1,10 @@
|
||||
"""Support for Broadlink RM devices."""
|
||||
import binascii
|
||||
from datetime import timedelta
|
||||
from ipaddress import ip_address
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import broadlink
|
||||
import broadlink as blk
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import DOMAIN, PLATFORM_SCHEMA, SwitchDevice
|
||||
@ -23,35 +23,27 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.util import Throttle, slugify
|
||||
|
||||
from . import async_setup_service, data_packet
|
||||
from . import async_setup_service, data_packet, hostname, mac_address
|
||||
from .const import (
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_RETRY,
|
||||
DEFAULT_TIMEOUT,
|
||||
MP1_TYPES,
|
||||
RM4_TYPES,
|
||||
RM_TYPES,
|
||||
SP1_TYPES,
|
||||
SP2_TYPES,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TIME_BETWEEN_UPDATES = timedelta(seconds=5)
|
||||
|
||||
DEFAULT_NAME = "Broadlink switch"
|
||||
DEFAULT_TIMEOUT = 10
|
||||
DEFAULT_RETRY = 2
|
||||
CONF_SLOTS = "slots"
|
||||
CONF_RETRY = "retry"
|
||||
|
||||
RM_TYPES = [
|
||||
"rm",
|
||||
"rm2",
|
||||
"rm_mini",
|
||||
"rm_pro_phicomm",
|
||||
"rm2_home_plus",
|
||||
"rm2_home_plus_gdt",
|
||||
"rm2_pro_plus",
|
||||
"rm2_pro_plus2",
|
||||
"rm2_pro_plus_bl",
|
||||
"rm_mini_shate",
|
||||
]
|
||||
SP1_TYPES = ["sp1"]
|
||||
SP2_TYPES = ["sp2", "honeywell_sp2", "sp3", "spmini2", "spminiplus"]
|
||||
MP1_TYPES = ["mp1"]
|
||||
|
||||
SWITCH_TYPES = RM_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES
|
||||
DEVICE_TYPES = RM_TYPES + RM4_TYPES + SP1_TYPES + SP2_TYPES + MP1_TYPES
|
||||
|
||||
SWITCH_SCHEMA = vol.Schema(
|
||||
{
|
||||
@ -76,10 +68,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
SWITCH_SCHEMA
|
||||
),
|
||||
vol.Optional(CONF_SLOTS, default={}): MP1_SWITCH_SLOT_SCHEMA,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_MAC): cv.string,
|
||||
vol.Required(CONF_HOST): vol.All(vol.Any(hostname, ip_address), cv.string),
|
||||
vol.Required(CONF_MAC): mac_address,
|
||||
vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_TYPE, default=SWITCH_TYPES[0]): vol.In(SWITCH_TYPES),
|
||||
vol.Optional(CONF_TYPE, default=DEVICE_TYPES[0]): vol.In(DEVICE_TYPES),
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int,
|
||||
}
|
||||
@ -91,47 +83,53 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
devices = config.get(CONF_SWITCHES)
|
||||
slots = config.get("slots", {})
|
||||
ip_addr = config.get(CONF_HOST)
|
||||
host = config.get(CONF_HOST)
|
||||
mac_addr = config.get(CONF_MAC)
|
||||
friendly_name = config.get(CONF_FRIENDLY_NAME)
|
||||
mac_addr = binascii.unhexlify(config.get(CONF_MAC).encode().replace(b":", b""))
|
||||
switch_type = config.get(CONF_TYPE)
|
||||
model = config[CONF_TYPE]
|
||||
retry_times = config.get(CONF_RETRY)
|
||||
|
||||
def _get_mp1_slot_name(switch_friendly_name, slot):
|
||||
def generate_rm_switches(switches, broadlink_device):
|
||||
"""Generate RM switches."""
|
||||
return [
|
||||
BroadlinkRMSwitch(
|
||||
object_id,
|
||||
config.get(CONF_FRIENDLY_NAME, object_id),
|
||||
broadlink_device,
|
||||
config.get(CONF_COMMAND_ON),
|
||||
config.get(CONF_COMMAND_OFF),
|
||||
retry_times,
|
||||
)
|
||||
for object_id, config in switches.items()
|
||||
]
|
||||
|
||||
def get_mp1_slot_name(switch_friendly_name, slot):
|
||||
"""Get slot name."""
|
||||
if not slots[f"slot_{slot}"]:
|
||||
return f"{switch_friendly_name} slot {slot}"
|
||||
return slots[f"slot_{slot}"]
|
||||
|
||||
if switch_type in RM_TYPES:
|
||||
broadlink_device = broadlink.rm((ip_addr, 80), mac_addr, None)
|
||||
hass.add_job(async_setup_service, hass, ip_addr, broadlink_device)
|
||||
|
||||
switches = []
|
||||
for object_id, device_config in devices.items():
|
||||
switches.append(
|
||||
BroadlinkRMSwitch(
|
||||
object_id,
|
||||
device_config.get(CONF_FRIENDLY_NAME, object_id),
|
||||
broadlink_device,
|
||||
device_config.get(CONF_COMMAND_ON),
|
||||
device_config.get(CONF_COMMAND_OFF),
|
||||
retry_times,
|
||||
)
|
||||
)
|
||||
elif switch_type in SP1_TYPES:
|
||||
broadlink_device = broadlink.sp1((ip_addr, 80), mac_addr, None)
|
||||
if model in RM_TYPES:
|
||||
broadlink_device = blk.rm((host, DEFAULT_PORT), mac_addr, None)
|
||||
hass.add_job(async_setup_service, hass, host, broadlink_device)
|
||||
switches = generate_rm_switches(devices, broadlink_device)
|
||||
elif model in RM4_TYPES:
|
||||
broadlink_device = blk.rm4((host, DEFAULT_PORT), mac_addr, None)
|
||||
hass.add_job(async_setup_service, hass, host, broadlink_device)
|
||||
switches = generate_rm_switches(devices, broadlink_device)
|
||||
elif model in SP1_TYPES:
|
||||
broadlink_device = blk.sp1((host, DEFAULT_PORT), mac_addr, None)
|
||||
switches = [BroadlinkSP1Switch(friendly_name, broadlink_device, retry_times)]
|
||||
elif switch_type in SP2_TYPES:
|
||||
broadlink_device = broadlink.sp2((ip_addr, 80), mac_addr, None)
|
||||
elif model in SP2_TYPES:
|
||||
broadlink_device = blk.sp2((host, DEFAULT_PORT), mac_addr, None)
|
||||
switches = [BroadlinkSP2Switch(friendly_name, broadlink_device, retry_times)]
|
||||
elif switch_type in MP1_TYPES:
|
||||
elif model in MP1_TYPES:
|
||||
switches = []
|
||||
broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr, None)
|
||||
broadlink_device = blk.mp1((host, DEFAULT_PORT), mac_addr, None)
|
||||
parent_device = BroadlinkMP1Switch(broadlink_device, retry_times)
|
||||
for i in range(1, 5):
|
||||
slot = BroadlinkMP1Slot(
|
||||
_get_mp1_slot_name(friendly_name, i),
|
||||
get_mp1_slot_name(friendly_name, i),
|
||||
broadlink_device,
|
||||
i,
|
||||
parent_device,
|
||||
|
Loading…
x
Reference in New Issue
Block a user