Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Support for Broadlink switches."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 import logging
7 from typing import Any
8 
9 from broadlink.exceptions import BroadlinkException
10 import voluptuous as vol
11 
13  PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
14  SwitchDeviceClass,
15  SwitchEntity,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import (
19  CONF_COMMAND_OFF,
20  CONF_COMMAND_ON,
21  CONF_HOST,
22  CONF_MAC,
23  CONF_NAME,
24  CONF_SWITCHES,
25  CONF_TIMEOUT,
26  CONF_TYPE,
27  STATE_ON,
28  Platform,
29 )
30 from homeassistant.core import HomeAssistant
31 from homeassistant.exceptions import PlatformNotReady
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 from homeassistant.helpers.restore_state import RestoreEntity
35 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
36 
37 from . import BroadlinkDevice
38 from .const import DOMAIN
39 from .entity import BroadlinkEntity
40 from .helpers import data_packet, import_device, mac_address
41 
42 _LOGGER = logging.getLogger(__name__)
43 
44 CONF_SLOTS = "slots"
45 
46 SWITCH_SCHEMA = vol.Schema(
47  {
48  vol.Required(CONF_NAME): cv.string,
49  vol.Optional(CONF_COMMAND_OFF): data_packet,
50  vol.Optional(CONF_COMMAND_ON): data_packet,
51  }
52 )
53 
54 PLATFORM_SCHEMA = vol.All(
55  cv.deprecated(CONF_HOST),
56  cv.deprecated(CONF_SLOTS),
57  cv.deprecated(CONF_TIMEOUT),
58  cv.deprecated(CONF_TYPE),
59  SWITCH_PLATFORM_SCHEMA.extend(
60  {
61  vol.Required(CONF_MAC): mac_address,
62  vol.Optional(CONF_HOST): cv.string,
63  vol.Optional(CONF_SWITCHES, default=[]): vol.All(
64  cv.ensure_list,
65  [SWITCH_SCHEMA],
66  ),
67  }
68  ),
69 )
70 
71 
73  hass: HomeAssistant,
74  config: ConfigType,
75  async_add_entities: AddEntitiesCallback,
76  discovery_info: DiscoveryInfoType | None = None,
77 ) -> None:
78  """Import the device and set up custom switches.
79 
80  This is for backward compatibility.
81  Do not use this method.
82  """
83  mac_addr = config[CONF_MAC]
84  host = config.get(CONF_HOST)
85 
86  if switches := config.get(CONF_SWITCHES):
87  platform_data = hass.data[DOMAIN].platforms.get(Platform.SWITCH, {})
88  async_add_entities_config_entry: AddEntitiesCallback
89  device: BroadlinkDevice
90  async_add_entities_config_entry, device = platform_data.get(
91  mac_addr, (None, None)
92  )
93  if not async_add_entities_config_entry:
94  raise PlatformNotReady
95 
96  async_add_entities_config_entry(
97  BroadlinkRMSwitch(device, config) for config in switches
98  )
99 
100  else:
101  _LOGGER.warning(
102  "The switch platform is deprecated, except for custom IR/RF "
103  "switches. Please refer to the Broadlink documentation to "
104  "catch up"
105  )
106 
107  if host:
108  import_device(hass, host)
109 
110 
112  hass: HomeAssistant,
113  config_entry: ConfigEntry,
114  async_add_entities: AddEntitiesCallback,
115 ) -> None:
116  """Set up the Broadlink switch."""
117  device = hass.data[DOMAIN].devices[config_entry.entry_id]
118  switches: list[BroadlinkSwitch] = []
119 
120  if device.api.type in {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"}:
121  platform_data = hass.data[DOMAIN].platforms.setdefault(Platform.SWITCH, {})
122  platform_data[device.api.mac] = async_add_entities, device
123  elif device.api.type == "SP1":
124  switches.append(BroadlinkSP1Switch(device))
125 
126  elif device.api.type in {"SP2", "SP2S", "SP3", "SP3S", "SP4", "SP4B"}:
127  switches.append(BroadlinkSP2Switch(device))
128 
129  elif device.api.type == "BG1":
130  switches.extend(BroadlinkBG1Slot(device, slot) for slot in range(1, 3))
131 
132  elif device.api.type in {"MP1", "MP1S"}:
133  switches.extend(BroadlinkMP1Slot(device, slot) for slot in range(1, 5))
134 
135  async_add_entities(switches)
136 
137 
139  """Representation of a Broadlink switch."""
140 
141  _attr_assumed_state = True
142  _attr_device_class = SwitchDeviceClass.SWITCH
143 
144  def __init__(self, device, command_on, command_off):
145  """Initialize the switch."""
146  super().__init__(device)
147  self._command_on_command_on = command_on
148  self._command_off_command_off = command_off
149 
150  async def async_added_to_hass(self) -> None:
151  """Call when the switch is added to hass."""
152  state = await self.async_get_last_stateasync_get_last_state()
153  self._attr_is_on_attr_is_on = state is not None and state.state == STATE_ON
154  await super().async_added_to_hass()
155 
156  async def async_turn_on(self, **kwargs: Any) -> None:
157  """Turn on the switch."""
158  if await self._async_send_packet_async_send_packet(self._command_on_command_on):
159  self._attr_is_on_attr_is_on = True
160  self.async_write_ha_stateasync_write_ha_state()
161 
162  async def async_turn_off(self, **kwargs: Any) -> None:
163  """Turn off the switch."""
164  if await self._async_send_packet_async_send_packet(self._command_off_command_off):
165  self._attr_is_on_attr_is_on = False
166  self.async_write_ha_stateasync_write_ha_state()
167 
168  @abstractmethod
169  async def _async_send_packet(self, packet):
170  """Send a packet to the device."""
171 
172 
173 class BroadlinkRMSwitch(BroadlinkSwitch):
174  """Representation of a Broadlink RM switch."""
175 
176  def __init__(self, device, config):
177  """Initialize the switch."""
178  super().__init__(
179  device, config.get(CONF_COMMAND_ON), config.get(CONF_COMMAND_OFF)
180  )
181  self._attr_name_attr_name = config[CONF_NAME]
182 
183  async def _async_send_packet(self, packet):
184  """Send a packet to the device."""
185  device = self._device_device
186 
187  if packet is None:
188  return True
189 
190  try:
191  await device.async_request(device.api.send_data, packet)
192  except (BroadlinkException, OSError) as err:
193  _LOGGER.error("Failed to send packet: %s", err)
194  return False
195  return True
196 
197 
199  """Representation of a Broadlink SP1 switch."""
200 
201  _attr_has_entity_name = True
202 
203  def __init__(self, device):
204  """Initialize the switch."""
205  super().__init__(device, 1, 0)
206  self._attr_unique_id_attr_unique_id = self._device_device.unique_id
207 
208  async def _async_send_packet(self, packet):
209  """Send a packet to the device."""
210  device = self._device_device
211 
212  try:
213  await device.async_request(device.api.set_power, packet)
214  except (BroadlinkException, OSError) as err:
215  _LOGGER.error("Failed to send packet: %s", err)
216  return False
217  return True
218 
219 
221  """Representation of a Broadlink SP2 switch."""
222 
223  _attr_assumed_state = False
224  _attr_has_entity_name = True
225  _attr_name = None
226 
227  def __init__(self, device, *args, **kwargs):
228  """Initialize the switch."""
229  super().__init__(device, *args, **kwargs)
230  self._attr_is_on_attr_is_on_attr_is_on = self._coordinator_coordinator.data["pwr"]
231 
232  def _update_state(self, data):
233  """Update the state of the entity."""
234  self._attr_is_on_attr_is_on_attr_is_on = data["pwr"]
235 
236 
238  """Representation of a Broadlink MP1 slot."""
239 
240  _attr_assumed_state = False
241  _attr_has_entity_name = True
242 
243  def __init__(self, device, slot):
244  """Initialize the switch."""
245  super().__init__(device, 1, 0)
246  self._slot_slot = slot
247  self._attr_is_on_attr_is_on_attr_is_on = self._coordinator_coordinator.data[f"s{slot}"]
248  self._attr_name_attr_name = f"S{slot}"
249  self._attr_unique_id_attr_unique_id = f"{device.unique_id}-s{slot}"
250 
251  def _update_state(self, data):
252  """Update the state of the entity."""
253  self._attr_is_on_attr_is_on_attr_is_on = data[f"s{self._slot}"]
254 
255  async def _async_send_packet(self, packet):
256  """Send a packet to the device."""
257  device = self._device_device
258 
259  try:
260  await device.async_request(device.api.set_power, self._slot_slot, packet)
261  except (BroadlinkException, OSError) as err:
262  _LOGGER.error("Failed to send packet: %s", err)
263  return False
264  return True
265 
266 
268  """Representation of a Broadlink BG1 slot."""
269 
270  _attr_assumed_state = False
271  _attr_has_entity_name = True
272 
273  def __init__(self, device, slot):
274  """Initialize the switch."""
275  super().__init__(device, 1, 0)
276  self._slot_slot = slot
277  self._attr_is_on_attr_is_on_attr_is_on = self._coordinator_coordinator.data[f"pwr{slot}"]
278 
279  self._attr_name_attr_name = f"S{slot}"
280  self._attr_device_class_attr_device_class_attr_device_class = SwitchDeviceClass.OUTLET
281  self._attr_unique_id_attr_unique_id = f"{device.unique_id}-s{slot}"
282 
283  def _update_state(self, data):
284  """Update the state of the entity."""
285  self._attr_is_on_attr_is_on_attr_is_on = data[f"pwr{self._slot}"]
286 
287  async def _async_send_packet(self, packet):
288  """Send a packet to the device."""
289  device = self._device_device
290  state = {f"pwr{self._slot}": packet}
291 
292  try:
293  await device.async_request(device.api.set_state, **state)
294  except (BroadlinkException, OSError) as err:
295  _LOGGER.error("Failed to send packet: %s", err)
296  return False
297  return True