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:
Felipe Martins Diel 2020-04-18 20:16:49 -03:00 committed by GitHub
parent 37463d655a
commit a365f456fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 88 deletions

View File

@ -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

View File

@ -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"]

View File

@ -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")

View File

@ -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

View File

@ -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,