core/homeassistant/components/broadlink/switch.py

410 lines
12 KiB
Python

"""Support for Broadlink switches."""
from abc import ABC, abstractmethod
from functools import partial
import logging
from broadlink.exceptions import BroadlinkException
import voluptuous as vol
from homeassistant.components.switch import (
DEVICE_CLASS_OUTLET,
DEVICE_CLASS_SWITCH,
PLATFORM_SCHEMA,
SwitchEntity,
)
from homeassistant.const import (
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_FRIENDLY_NAME,
CONF_HOST,
CONF_MAC,
CONF_NAME,
CONF_SWITCHES,
CONF_TIMEOUT,
CONF_TYPE,
STATE_ON,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
from .const import DOMAIN, SWITCH_DOMAIN
from .helpers import data_packet, import_device, mac_address
_LOGGER = logging.getLogger(__name__)
CONF_SLOTS = "slots"
SWITCH_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_COMMAND_OFF): data_packet,
vol.Optional(CONF_COMMAND_ON): data_packet,
}
)
OLD_SWITCH_SCHEMA = vol.Schema(
{
vol.Optional(CONF_COMMAND_OFF): data_packet,
vol.Optional(CONF_COMMAND_ON): data_packet,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
}
)
PLATFORM_SCHEMA = vol.All(
cv.deprecated(CONF_HOST),
cv.deprecated(CONF_SLOTS),
cv.deprecated(CONF_TIMEOUT),
cv.deprecated(CONF_TYPE),
PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MAC): mac_address,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_SWITCHES, default=[]): vol.Any(
cv.schema_with_slug_keys(OLD_SWITCH_SCHEMA),
vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
),
}
),
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Import the device and set up custom switches.
This is for backward compatibility.
Do not use this method.
"""
mac_addr = config[CONF_MAC]
host = config.get(CONF_HOST)
switches = config.get(CONF_SWITCHES)
if not isinstance(switches, list):
switches = [
{CONF_NAME: switch.pop(CONF_FRIENDLY_NAME, name), **switch}
for name, switch in switches.items()
]
_LOGGER.warning(
"Your configuration for the switch platform is deprecated. "
"Please refer to the Broadlink documentation to catch up"
)
if switches:
platform_data = hass.data[DOMAIN].platforms.setdefault(SWITCH_DOMAIN, {})
platform_data.setdefault(mac_addr, []).extend(switches)
else:
_LOGGER.warning(
"The switch platform is deprecated, except for custom IR/RF "
"switches. Please refer to the Broadlink documentation to "
"catch up"
)
if host:
import_device(hass, host)
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"}:
platform_data = hass.data[DOMAIN].platforms.get(SWITCH_DOMAIN, {})
user_defined_switches = platform_data.get(device.api.mac, {})
switches = [
BroadlinkRMSwitch(device, config) for config in user_defined_switches
]
elif device.api.type == "SP1":
switches = [BroadlinkSP1Switch(device)]
elif device.api.type == "SP2":
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)]
elif device.api.type == "MP1":
switches = [BroadlinkMP1Slot(device, slot) for slot in range(1, 5)]
async_add_entities(switches)
class BroadlinkSwitch(SwitchEntity, RestoreEntity, ABC):
"""Representation of a Broadlink switch."""
def __init__(self, device, command_on, command_off):
"""Initialize the switch."""
self._device = device
self._command_on = command_on
self._command_off = command_off
self._coordinator = device.update_manager.coordinator
self._device_class = None
self._state = None
@property
def name(self):
"""Return the name of the switch."""
return f"{self._device.name} Switch"
@property
def assumed_state(self):
"""Return True if unable to access real state of the switch."""
return True
@property
def available(self):
"""Return True if the switch is available."""
return self._device.update_manager.available
@property
def is_on(self):
"""Return True if the switch is on."""
return self._state
@property
def should_poll(self):
"""Return True if the switch has to be polled for state."""
return False
@property
def device_class(self):
"""Return device class."""
return self._device_class
@property
def device_info(self):
"""Return device info."""
return {
"identifiers": {(DOMAIN, self._device.unique_id)},
"manufacturer": self._device.api.manufacturer,
"model": self._device.api.model,
"name": self._device.name,
"sw_version": self._device.fw_version,
}
@callback
def update_data(self):
"""Update data."""
self.async_write_ha_state()
async def async_added_to_hass(self):
"""Call when the switch is added to hass."""
if self._state is None:
state = await self.async_get_last_state()
self._state = state is not None and state.state == STATE_ON
self.async_on_remove(self._coordinator.async_add_listener(self.update_data))
async def async_update(self):
"""Update the switch."""
await self._coordinator.async_request_refresh()
async def async_turn_on(self, **kwargs):
"""Turn on the switch."""
if await self._async_send_packet(self._command_on):
self._state = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs):
"""Turn off the switch."""
if await self._async_send_packet(self._command_off):
self._state = False
self.async_write_ha_state()
@abstractmethod
async def _async_send_packet(self, packet):
"""Send a packet to the device."""
class BroadlinkRMSwitch(BroadlinkSwitch):
"""Representation of a Broadlink RM switch."""
def __init__(self, device, config):
"""Initialize the switch."""
super().__init__(
device, config.get(CONF_COMMAND_ON), config.get(CONF_COMMAND_OFF)
)
self._name = config[CONF_NAME]
@property
def name(self):
"""Return the name of the switch."""
return self._name
async def _async_send_packet(self, packet):
"""Send a packet to the device."""
if packet is None:
return True
try:
await self._device.async_request(self._device.api.send_data, packet)
except (BroadlinkException, OSError) as err:
_LOGGER.error("Failed to send packet: %s", err)
return False
return True
class BroadlinkSP1Switch(BroadlinkSwitch):
"""Representation of a Broadlink SP1 switch."""
def __init__(self, device):
"""Initialize the switch."""
super().__init__(device, 1, 0)
self._device_class = DEVICE_CLASS_OUTLET
@property
def unique_id(self):
"""Return the unique id of the switch."""
return self._device.unique_id
async def _async_send_packet(self, packet):
"""Send a packet to the device."""
try:
await self._device.async_request(self._device.api.set_power, packet)
except (BroadlinkException, OSError) as err:
_LOGGER.error("Failed to send packet: %s", err)
return False
return True
class BroadlinkSP2Switch(BroadlinkSP1Switch):
"""Representation of a Broadlink SP2 switch."""
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
@property
def assumed_state(self):
"""Return True if unable to access real state of the switch."""
return False
@property
def current_power_w(self):
"""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.async_write_ha_state()
class BroadlinkMP1Slot(BroadlinkSwitch):
"""Representation of a Broadlink MP1 slot."""
def __init__(self, device, slot):
"""Initialize the switch."""
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):
"""Return the unique id of the slot."""
return f"{self._device.unique_id}-s{self._slot}"
@property
def name(self):
"""Return the name of the switch."""
return f"{self._device.name} S{self._slot}"
@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[f"s{self._slot}"]
self.async_write_ha_state()
async def _async_send_packet(self, packet):
"""Send a packet to the device."""
try:
await self._device.async_request(
self._device.api.set_power, self._slot, packet
)
except (BroadlinkException, OSError) as err:
_LOGGER.error("Failed to send packet: %s", err)
return False
return True
class BroadlinkBG1Slot(BroadlinkSwitch):
"""Representation of a Broadlink BG1 slot."""
def __init__(self, device, slot):
"""Initialize the switch."""
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):
"""Return the unique id of the slot."""
return f"{self._device.unique_id}-s{self._slot}"
@property
def name(self):
"""Return the name of the switch."""
return f"{self._device.name} S{self._slot}"
@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[f"pwr{self._slot}"]
self.async_write_ha_state()
async def _async_send_packet(self, packet):
"""Send a packet to the device."""
set_state = partial(self._device.api.set_state, **{f"pwr{self._slot}": packet})
try:
await self._device.async_request(set_state)
except (BroadlinkException, OSError) as err:
_LOGGER.error("Failed to send packet: %s", err)
return False
return True