Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Switch platform for Enphase Envoy solar energy monitor."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable, Coroutine
6 from dataclasses import dataclass
7 from typing import Any
8 
9 from pyenphase import Envoy, EnvoyDryContactStatus, EnvoyEnpower
10 from pyenphase.const import SupportedFeatures
11 from pyenphase.models.dry_contacts import DryContactStatus
12 from pyenphase.models.tariff import EnvoyStorageSettings
13 
14 from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
15 from homeassistant.core import HomeAssistant
16 from homeassistant.helpers.device_registry import DeviceInfo
17 from homeassistant.helpers.entity_platform import AddEntitiesCallback
18 
19 from .const import DOMAIN
20 from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
21 from .entity import EnvoyBaseEntity
22 
23 
24 @dataclass(frozen=True, kw_only=True)
26  """Describes an Envoy Enpower switch entity."""
27 
28  value_fn: Callable[[EnvoyEnpower], bool]
29  turn_on_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]]
30  turn_off_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]]
31 
32 
33 @dataclass(frozen=True, kw_only=True)
35  """Describes an Envoy Enpower dry contact switch entity."""
36 
37  value_fn: Callable[[EnvoyDryContactStatus], bool]
38  turn_on_fn: Callable[[Envoy, str], Coroutine[Any, Any, dict[str, Any]]]
39  turn_off_fn: Callable[[Envoy, str], Coroutine[Any, Any, dict[str, Any]]]
40 
41 
42 @dataclass(frozen=True, kw_only=True)
44  """Describes an Envoy storage settings switch entity."""
45 
46  value_fn: Callable[[EnvoyStorageSettings], bool]
47  turn_on_fn: Callable[[Envoy], Awaitable[dict[str, Any]]]
48  turn_off_fn: Callable[[Envoy], Awaitable[dict[str, Any]]]
49 
50 
52  key="mains_admin_state",
53  translation_key="grid_enabled",
54  value_fn=lambda enpower: enpower.mains_admin_state == "closed",
55  turn_on_fn=lambda envoy: envoy.go_on_grid(),
56  turn_off_fn=lambda envoy: envoy.go_off_grid(),
57 )
58 
60  key="relay_status",
61  value_fn=lambda dry_contact: dry_contact.status == DryContactStatus.CLOSED,
62  turn_on_fn=lambda envoy, id: envoy.close_dry_contact(id),
63  turn_off_fn=lambda envoy, id: envoy.open_dry_contact(id),
64 )
65 
67  key="charge_from_grid",
68  translation_key="charge_from_grid",
69  value_fn=lambda storage_settings: storage_settings.charge_from_grid,
70  turn_on_fn=lambda envoy: envoy.enable_charge_from_grid(),
71  turn_off_fn=lambda envoy: envoy.disable_charge_from_grid(),
72 )
73 
74 
76  hass: HomeAssistant,
77  config_entry: EnphaseConfigEntry,
78  async_add_entities: AddEntitiesCallback,
79 ) -> None:
80  """Set up Enphase Envoy switch platform."""
81  coordinator = config_entry.runtime_data
82  envoy_data = coordinator.envoy.data
83  assert envoy_data is not None
84  entities: list[SwitchEntity] = []
85  if envoy_data.enpower:
86  entities.extend(
87  [
89  coordinator, ENPOWER_GRID_SWITCH, envoy_data.enpower
90  )
91  ]
92  )
93 
94  if envoy_data.dry_contact_status:
95  entities.extend(
96  EnvoyDryContactSwitchEntity(coordinator, RELAY_STATE_SWITCH, relay)
97  for relay in envoy_data.dry_contact_status
98  )
99 
100  if (
101  envoy_data.tariff
102  and envoy_data.tariff.storage_settings
103  and (coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE)
104  ):
105  entities.append(
107  coordinator, CHARGE_FROM_GRID_SWITCH, envoy_data.enpower
108  )
109  )
110 
111  async_add_entities(entities)
112 
113 
115  """Representation of an Enphase Enpower switch entity."""
116 
117  entity_description: EnvoyEnpowerSwitchEntityDescription
118 
119  def __init__(
120  self,
121  coordinator: EnphaseUpdateCoordinator,
122  description: EnvoyEnpowerSwitchEntityDescription,
123  enpower: EnvoyEnpower,
124  ) -> None:
125  """Initialize the Enphase Enpower switch entity."""
126  super().__init__(coordinator, description)
127  self.envoyenvoyenvoy = coordinator.envoy
128  self.enpowerenpower = enpower
129  self._serial_number_serial_number = enpower.serial_number
130  self._attr_unique_id_attr_unique_id = f"{self._serial_number}_{description.key}"
131  self._attr_device_info_attr_device_info = DeviceInfo(
132  identifiers={(DOMAIN, self._serial_number_serial_number)},
133  manufacturer="Enphase",
134  model="Enpower",
135  name=f"Enpower {self._serial_number}",
136  sw_version=str(enpower.firmware_version),
137  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
138  )
139 
140  @property
141  def is_on(self) -> bool:
142  """Return the state of the Enpower switch."""
143  enpower = self.datadatadata.enpower
144  assert enpower is not None
145  return self.entity_descriptionentity_description.value_fn(enpower)
146 
147  async def async_turn_on(self, **kwargs: Any) -> None:
148  """Turn on the Enpower switch."""
149  await self.entity_descriptionentity_description.turn_on_fn(self.envoyenvoyenvoy)
150  await self.coordinator.async_request_refresh()
151 
152  async def async_turn_off(self, **kwargs: Any) -> None:
153  """Turn off the Enpower switch."""
154  await self.entity_descriptionentity_description.turn_off_fn(self.envoyenvoyenvoy)
155  await self.coordinator.async_request_refresh()
156 
157 
159  """Representation of an Enphase dry contact switch entity."""
160 
161  entity_description: EnvoyDryContactSwitchEntityDescription
162  _attr_name = None
163 
164  def __init__(
165  self,
166  coordinator: EnphaseUpdateCoordinator,
167  description: EnvoyDryContactSwitchEntityDescription,
168  relay_id: str,
169  ) -> None:
170  """Initialize the Enphase dry contact switch entity."""
171  super().__init__(coordinator, description)
172  self.envoyenvoyenvoy = coordinator.envoy
173  enpower = self.datadatadata.enpower
174  assert enpower is not None
175  self.relay_idrelay_id = relay_id
176  serial_number = enpower.serial_number
177  self._attr_unique_id_attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}"
178  relay = self.datadatadata.dry_contact_settings[relay_id]
179  self._attr_device_info_attr_device_info = DeviceInfo(
180  identifiers={(DOMAIN, relay_id)},
181  manufacturer="Enphase",
182  model="Dry contact relay",
183  name=relay.load_name,
184  sw_version=str(enpower.firmware_version),
185  via_device=(DOMAIN, enpower.serial_number),
186  )
187 
188  @property
189  def is_on(self) -> bool:
190  """Return the state of the dry contact."""
191  relay = self.datadatadata.dry_contact_status[self.relay_idrelay_id]
192  assert relay is not None
193  return self.entity_descriptionentity_description.value_fn(relay)
194 
195  async def async_turn_on(self, **kwargs: Any) -> None:
196  """Turn on (close) the dry contact."""
197  if await self.entity_descriptionentity_description.turn_on_fn(self.envoyenvoyenvoy, self.relay_idrelay_id):
198  self.async_write_ha_stateasync_write_ha_state()
199 
200  async def async_turn_off(self, **kwargs: Any) -> None:
201  """Turn off (open) the dry contact."""
202  if await self.entity_descriptionentity_description.turn_off_fn(self.envoyenvoyenvoy, self.relay_idrelay_id):
203  self.async_write_ha_stateasync_write_ha_state()
204 
205 
207  """Representation of an Enphase storage settings switch entity."""
208 
209  entity_description: EnvoyStorageSettingsSwitchEntityDescription
210 
211  def __init__(
212  self,
213  coordinator: EnphaseUpdateCoordinator,
214  description: EnvoyStorageSettingsSwitchEntityDescription,
215  enpower: EnvoyEnpower | None,
216  ) -> None:
217  """Initialize the Enphase storage settings switch entity."""
218  super().__init__(coordinator, description)
219  self.envoyenvoyenvoy = coordinator.envoy
220  self.enpowerenpower = enpower
221  if enpower:
222  self._serial_number_serial_number = enpower.serial_number
223  self._attr_unique_id_attr_unique_id = f"{self._serial_number}_{description.key}"
224  self._attr_device_info_attr_device_info = DeviceInfo(
225  identifiers={(DOMAIN, self._serial_number_serial_number)},
226  manufacturer="Enphase",
227  model="Enpower",
228  name=f"Enpower {self._serial_number}",
229  sw_version=str(enpower.firmware_version),
230  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
231  )
232  else:
233  # If no enpower device assign switches to Envoy itself
234  self._attr_unique_id_attr_unique_id = f"{self.envoy_serial_num}_{description.key}"
235  self._attr_device_info_attr_device_info = DeviceInfo(
236  identifiers={(DOMAIN, self.envoy_serial_numenvoy_serial_num)},
237  manufacturer="Enphase",
238  model=coordinator.envoy.envoy_model,
239  name=coordinator.name,
240  sw_version=str(coordinator.envoy.firmware),
241  hw_version=coordinator.envoy.part_number,
242  serial_number=self.envoy_serial_numenvoy_serial_num,
243  )
244 
245  @property
246  def is_on(self) -> bool:
247  """Return the state of the storage settings switch."""
248  assert self.datadatadata.tariff is not None
249  assert self.datadatadata.tariff.storage_settings is not None
250  return self.entity_descriptionentity_description.value_fn(self.datadatadata.tariff.storage_settings)
251 
252  async def async_turn_on(self, **kwargs: Any) -> None:
253  """Turn on the storage settings switch."""
254  await self.entity_descriptionentity_description.turn_on_fn(self.envoyenvoyenvoy)
255  await self.coordinator.async_request_refresh()
256 
257  async def async_turn_off(self, **kwargs: Any) -> None:
258  """Turn off the storage switch."""
259  await self.entity_descriptionentity_description.turn_off_fn(self.envoyenvoyenvoy)
260  await self.coordinator.async_request_refresh()
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyDryContactSwitchEntityDescription description, str relay_id)
Definition: switch.py:169
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyEnpowerSwitchEntityDescription description, EnvoyEnpower enpower)
Definition: switch.py:124
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyStorageSettingsSwitchEntityDescription description, EnvoyEnpower|None enpower)
Definition: switch.py:216
None async_setup_entry(HomeAssistant hass, EnphaseConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: switch.py:79