Home Assistant Unofficial Reference 2024.12.1
select.py
Go to the documentation of this file.
1 """Select 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, EnvoyDryContactSettings
10 from pyenphase.const import SupportedFeatures
11 from pyenphase.models.dry_contacts import DryContactAction, DryContactMode
12 from pyenphase.models.tariff import EnvoyStorageMode, EnvoyStorageSettings
13 
14 from homeassistant.components.select import SelectEntity, SelectEntityDescription
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 Dry Contact Relay select entity."""
27 
28  value_fn: Callable[[EnvoyDryContactSettings], str]
29  update_fn: Callable[
30  [Envoy, EnvoyDryContactSettings, str], Coroutine[Any, Any, dict[str, Any]]
31  ]
32 
33 
34 @dataclass(frozen=True, kw_only=True)
36  """Describes an Envoy storage settings select entity."""
37 
38  value_fn: Callable[[EnvoyStorageSettings], str]
39  update_fn: Callable[[Envoy, str], Awaitable[dict[str, Any]]]
40 
41 
42 RELAY_MODE_MAP = {
43  DryContactMode.MANUAL: "standard",
44  DryContactMode.STATE_OF_CHARGE: "battery",
45 }
46 REVERSE_RELAY_MODE_MAP = {v: k for k, v in RELAY_MODE_MAP.items()}
47 RELAY_ACTION_MAP = {
48  DryContactAction.APPLY: "powered",
49  DryContactAction.SHED: "not_powered",
50  DryContactAction.SCHEDULE: "schedule",
51  DryContactAction.NONE: "none",
52 }
53 REVERSE_RELAY_ACTION_MAP = {v: k for k, v in RELAY_ACTION_MAP.items()}
54 MODE_OPTIONS = list(REVERSE_RELAY_MODE_MAP)
55 ACTION_OPTIONS = list(REVERSE_RELAY_ACTION_MAP)
56 
57 STORAGE_MODE_MAP = {
58  EnvoyStorageMode.BACKUP: "backup",
59  EnvoyStorageMode.SELF_CONSUMPTION: "self_consumption",
60  EnvoyStorageMode.SAVINGS: "savings",
61 }
62 REVERSE_STORAGE_MODE_MAP = {v: k for k, v in STORAGE_MODE_MAP.items()}
63 STORAGE_MODE_OPTIONS = list(REVERSE_STORAGE_MODE_MAP)
64 
65 RELAY_ENTITIES = (
67  key="mode",
68  translation_key="relay_mode",
69  options=MODE_OPTIONS,
70  value_fn=lambda relay: RELAY_MODE_MAP[relay.mode],
71  update_fn=lambda envoy, relay, value: envoy.update_dry_contact(
72  {
73  "id": relay.id,
74  "mode": REVERSE_RELAY_MODE_MAP[value],
75  }
76  ),
77  ),
79  key="grid_action",
80  translation_key="relay_grid_action",
81  options=ACTION_OPTIONS,
82  value_fn=lambda relay: RELAY_ACTION_MAP[relay.grid_action],
83  update_fn=lambda envoy, relay, value: envoy.update_dry_contact(
84  {
85  "id": relay.id,
86  "grid_action": REVERSE_RELAY_ACTION_MAP[value],
87  }
88  ),
89  ),
91  key="microgrid_action",
92  translation_key="relay_microgrid_action",
93  options=ACTION_OPTIONS,
94  value_fn=lambda relay: RELAY_ACTION_MAP[relay.micro_grid_action],
95  update_fn=lambda envoy, relay, value: envoy.update_dry_contact(
96  {
97  "id": relay.id,
98  "micro_grid_action": REVERSE_RELAY_ACTION_MAP[value],
99  }
100  ),
101  ),
103  key="generator_action",
104  translation_key="relay_generator_action",
105  options=ACTION_OPTIONS,
106  value_fn=lambda relay: RELAY_ACTION_MAP[relay.generator_action],
107  update_fn=lambda envoy, relay, value: envoy.update_dry_contact(
108  {
109  "id": relay.id,
110  "generator_action": REVERSE_RELAY_ACTION_MAP[value],
111  }
112  ),
113  ),
114 )
116  key="storage_mode",
117  translation_key="storage_mode",
118  options=STORAGE_MODE_OPTIONS,
119  value_fn=lambda storage_settings: STORAGE_MODE_MAP[storage_settings.mode],
120  update_fn=lambda envoy, value: envoy.set_storage_mode(
121  REVERSE_STORAGE_MODE_MAP[value]
122  ),
123 )
124 
125 
127  hass: HomeAssistant,
128  config_entry: EnphaseConfigEntry,
129  async_add_entities: AddEntitiesCallback,
130 ) -> None:
131  """Set up Enphase Envoy select platform."""
132  coordinator = config_entry.runtime_data
133  envoy_data = coordinator.envoy.data
134  assert envoy_data is not None
135  entities: list[SelectEntity] = []
136  if envoy_data.dry_contact_settings:
137  entities.extend(
138  EnvoyRelaySelectEntity(coordinator, entity, relay)
139  for entity in RELAY_ENTITIES
140  for relay in envoy_data.dry_contact_settings
141  )
142  if (
143  envoy_data.tariff
144  and envoy_data.tariff.storage_settings
145  and coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE
146  ):
147  entities.append(
148  EnvoyStorageSettingsSelectEntity(coordinator, STORAGE_MODE_ENTITY)
149  )
150  async_add_entities(entities)
151 
152 
154  """Representation of an Enphase Enpower select entity."""
155 
156  entity_description: EnvoyRelaySelectEntityDescription
157 
158  def __init__(
159  self,
160  coordinator: EnphaseUpdateCoordinator,
161  description: EnvoyRelaySelectEntityDescription,
162  relay_id: str,
163  ) -> None:
164  """Initialize the Enphase relay select entity."""
165  super().__init__(coordinator, description)
166  self.envoyenvoyenvoy = coordinator.envoy
167  enpower = self.datadatadata.enpower
168  assert enpower is not None
169  serial_number = enpower.serial_number
170  self._relay_id_relay_id = relay_id
171  self._attr_unique_id_attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}"
172  self._attr_device_info_attr_device_info = DeviceInfo(
173  identifiers={(DOMAIN, relay_id)},
174  manufacturer="Enphase",
175  model="Dry contact relay",
176  name=self.relayrelay.load_name,
177  sw_version=str(enpower.firmware_version),
178  via_device=(DOMAIN, serial_number),
179  )
180 
181  @property
182  def relay(self) -> EnvoyDryContactSettings:
183  """Return the relay object."""
184  return self.datadatadata.dry_contact_settings[self._relay_id_relay_id]
185 
186  @property
187  def current_option(self) -> str:
188  """Return the state of the Enpower switch."""
189  return self.entity_descriptionentity_description.value_fn(self.relayrelay)
190 
191  async def async_select_option(self, option: str) -> None:
192  """Update the relay."""
193  await self.entity_descriptionentity_description.update_fn(self.envoyenvoyenvoy, self.relayrelay, option)
194  await self.coordinator.async_request_refresh()
195 
196 
198  """Representation of an Enphase storage settings select entity."""
199 
200  entity_description: EnvoyStorageSettingsSelectEntityDescription
201 
202  def __init__(
203  self,
204  coordinator: EnphaseUpdateCoordinator,
205  description: EnvoyStorageSettingsSelectEntityDescription,
206  ) -> None:
207  """Initialize the Enphase storage settings select entity."""
208  super().__init__(coordinator, description)
209  self.envoyenvoyenvoy = coordinator.envoy
210  assert coordinator.envoy.data is not None
211  if enpower := coordinator.envoy.data.enpower:
212  self._serial_number_serial_number = enpower.serial_number
213  self._attr_unique_id_attr_unique_id = f"{self._serial_number}_{description.key}"
214  self._attr_device_info_attr_device_info = DeviceInfo(
215  identifiers={(DOMAIN, self._serial_number_serial_number)},
216  manufacturer="Enphase",
217  model="Enpower",
218  name=f"Enpower {self._serial_number}",
219  sw_version=str(enpower.firmware_version),
220  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
221  )
222  else:
223  # If no enpower device assign selects to Envoy itself
224  self._attr_unique_id_attr_unique_id = f"{self.envoy_serial_num}_{description.key}"
225  self._attr_device_info_attr_device_info = DeviceInfo(
226  identifiers={(DOMAIN, self.envoy_serial_numenvoy_serial_num)},
227  manufacturer="Enphase",
228  model=coordinator.envoy.envoy_model,
229  name=coordinator.name,
230  sw_version=str(coordinator.envoy.firmware),
231  hw_version=coordinator.envoy.part_number,
232  serial_number=self.envoy_serial_numenvoy_serial_num,
233  )
234 
235  @property
236  def current_option(self) -> str:
237  """Return the state of the select entity."""
238  assert self.datadatadata.tariff is not None
239  assert self.datadatadata.tariff.storage_settings is not None
240  return self.entity_descriptionentity_description.value_fn(self.datadatadata.tariff.storage_settings)
241 
242  async def async_select_option(self, option: str) -> None:
243  """Update the relay."""
244  await self.entity_descriptionentity_description.update_fn(self.envoyenvoyenvoy, option)
245  await self.coordinator.async_request_refresh()
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyRelaySelectEntityDescription description, str relay_id)
Definition: select.py:163
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyStorageSettingsSelectEntityDescription description)
Definition: select.py:206
None async_setup_entry(HomeAssistant hass, EnphaseConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: select.py:130