Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Support for Xiaomi Smart WiFi Socket and Smart Power Strip."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from dataclasses import dataclass
7 from functools import partial
8 import logging
9 from typing import Any
10 
11 from miio import AirConditioningCompanionV3, ChuangmiPlug, DeviceException, PowerStrip
12 from miio.powerstrip import PowerMode
13 import voluptuous as vol
14 
16  SwitchDeviceClass,
17  SwitchEntity,
18  SwitchEntityDescription,
19 )
20 from homeassistant.config_entries import ConfigEntry
21 from homeassistant.const import (
22  ATTR_ENTITY_ID,
23  ATTR_MODE,
24  ATTR_TEMPERATURE,
25  CONF_DEVICE,
26  CONF_HOST,
27  CONF_MODEL,
28  CONF_TOKEN,
29  EntityCategory,
30 )
31 from homeassistant.core import HomeAssistant, ServiceCall, callback
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 
35 from .const import (
36  CONF_FLOW_TYPE,
37  CONF_GATEWAY,
38  DOMAIN,
39  FEATURE_FLAGS_AIRFRESH,
40  FEATURE_FLAGS_AIRFRESH_A1,
41  FEATURE_FLAGS_AIRFRESH_T2017,
42  FEATURE_FLAGS_AIRFRESH_VA4,
43  FEATURE_FLAGS_AIRHUMIDIFIER,
44  FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
45  FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
46  FEATURE_FLAGS_AIRHUMIDIFIER_MJSSQ,
47  FEATURE_FLAGS_AIRPURIFIER_2S,
48  FEATURE_FLAGS_AIRPURIFIER_3C,
49  FEATURE_FLAGS_AIRPURIFIER_4,
50  FEATURE_FLAGS_AIRPURIFIER_4_LITE,
51  FEATURE_FLAGS_AIRPURIFIER_MIIO,
52  FEATURE_FLAGS_AIRPURIFIER_MIOT,
53  FEATURE_FLAGS_AIRPURIFIER_PRO,
54  FEATURE_FLAGS_AIRPURIFIER_PRO_V7,
55  FEATURE_FLAGS_AIRPURIFIER_V1,
56  FEATURE_FLAGS_AIRPURIFIER_V3,
57  FEATURE_FLAGS_AIRPURIFIER_ZA1,
58  FEATURE_FLAGS_FAN,
59  FEATURE_FLAGS_FAN_1C,
60  FEATURE_FLAGS_FAN_P5,
61  FEATURE_FLAGS_FAN_P9,
62  FEATURE_FLAGS_FAN_P10_P11_P18,
63  FEATURE_FLAGS_FAN_ZA5,
64  FEATURE_SET_ANION,
65  FEATURE_SET_AUTO_DETECT,
66  FEATURE_SET_BUZZER,
67  FEATURE_SET_CHILD_LOCK,
68  FEATURE_SET_CLEAN,
69  FEATURE_SET_DISPLAY,
70  FEATURE_SET_DRY,
71  FEATURE_SET_IONIZER,
72  FEATURE_SET_LEARN_MODE,
73  FEATURE_SET_LED,
74  FEATURE_SET_PTC,
75  KEY_COORDINATOR,
76  KEY_DEVICE,
77  MODEL_AIRFRESH_A1,
78  MODEL_AIRFRESH_T2017,
79  MODEL_AIRFRESH_VA2,
80  MODEL_AIRFRESH_VA4,
81  MODEL_AIRHUMIDIFIER_CA1,
82  MODEL_AIRHUMIDIFIER_CA4,
83  MODEL_AIRHUMIDIFIER_CB1,
84  MODEL_AIRPURIFIER_2H,
85  MODEL_AIRPURIFIER_2S,
86  MODEL_AIRPURIFIER_3C,
87  MODEL_AIRPURIFIER_3C_REV_A,
88  MODEL_AIRPURIFIER_4,
89  MODEL_AIRPURIFIER_4_LITE_RMA1,
90  MODEL_AIRPURIFIER_4_LITE_RMB1,
91  MODEL_AIRPURIFIER_4_PRO,
92  MODEL_AIRPURIFIER_PRO,
93  MODEL_AIRPURIFIER_PRO_V7,
94  MODEL_AIRPURIFIER_V1,
95  MODEL_AIRPURIFIER_V3,
96  MODEL_AIRPURIFIER_ZA1,
97  MODEL_FAN_1C,
98  MODEL_FAN_P5,
99  MODEL_FAN_P9,
100  MODEL_FAN_P10,
101  MODEL_FAN_P11,
102  MODEL_FAN_P18,
103  MODEL_FAN_ZA1,
104  MODEL_FAN_ZA3,
105  MODEL_FAN_ZA4,
106  MODEL_FAN_ZA5,
107  MODELS_FAN,
108  MODELS_HUMIDIFIER,
109  MODELS_HUMIDIFIER_MJJSQ,
110  MODELS_PURIFIER_MIIO,
111  MODELS_PURIFIER_MIOT,
112  SERVICE_SET_POWER_MODE,
113  SERVICE_SET_POWER_PRICE,
114  SERVICE_SET_WIFI_LED_OFF,
115  SERVICE_SET_WIFI_LED_ON,
116  SUCCESS,
117 )
118 from .entity import XiaomiCoordinatedMiioEntity, XiaomiGatewayDevice, XiaomiMiioEntity
119 from .typing import ServiceMethodDetails
120 
121 _LOGGER = logging.getLogger(__name__)
122 
123 DEFAULT_NAME = "Xiaomi Miio Switch"
124 DATA_KEY = "switch.xiaomi_miio"
125 
126 MODEL_POWER_STRIP_V2 = "zimi.powerstrip.v2"
127 MODEL_PLUG_V3 = "chuangmi.plug.v3"
128 
129 KEY_CHANNEL = "channel"
130 GATEWAY_SWITCH_VARS = {
131  "status_ch0": {KEY_CHANNEL: 0},
132  "status_ch1": {KEY_CHANNEL: 1},
133  "status_ch2": {KEY_CHANNEL: 2},
134 }
135 
136 
137 ATTR_AUTO_DETECT = "auto_detect"
138 ATTR_BUZZER = "buzzer"
139 ATTR_CHILD_LOCK = "child_lock"
140 ATTR_CLEAN = "clean_mode"
141 ATTR_DISPLAY = "display"
142 ATTR_DRY = "dry"
143 ATTR_LEARN_MODE = "learn_mode"
144 ATTR_LED = "led"
145 ATTR_IONIZER = "ionizer"
146 ATTR_ANION = "anion"
147 ATTR_LOAD_POWER = "load_power"
148 ATTR_MODEL = "model"
149 ATTR_POWER = "power"
150 ATTR_POWER_MODE = "power_mode"
151 ATTR_POWER_PRICE = "power_price"
152 ATTR_PRICE = "price"
153 ATTR_PTC = "ptc"
154 ATTR_WIFI_LED = "wifi_led"
155 
156 FEATURE_SET_POWER_MODE = 1
157 FEATURE_SET_WIFI_LED = 2
158 FEATURE_SET_POWER_PRICE = 4
159 
160 FEATURE_FLAGS_GENERIC = 0
161 
162 FEATURE_FLAGS_POWER_STRIP_V1 = (
163  FEATURE_SET_POWER_MODE | FEATURE_SET_WIFI_LED | FEATURE_SET_POWER_PRICE
164 )
165 
166 FEATURE_FLAGS_POWER_STRIP_V2 = FEATURE_SET_WIFI_LED | FEATURE_SET_POWER_PRICE
167 
168 FEATURE_FLAGS_PLUG_V3 = FEATURE_SET_WIFI_LED
169 
170 SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
171 
172 SERVICE_SCHEMA_POWER_MODE = SERVICE_SCHEMA.extend(
173  {vol.Required(ATTR_MODE): vol.All(vol.In(["green", "normal"]))}
174 )
175 
176 SERVICE_SCHEMA_POWER_PRICE = SERVICE_SCHEMA.extend(
177  {vol.Required(ATTR_PRICE): cv.positive_float}
178 )
179 
180 SERVICE_TO_METHOD = {
181  SERVICE_SET_WIFI_LED_ON: ServiceMethodDetails(method="async_set_wifi_led_on"),
182  SERVICE_SET_WIFI_LED_OFF: ServiceMethodDetails(method="async_set_wifi_led_off"),
183  SERVICE_SET_POWER_MODE: ServiceMethodDetails(
184  method="async_set_power_mode",
185  schema=SERVICE_SCHEMA_POWER_MODE,
186  ),
187  SERVICE_SET_POWER_PRICE: ServiceMethodDetails(
188  method="async_set_power_price",
189  schema=SERVICE_SCHEMA_POWER_PRICE,
190  ),
191 }
192 
193 MODEL_TO_FEATURES_MAP = {
194  MODEL_AIRFRESH_A1: FEATURE_FLAGS_AIRFRESH_A1,
195  MODEL_AIRFRESH_VA2: FEATURE_FLAGS_AIRFRESH,
196  MODEL_AIRFRESH_VA4: FEATURE_FLAGS_AIRFRESH_VA4,
197  MODEL_AIRFRESH_T2017: FEATURE_FLAGS_AIRFRESH_T2017,
198  MODEL_AIRHUMIDIFIER_CA1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
199  MODEL_AIRHUMIDIFIER_CA4: FEATURE_FLAGS_AIRHUMIDIFIER_CA4,
200  MODEL_AIRHUMIDIFIER_CB1: FEATURE_FLAGS_AIRHUMIDIFIER_CA_AND_CB,
201  MODEL_AIRPURIFIER_2H: FEATURE_FLAGS_AIRPURIFIER_2S,
202  MODEL_AIRPURIFIER_2S: FEATURE_FLAGS_AIRPURIFIER_2S,
203  MODEL_AIRPURIFIER_3C: FEATURE_FLAGS_AIRPURIFIER_3C,
204  MODEL_AIRPURIFIER_3C_REV_A: FEATURE_FLAGS_AIRPURIFIER_3C,
205  MODEL_AIRPURIFIER_PRO: FEATURE_FLAGS_AIRPURIFIER_PRO,
206  MODEL_AIRPURIFIER_PRO_V7: FEATURE_FLAGS_AIRPURIFIER_PRO_V7,
207  MODEL_AIRPURIFIER_V1: FEATURE_FLAGS_AIRPURIFIER_V1,
208  MODEL_AIRPURIFIER_V3: FEATURE_FLAGS_AIRPURIFIER_V3,
209  MODEL_AIRPURIFIER_4_LITE_RMA1: FEATURE_FLAGS_AIRPURIFIER_4_LITE,
210  MODEL_AIRPURIFIER_4_LITE_RMB1: FEATURE_FLAGS_AIRPURIFIER_4_LITE,
211  MODEL_AIRPURIFIER_4: FEATURE_FLAGS_AIRPURIFIER_4,
212  MODEL_AIRPURIFIER_4_PRO: FEATURE_FLAGS_AIRPURIFIER_4,
213  MODEL_AIRPURIFIER_ZA1: FEATURE_FLAGS_AIRPURIFIER_ZA1,
214  MODEL_FAN_1C: FEATURE_FLAGS_FAN_1C,
215  MODEL_FAN_P10: FEATURE_FLAGS_FAN_P10_P11_P18,
216  MODEL_FAN_P11: FEATURE_FLAGS_FAN_P10_P11_P18,
217  MODEL_FAN_P18: FEATURE_FLAGS_FAN_P10_P11_P18,
218  MODEL_FAN_P5: FEATURE_FLAGS_FAN_P5,
219  MODEL_FAN_P9: FEATURE_FLAGS_FAN_P9,
220  MODEL_FAN_ZA1: FEATURE_FLAGS_FAN,
221  MODEL_FAN_ZA3: FEATURE_FLAGS_FAN,
222  MODEL_FAN_ZA4: FEATURE_FLAGS_FAN,
223  MODEL_FAN_ZA5: FEATURE_FLAGS_FAN_ZA5,
224 }
225 
226 
227 @dataclass(frozen=True, kw_only=True)
229  """A class that describes switch entities."""
230 
231  feature: int
232  method_on: str
233  method_off: str
234 
235  available_with_device_off: bool = True
236 
237 
238 SWITCH_TYPES = (
240  key=ATTR_BUZZER,
241  feature=FEATURE_SET_BUZZER,
242  translation_key=ATTR_BUZZER,
243  icon="mdi:volume-high",
244  method_on="async_set_buzzer_on",
245  method_off="async_set_buzzer_off",
246  entity_category=EntityCategory.CONFIG,
247  ),
249  key=ATTR_CHILD_LOCK,
250  feature=FEATURE_SET_CHILD_LOCK,
251  translation_key=ATTR_CHILD_LOCK,
252  icon="mdi:lock",
253  method_on="async_set_child_lock_on",
254  method_off="async_set_child_lock_off",
255  entity_category=EntityCategory.CONFIG,
256  ),
258  key=ATTR_DISPLAY,
259  feature=FEATURE_SET_DISPLAY,
260  translation_key=ATTR_DISPLAY,
261  icon="mdi:led-outline",
262  method_on="async_set_display_on",
263  method_off="async_set_display_off",
264  entity_category=EntityCategory.CONFIG,
265  ),
267  key=ATTR_DRY,
268  feature=FEATURE_SET_DRY,
269  translation_key=ATTR_DRY,
270  icon="mdi:hair-dryer",
271  method_on="async_set_dry_on",
272  method_off="async_set_dry_off",
273  entity_category=EntityCategory.CONFIG,
274  ),
276  key=ATTR_CLEAN,
277  feature=FEATURE_SET_CLEAN,
278  translation_key=ATTR_CLEAN,
279  icon="mdi:shimmer",
280  method_on="async_set_clean_on",
281  method_off="async_set_clean_off",
282  available_with_device_off=False,
283  entity_category=EntityCategory.CONFIG,
284  ),
286  key=ATTR_LED,
287  feature=FEATURE_SET_LED,
288  translation_key=ATTR_LED,
289  icon="mdi:led-outline",
290  method_on="async_set_led_on",
291  method_off="async_set_led_off",
292  entity_category=EntityCategory.CONFIG,
293  ),
295  key=ATTR_LEARN_MODE,
296  feature=FEATURE_SET_LEARN_MODE,
297  translation_key=ATTR_LEARN_MODE,
298  icon="mdi:school-outline",
299  method_on="async_set_learn_mode_on",
300  method_off="async_set_learn_mode_off",
301  entity_category=EntityCategory.CONFIG,
302  ),
304  key=ATTR_AUTO_DETECT,
305  feature=FEATURE_SET_AUTO_DETECT,
306  translation_key=ATTR_AUTO_DETECT,
307  method_on="async_set_auto_detect_on",
308  method_off="async_set_auto_detect_off",
309  entity_category=EntityCategory.CONFIG,
310  ),
312  key=ATTR_IONIZER,
313  feature=FEATURE_SET_IONIZER,
314  translation_key=ATTR_IONIZER,
315  icon="mdi:shimmer",
316  method_on="async_set_ionizer_on",
317  method_off="async_set_ionizer_off",
318  entity_category=EntityCategory.CONFIG,
319  ),
321  key=ATTR_ANION,
322  feature=FEATURE_SET_ANION,
323  translation_key=ATTR_ANION,
324  icon="mdi:shimmer",
325  method_on="async_set_anion_on",
326  method_off="async_set_anion_off",
327  entity_category=EntityCategory.CONFIG,
328  ),
330  key=ATTR_PTC,
331  feature=FEATURE_SET_PTC,
332  translation_key=ATTR_PTC,
333  icon="mdi:radiator",
334  method_on="async_set_ptc_on",
335  method_off="async_set_ptc_off",
336  entity_category=EntityCategory.CONFIG,
337  ),
338 )
339 
340 
342  hass: HomeAssistant,
343  config_entry: ConfigEntry,
344  async_add_entities: AddEntitiesCallback,
345 ) -> None:
346  """Set up the switch from a config entry."""
347  model = config_entry.data[CONF_MODEL]
348  if model in (*MODELS_HUMIDIFIER, *MODELS_FAN):
349  await async_setup_coordinated_entry(hass, config_entry, async_add_entities)
350  else:
351  await async_setup_other_entry(hass, config_entry, async_add_entities)
352 
353 
354 async def async_setup_coordinated_entry(hass, config_entry, async_add_entities):
355  """Set up the coordinated switch from a config entry."""
356  model = config_entry.data[CONF_MODEL]
357  unique_id = config_entry.unique_id
358  device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
359  coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
360 
361  if DATA_KEY not in hass.data:
362  hass.data[DATA_KEY] = {}
363 
364  device_features = 0
365 
366  if model in MODEL_TO_FEATURES_MAP:
367  device_features = MODEL_TO_FEATURES_MAP[model]
368  elif model in MODELS_HUMIDIFIER_MJJSQ:
369  device_features = FEATURE_FLAGS_AIRHUMIDIFIER_MJSSQ
370  elif model in MODELS_HUMIDIFIER:
371  device_features = FEATURE_FLAGS_AIRHUMIDIFIER
372  elif model in MODELS_PURIFIER_MIIO:
373  device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO
374  elif model in MODELS_PURIFIER_MIOT:
375  device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT
376 
379  device,
380  config_entry,
381  f"{description.key}_{unique_id}",
382  coordinator,
383  description,
384  )
385  for description in SWITCH_TYPES
386  if description.feature & device_features
387  )
388 
389 
390 async def async_setup_other_entry(hass, config_entry, async_add_entities):
391  """Set up the other type switch from a config entry."""
392  entities = []
393  host = config_entry.data[CONF_HOST]
394  token = config_entry.data[CONF_TOKEN]
395  name = config_entry.title
396  model = config_entry.data[CONF_MODEL]
397  unique_id = config_entry.unique_id
398  if config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY:
399  gateway = hass.data[DOMAIN][config_entry.entry_id][CONF_GATEWAY]
400  # Gateway sub devices
401  sub_devices = gateway.devices
402  for sub_device in sub_devices.values():
403  if sub_device.device_type != "Switch":
404  continue
405  coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR][
406  sub_device.sid
407  ]
408  switch_variables = set(sub_device.status) & set(GATEWAY_SWITCH_VARS)
409  if switch_variables:
410  entities.extend(
411  [
413  coordinator, sub_device, config_entry, variable
414  )
415  for variable in switch_variables
416  ]
417  )
418 
419  if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE or (
420  config_entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY
421  and model == "lumi.acpartner.v3"
422  ):
423  if DATA_KEY not in hass.data:
424  hass.data[DATA_KEY] = {}
425 
426  _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
427 
428  if model in ["chuangmi.plug.v1", "chuangmi.plug.v3", "chuangmi.plug.hmi208"]:
429  plug = ChuangmiPlug(host, token, model=model)
430 
431  # The device has two switchable channels (mains and a USB port).
432  # A switch device per channel will be created.
433  for channel_usb in (True, False):
434  if channel_usb:
435  unique_id_ch = f"{unique_id}-USB"
436  else:
437  unique_id_ch = f"{unique_id}-mains"
438  device = ChuangMiPlugSwitch(
439  name, plug, config_entry, unique_id_ch, channel_usb
440  )
441  entities.append(device)
442  hass.data[DATA_KEY][host] = device
443  elif model in ["qmi.powerstrip.v1", "zimi.powerstrip.v2"]:
444  plug = PowerStrip(host, token, model=model)
445  device = XiaomiPowerStripSwitch(name, plug, config_entry, unique_id)
446  entities.append(device)
447  hass.data[DATA_KEY][host] = device
448  elif model in [
449  "chuangmi.plug.m1",
450  "chuangmi.plug.m3",
451  "chuangmi.plug.v2",
452  "chuangmi.plug.hmi205",
453  "chuangmi.plug.hmi206",
454  ]:
455  plug = ChuangmiPlug(host, token, model=model)
456  device = XiaomiPlugGenericSwitch(name, plug, config_entry, unique_id)
457  entities.append(device)
458  hass.data[DATA_KEY][host] = device
459  elif model in ["lumi.acpartner.v3"]:
460  plug = AirConditioningCompanionV3(host, token)
462  name, plug, config_entry, unique_id
463  )
464  entities.append(device)
465  hass.data[DATA_KEY][host] = device
466  else:
467  _LOGGER.error(
468  (
469  "Unsupported device found! Please create an issue at "
470  "https://github.com/rytilahti/python-miio/issues "
471  "and provide the following data: %s"
472  ),
473  model,
474  )
475 
476  async def async_service_handler(service: ServiceCall) -> None:
477  """Map services to methods on XiaomiPlugGenericSwitch."""
478  method = SERVICE_TO_METHOD[service.service]
479  params = {
480  key: value
481  for key, value in service.data.items()
482  if key != ATTR_ENTITY_ID
483  }
484  if entity_ids := service.data.get(ATTR_ENTITY_ID):
485  devices = [
486  device
487  for device in hass.data[DATA_KEY].values()
488  if device.entity_id in entity_ids
489  ]
490  else:
491  devices = hass.data[DATA_KEY].values()
492 
493  update_tasks = []
494  for device in devices:
495  if not hasattr(device, method.method):
496  continue
497  await getattr(device, method.method)(**params)
498  update_tasks.append(
499  asyncio.create_task(device.async_update_ha_state(True))
500  )
501 
502  if update_tasks:
503  await asyncio.wait(update_tasks)
504 
505  for plug_service, method in SERVICE_TO_METHOD.items():
506  schema = method.schema or SERVICE_SCHEMA
507  hass.services.async_register(
508  DOMAIN, plug_service, async_service_handler, schema=schema
509  )
510 
511  async_add_entities(entities)
512 
513 
515  """Representation of a Xiaomi Plug Generic."""
516 
517  entity_description: XiaomiMiioSwitchDescription
518 
519  def __init__(self, device, entry, unique_id, coordinator, description):
520  """Initialize the plug switch."""
521  super().__init__(device, entry, unique_id, coordinator)
522 
523  self._attr_is_on_attr_is_on = self._extract_value_from_attribute(
524  self.coordinator.data, description.key
525  )
526  self.entity_descriptionentity_description = description
527 
528  @callback
530  """Fetch state from the device."""
531  # On state change the device doesn't provide the new state immediately.
532  self._attr_is_on_attr_is_on = self._extract_value_from_attribute(
533  self.coordinator.data, self.entity_descriptionentity_description.key
534  )
535  self.async_write_ha_stateasync_write_ha_state()
536 
537  @property
538  def available(self) -> bool:
539  """Return true when state is known."""
540  if (
541  super().available
542  and not self.coordinator.data.is_on
543  and not self.entity_descriptionentity_description.available_with_device_off
544  ):
545  return False
546  return super().available
547 
548  async def async_turn_on(self, **kwargs: Any) -> None:
549  """Turn on an option of the miio device."""
550  method = getattr(self, self.entity_descriptionentity_description.method_on)
551  if await method():
552  # Write state back to avoid switch flips with a slow response
553  self._attr_is_on_attr_is_on = True
554  self.async_write_ha_stateasync_write_ha_state()
555 
556  async def async_turn_off(self, **kwargs: Any) -> None:
557  """Turn off an option of the miio device."""
558  method = getattr(self, self.entity_descriptionentity_description.method_off)
559  if await method():
560  # Write state back to avoid switch flips with a slow response
561  self._attr_is_on_attr_is_on = False
562  self.async_write_ha_stateasync_write_ha_state()
563 
564  async def async_set_buzzer_on(self) -> bool:
565  """Turn the buzzer on."""
566  return await self._try_command(
567  "Turning the buzzer of the miio device on failed.",
568  self._device.set_buzzer,
569  True,
570  )
571 
572  async def async_set_buzzer_off(self) -> bool:
573  """Turn the buzzer off."""
574  return await self._try_command(
575  "Turning the buzzer of the miio device off failed.",
576  self._device.set_buzzer,
577  False,
578  )
579 
580  async def async_set_child_lock_on(self) -> bool:
581  """Turn the child lock on."""
582  return await self._try_command(
583  "Turning the child lock of the miio device on failed.",
584  self._device.set_child_lock,
585  True,
586  )
587 
588  async def async_set_child_lock_off(self) -> bool:
589  """Turn the child lock off."""
590  return await self._try_command(
591  "Turning the child lock of the miio device off failed.",
592  self._device.set_child_lock,
593  False,
594  )
595 
596  async def async_set_display_on(self) -> bool:
597  """Turn the display on."""
598  return await self._try_command(
599  "Turning the display of the miio device on failed.",
600  self._device.set_display,
601  True,
602  )
603 
604  async def async_set_display_off(self) -> bool:
605  """Turn the display off."""
606  return await self._try_command(
607  "Turning the display of the miio device off failed.",
608  self._device.set_display,
609  False,
610  )
611 
612  async def async_set_dry_on(self) -> bool:
613  """Turn the dry mode on."""
614  return await self._try_command(
615  "Turning the dry mode of the miio device on failed.",
616  self._device.set_dry,
617  True,
618  )
619 
620  async def async_set_dry_off(self) -> bool:
621  """Turn the dry mode off."""
622  return await self._try_command(
623  "Turning the dry mode of the miio device off failed.",
624  self._device.set_dry,
625  False,
626  )
627 
628  async def async_set_clean_on(self) -> bool:
629  """Turn the dry mode on."""
630  return await self._try_command(
631  "Turning the clean mode of the miio device on failed.",
632  self._device.set_clean_mode,
633  True,
634  )
635 
636  async def async_set_clean_off(self) -> bool:
637  """Turn the dry mode off."""
638  return await self._try_command(
639  "Turning the clean mode of the miio device off failed.",
640  self._device.set_clean_mode,
641  False,
642  )
643 
644  async def async_set_led_on(self) -> bool:
645  """Turn the led on."""
646  return await self._try_command(
647  "Turning the led of the miio device on failed.",
648  self._device.set_led,
649  True,
650  )
651 
652  async def async_set_led_off(self) -> bool:
653  """Turn the led off."""
654  return await self._try_command(
655  "Turning the led of the miio device off failed.",
656  self._device.set_led,
657  False,
658  )
659 
660  async def async_set_learn_mode_on(self) -> bool:
661  """Turn the learn mode on."""
662  return await self._try_command(
663  "Turning the learn mode of the miio device on failed.",
664  self._device.set_learn_mode,
665  True,
666  )
667 
668  async def async_set_learn_mode_off(self) -> bool:
669  """Turn the learn mode off."""
670  return await self._try_command(
671  "Turning the learn mode of the miio device off failed.",
672  self._device.set_learn_mode,
673  False,
674  )
675 
676  async def async_set_auto_detect_on(self) -> bool:
677  """Turn auto detect on."""
678  return await self._try_command(
679  "Turning auto detect of the miio device on failed.",
680  self._device.set_auto_detect,
681  True,
682  )
683 
684  async def async_set_auto_detect_off(self) -> bool:
685  """Turn auto detect off."""
686  return await self._try_command(
687  "Turning auto detect of the miio device off failed.",
688  self._device.set_auto_detect,
689  False,
690  )
691 
692  async def async_set_ionizer_on(self) -> bool:
693  """Turn ionizer on."""
694  return await self._try_command(
695  "Turning ionizer of the miio device on failed.",
696  self._device.set_ionizer,
697  True,
698  )
699 
700  async def async_set_ionizer_off(self) -> bool:
701  """Turn ionizer off."""
702  return await self._try_command(
703  "Turning ionizer of the miio device off failed.",
704  self._device.set_ionizer,
705  False,
706  )
707 
708  async def async_set_anion_on(self) -> bool:
709  """Turn ionizer on."""
710  return await self._try_command(
711  "Turning ionizer of the miio device on failed.",
712  self._device.set_anion,
713  True,
714  )
715 
716  async def async_set_anion_off(self) -> bool:
717  """Turn ionizer off."""
718  return await self._try_command(
719  "Turning ionizer of the miio device off failed.",
720  self._device.set_anion,
721  False,
722  )
723 
724  async def async_set_ptc_on(self) -> bool:
725  """Turn ionizer on."""
726  return await self._try_command(
727  "Turning ionizer of the miio device on failed.",
728  self._device.set_ptc,
729  True,
730  )
731 
732  async def async_set_ptc_off(self) -> bool:
733  """Turn ionizer off."""
734  return await self._try_command(
735  "Turning ionizer of the miio device off failed.",
736  self._device.set_ptc,
737  False,
738  )
739 
740 
742  """Representation of a XiaomiGatewaySwitch."""
743 
744  _attr_device_class = SwitchDeviceClass.SWITCH
745 
746  def __init__(self, coordinator, sub_device, entry, variable):
747  """Initialize the XiaomiSensor."""
748  super().__init__(coordinator, sub_device, entry)
749  self._channel_channel = GATEWAY_SWITCH_VARS[variable][KEY_CHANNEL]
750  self._data_key_data_key = f"status_ch{self._channel}"
751  self._unique_id_unique_id_unique_id = f"{sub_device.sid}-ch{self._channel}"
752  self._name_name_name = f"{sub_device.name} ch{self._channel} ({sub_device.sid})"
753 
754  @property
755  def is_on(self):
756  """Return true if switch is on."""
757  return self._sub_device_sub_device.status[self._data_key_data_key] == "on"
758 
759  async def async_turn_on(self, **kwargs: Any) -> None:
760  """Turn the switch on."""
761  await self.hasshass.async_add_executor_job(self._sub_device_sub_device.on, self._channel_channel)
762 
763  async def async_turn_off(self, **kwargs: Any) -> None:
764  """Turn the switch off."""
765  await self.hasshass.async_add_executor_job(self._sub_device_sub_device.off, self._channel_channel)
766 
767  async def async_toggle(self, **kwargs: Any) -> None:
768  """Toggle the switch."""
769  await self.hasshass.async_add_executor_job(self._sub_device_sub_device.toggle, self._channel_channel)
770 
771 
773  """Representation of a Xiaomi Plug Generic."""
774 
775  def __init__(self, name, device, entry, unique_id):
776  """Initialize the plug switch."""
777  super().__init__(name, device, entry, unique_id)
778 
779  self._icon_icon = "mdi:power-socket"
780  self._available_available_available = False
781  self._state_state = None
782  self._state_attrs_state_attrs = {ATTR_TEMPERATURE: None, ATTR_MODEL: self._model_model}
783  self._device_features_device_features = FEATURE_FLAGS_GENERIC
784  self._skip_update_skip_update = False
785 
786  @property
787  def icon(self):
788  """Return the icon to use for device if any."""
789  return self._icon_icon
790 
791  @property
792  def available(self):
793  """Return true when state is known."""
794  return self._available_available_available
795 
796  @property
798  """Return the state attributes of the device."""
799  return self._state_attrs_state_attrs
800 
801  @property
802  def is_on(self):
803  """Return true if switch is on."""
804  return self._state_state
805 
806  async def _try_command(self, mask_error, func, *args, **kwargs):
807  """Call a plug command handling error messages."""
808  try:
809  result = await self.hasshass.async_add_executor_job(
810  partial(func, *args, **kwargs)
811  )
812  except DeviceException as exc:
813  if self._available_available_available:
814  _LOGGER.error(mask_error, exc)
815  self._available_available_available = False
816 
817  return False
818 
819  _LOGGER.debug("Response received from plug: %s", result)
820 
821  # The Chuangmi Plug V3 returns 0 on success on usb_on/usb_off.
822  if func in ["usb_on", "usb_off"] and result == 0:
823  return True
824 
825  return result == SUCCESS
826 
827  async def async_turn_on(self, **kwargs: Any) -> None:
828  """Turn the plug on."""
829  result = await self._try_command_try_command("Turning the plug on failed", self._device_device.on)
830 
831  if result:
832  self._state_state = True
833  self._skip_update_skip_update = True
834 
835  async def async_turn_off(self, **kwargs: Any) -> None:
836  """Turn the plug off."""
837  result = await self._try_command_try_command(
838  "Turning the plug off failed", self._device_device.off
839  )
840 
841  if result:
842  self._state_state = False
843  self._skip_update_skip_update = True
844 
845  async def async_update(self) -> None:
846  """Fetch state from the device."""
847  # On state change the device doesn't provide the new state immediately.
848  if self._skip_update_skip_update:
849  self._skip_update_skip_update = False
850  return
851 
852  try:
853  state = await self.hasshass.async_add_executor_job(self._device_device.status)
854  _LOGGER.debug("Got new state: %s", state)
855 
856  self._available_available_available = True
857  self._state_state = state.is_on
858  self._state_attrs_state_attrs[ATTR_TEMPERATURE] = state.temperature
859 
860  except DeviceException as ex:
861  if self._available_available_available:
862  self._available_available_available = False
863  _LOGGER.error("Got exception while fetching the state: %s", ex)
864 
865  async def async_set_wifi_led_on(self):
866  """Turn the wifi led on."""
867  if self._device_features_device_features & FEATURE_SET_WIFI_LED == 0:
868  return
869 
870  await self._try_command_try_command(
871  "Turning the wifi led on failed", self._device_device.set_wifi_led, True
872  )
873 
874  async def async_set_wifi_led_off(self):
875  """Turn the wifi led on."""
876  if self._device_features_device_features & FEATURE_SET_WIFI_LED == 0:
877  return
878 
879  await self._try_command_try_command(
880  "Turning the wifi led off failed", self._device_device.set_wifi_led, False
881  )
882 
883  async def async_set_power_price(self, price: int):
884  """Set the power price."""
885  if self._device_features_device_features & FEATURE_SET_POWER_PRICE == 0:
886  return
887 
888  await self._try_command_try_command(
889  "Setting the power price of the power strip failed",
890  self._device_device.set_power_price,
891  price,
892  )
893 
894 
896  """Representation of a Xiaomi Power Strip."""
897 
898  def __init__(self, name, plug, model, unique_id):
899  """Initialize the plug switch."""
900  super().__init__(name, plug, model, unique_id)
901 
902  if self._model_model_model == MODEL_POWER_STRIP_V2:
903  self._device_features_device_features_device_features = FEATURE_FLAGS_POWER_STRIP_V2
904  else:
905  self._device_features_device_features_device_features = FEATURE_FLAGS_POWER_STRIP_V1
906 
907  self._state_attrs_state_attrs[ATTR_LOAD_POWER] = None
908 
909  if self._device_features_device_features_device_features & FEATURE_SET_POWER_MODE == 1:
910  self._state_attrs_state_attrs[ATTR_POWER_MODE] = None
911 
912  if self._device_features_device_features_device_features & FEATURE_SET_WIFI_LED == 1:
913  self._state_attrs_state_attrs[ATTR_WIFI_LED] = None
914 
915  if self._device_features_device_features_device_features & FEATURE_SET_POWER_PRICE == 1:
916  self._state_attrs_state_attrs[ATTR_POWER_PRICE] = None
917 
918  async def async_update(self) -> None:
919  """Fetch state from the device."""
920  # On state change the device doesn't provide the new state immediately.
921  if self._skip_update_skip_update_skip_update:
922  self._skip_update_skip_update_skip_update = False
923  return
924 
925  try:
926  state = await self.hasshass.async_add_executor_job(self._device_device.status)
927  _LOGGER.debug("Got new state: %s", state)
928 
929  self._available_available_available_available = True
930  self._state_state_state = state.is_on
931  self._state_attrs_state_attrs.update(
932  {ATTR_TEMPERATURE: state.temperature, ATTR_LOAD_POWER: state.load_power}
933  )
934 
935  if self._device_features_device_features_device_features & FEATURE_SET_POWER_MODE == 1 and state.mode:
936  self._state_attrs_state_attrs[ATTR_POWER_MODE] = state.mode.value
937 
938  if self._device_features_device_features_device_features & FEATURE_SET_WIFI_LED == 1 and state.wifi_led:
939  self._state_attrs_state_attrs[ATTR_WIFI_LED] = state.wifi_led
940 
941  if (
942  self._device_features_device_features_device_features & FEATURE_SET_POWER_PRICE == 1
943  and state.power_price
944  ):
945  self._state_attrs_state_attrs[ATTR_POWER_PRICE] = state.power_price
946 
947  except DeviceException as ex:
948  if self._available_available_available_available:
949  self._available_available_available_available = False
950  _LOGGER.error("Got exception while fetching the state: %s", ex)
951 
952  async def async_set_power_mode(self, mode: str):
953  """Set the power mode."""
954  if self._device_features_device_features_device_features & FEATURE_SET_POWER_MODE == 0:
955  return
956 
957  await self._try_command_try_command(
958  "Setting the power mode of the power strip failed",
959  self._device_device.set_power_mode,
960  PowerMode(mode),
961  )
962 
963 
965  """Representation of a Chuang Mi Plug V1 and V3."""
966 
967  def __init__(self, name, plug, entry, unique_id, channel_usb):
968  """Initialize the plug switch."""
969  name = f"{name} USB" if channel_usb else name
970 
971  if unique_id is not None and channel_usb:
972  unique_id = f"{unique_id}-usb"
973 
974  super().__init__(name, plug, entry, unique_id)
975  self._channel_usb_channel_usb = channel_usb
976 
977  if self._model_model_model == MODEL_PLUG_V3:
978  self._device_features_device_features_device_features = FEATURE_FLAGS_PLUG_V3
979  self._state_attrs_state_attrs[ATTR_WIFI_LED] = None
980  if self._channel_usb_channel_usb is False:
981  self._state_attrs_state_attrs[ATTR_LOAD_POWER] = None
982 
983  async def async_turn_on(self, **kwargs: Any) -> None:
984  """Turn a channel on."""
985  if self._channel_usb_channel_usb:
986  result = await self._try_command_try_command(
987  "Turning the plug on failed", self._device_device.usb_on
988  )
989  else:
990  result = await self._try_command_try_command(
991  "Turning the plug on failed", self._device_device.on
992  )
993 
994  if result:
995  self._state_state_state = True
996  self._skip_update_skip_update_skip_update = True
997 
998  async def async_turn_off(self, **kwargs: Any) -> None:
999  """Turn a channel off."""
1000  if self._channel_usb_channel_usb:
1001  result = await self._try_command_try_command(
1002  "Turning the plug off failed", self._device_device.usb_off
1003  )
1004  else:
1005  result = await self._try_command_try_command(
1006  "Turning the plug off failed", self._device_device.off
1007  )
1008 
1009  if result:
1010  self._state_state_state = False
1011  self._skip_update_skip_update_skip_update = True
1012 
1013  async def async_update(self) -> None:
1014  """Fetch state from the device."""
1015  # On state change the device doesn't provide the new state immediately.
1016  if self._skip_update_skip_update_skip_update:
1017  self._skip_update_skip_update_skip_update = False
1018  return
1019 
1020  try:
1021  state = await self.hasshass.async_add_executor_job(self._device_device.status)
1022  _LOGGER.debug("Got new state: %s", state)
1023 
1024  self._available_available_available_available = True
1025  if self._channel_usb_channel_usb:
1026  self._state_state_state = state.usb_power
1027  else:
1028  self._state_state_state = state.is_on
1029 
1030  self._state_attrs_state_attrs[ATTR_TEMPERATURE] = state.temperature
1031 
1032  if state.wifi_led:
1033  self._state_attrs_state_attrs[ATTR_WIFI_LED] = state.wifi_led
1034 
1035  if self._channel_usb_channel_usb is False and state.load_power:
1036  self._state_attrs_state_attrs[ATTR_LOAD_POWER] = state.load_power
1037 
1038  except DeviceException as ex:
1039  if self._available_available_available_available:
1040  self._available_available_available_available = False
1041  _LOGGER.error("Got exception while fetching the state: %s", ex)
1042 
1043 
1045  """Representation of a Xiaomi AirConditioning Companion."""
1046 
1047  def __init__(self, name, plug, model, unique_id):
1048  """Initialize the acpartner switch."""
1049  super().__init__(name, plug, model, unique_id)
1050 
1051  self._state_attrs_state_attrs.update({ATTR_TEMPERATURE: None, ATTR_LOAD_POWER: None})
1052 
1053  async def async_turn_on(self, **kwargs: Any) -> None:
1054  """Turn the socket on."""
1055  result = await self._try_command_try_command(
1056  "Turning the socket on failed", self._device_device.socket_on
1057  )
1058 
1059  if result:
1060  self._state_state_state = True
1061  self._skip_update_skip_update_skip_update = True
1062 
1063  async def async_turn_off(self, **kwargs: Any) -> None:
1064  """Turn the socket off."""
1065  result = await self._try_command_try_command(
1066  "Turning the socket off failed", self._device_device.socket_off
1067  )
1068 
1069  if result:
1070  self._state_state_state = False
1071  self._skip_update_skip_update_skip_update = True
1072 
1073  async def async_update(self) -> None:
1074  """Fetch state from the device."""
1075  # On state change the device doesn't provide the new state immediately.
1076  if self._skip_update_skip_update_skip_update:
1077  self._skip_update_skip_update_skip_update = False
1078  return
1079 
1080  try:
1081  state = await self.hasshass.async_add_executor_job(self._device_device.status)
1082  _LOGGER.debug("Got new state: %s", state)
1083 
1084  self._available_available_available_available = True
1085  self._state_state_state = state.power_socket == "on"
1086  self._state_attrs_state_attrs[ATTR_LOAD_POWER] = state.load_power
1087 
1088  except DeviceException as ex:
1089  if self._available_available_available_available:
1090  self._available_available_available_available = False
1091  _LOGGER.error("Got exception while fetching the state: %s", ex)
def __init__(self, name, plug, entry, unique_id, channel_usb)
Definition: switch.py:967
def __init__(self, coordinator, sub_device, entry, variable)
Definition: switch.py:746
def __init__(self, device, entry, unique_id, coordinator, description)
Definition: switch.py:519
def __init__(self, name, device, entry, unique_id)
Definition: switch.py:775
def _try_command(self, mask_error, func, *args, **kwargs)
Definition: switch.py:806
def __init__(self, name, plug, model, unique_id)
Definition: switch.py:898
IssData update(pyiss.ISS iss)
Definition: __init__.py:33
def async_setup_coordinated_entry(hass, config_entry, async_add_entities)
Definition: switch.py:354
def async_setup_other_entry(hass, config_entry, async_add_entities)
Definition: switch.py:390
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: switch.py:345