Home Assistant Unofficial Reference 2024.12.1
number.py
Go to the documentation of this file.
1 """Number platform for Enphase Envoy solar energy monitor."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Awaitable, Callable
6 from dataclasses import dataclass
7 from operator import attrgetter
8 from typing import Any
9 
10 from pyenphase import Envoy, EnvoyDryContactSettings
11 from pyenphase.const import SupportedFeatures
12 from pyenphase.models.tariff import EnvoyStorageSettings
13 
15  NumberDeviceClass,
16  NumberEntity,
17  NumberEntityDescription,
18 )
19 from homeassistant.const import PERCENTAGE, EntityCategory
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers.device_registry import DeviceInfo
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 
24 from .const import DOMAIN
25 from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
26 from .entity import EnvoyBaseEntity
27 
28 
29 @dataclass(frozen=True, kw_only=True)
31  """Describes an Envoy Dry Contact Relay number entity."""
32 
33  value_fn: Callable[[EnvoyDryContactSettings], float]
34 
35 
36 @dataclass(frozen=True, kw_only=True)
38  """Describes an Envoy storage mode number entity."""
39 
40  value_fn: Callable[[EnvoyStorageSettings], float]
41  update_fn: Callable[[Envoy, float], Awaitable[dict[str, Any]]]
42 
43 
44 RELAY_ENTITIES = (
46  key="soc_low",
47  translation_key="cutoff_battery_level",
48  device_class=NumberDeviceClass.BATTERY,
49  entity_category=EntityCategory.CONFIG,
50  value_fn=attrgetter("soc_low"),
51  ),
53  key="soc_high",
54  translation_key="restore_battery_level",
55  device_class=NumberDeviceClass.BATTERY,
56  entity_category=EntityCategory.CONFIG,
57  value_fn=attrgetter("soc_high"),
58  ),
59 )
60 
61 STORAGE_RESERVE_SOC_ENTITY = EnvoyStorageSettingsNumberEntityDescription(
62  key="reserve_soc",
63  translation_key="reserve_soc",
64  native_unit_of_measurement=PERCENTAGE,
65  device_class=NumberDeviceClass.BATTERY,
66  value_fn=attrgetter("reserved_soc"),
67  update_fn=lambda envoy, value: envoy.set_reserve_soc(int(value)),
68 )
69 
70 
72  hass: HomeAssistant,
73  config_entry: EnphaseConfigEntry,
74  async_add_entities: AddEntitiesCallback,
75 ) -> None:
76  """Set up Enphase Envoy number platform."""
77  coordinator = config_entry.runtime_data
78  envoy_data = coordinator.envoy.data
79  assert envoy_data is not None
80  entities: list[NumberEntity] = []
81  if envoy_data.dry_contact_settings:
82  entities.extend(
83  EnvoyRelayNumberEntity(coordinator, entity, relay)
84  for entity in RELAY_ENTITIES
85  for relay in envoy_data.dry_contact_settings
86  )
87  if (
88  envoy_data.tariff
89  and envoy_data.tariff.storage_settings
90  and coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE
91  ):
92  entities.append(
93  EnvoyStorageSettingsNumberEntity(coordinator, STORAGE_RESERVE_SOC_ENTITY)
94  )
95  async_add_entities(entities)
96 
97 
99  """Representation of an Enphase Enpower number entity."""
100 
101  entity_description: EnvoyRelayNumberEntityDescription
102 
103  def __init__(
104  self,
105  coordinator: EnphaseUpdateCoordinator,
106  description: EnvoyRelayNumberEntityDescription,
107  relay_id: str,
108  ) -> None:
109  """Initialize the Enphase relay number entity."""
110  super().__init__(coordinator, description)
111  self.envoyenvoyenvoy = coordinator.envoy
112  enpower = self.datadatadata.enpower
113  assert enpower is not None
114  serial_number = enpower.serial_number
115  self._relay_id_relay_id = relay_id
116  self._attr_unique_id_attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}"
117  self._attr_device_info_attr_device_info = DeviceInfo(
118  identifiers={(DOMAIN, relay_id)},
119  manufacturer="Enphase",
120  model="Dry contact relay",
121  name=self.datadatadata.dry_contact_settings[relay_id].load_name,
122  sw_version=str(enpower.firmware_version),
123  via_device=(DOMAIN, serial_number),
124  )
125 
126  @property
127  def native_value(self) -> float:
128  """Return the state of the relay entity."""
129  return self.entity_descriptionentity_description.value_fn(
130  self.datadatadata.dry_contact_settings[self._relay_id_relay_id]
131  )
132 
133  async def async_set_native_value(self, value: float) -> None:
134  """Update the relay."""
135  await self.envoyenvoyenvoy.update_dry_contact(
136  {"id": self._relay_id_relay_id, self.entity_descriptionentity_description.key: int(value)}
137  )
138  await self.coordinator.async_request_refresh()
139 
140 
142  """Representation of an Enphase storage settings number entity."""
143 
144  entity_description: EnvoyStorageSettingsNumberEntityDescription
145 
146  def __init__(
147  self,
148  coordinator: EnphaseUpdateCoordinator,
149  description: EnvoyStorageSettingsNumberEntityDescription,
150  ) -> None:
151  """Initialize the Enphase relay number entity."""
152  super().__init__(coordinator, description)
153  self.envoyenvoyenvoy = coordinator.envoy
154  assert self.datadatadata is not None
155  if enpower := self.datadatadata.enpower:
156  self._serial_number_serial_number = enpower.serial_number
157  self._attr_unique_id_attr_unique_id = f"{self._serial_number}_{description.key}"
158  self._attr_device_info_attr_device_info = DeviceInfo(
159  identifiers={(DOMAIN, self._serial_number_serial_number)},
160  manufacturer="Enphase",
161  model="Enpower",
162  name=f"Enpower {self._serial_number}",
163  sw_version=str(enpower.firmware_version),
164  via_device=(DOMAIN, self.envoy_serial_numenvoy_serial_num),
165  )
166  else:
167  # If no enpower device assign numbers to Envoy itself
168  self._attr_unique_id_attr_unique_id = f"{self.envoy_serial_num}_{description.key}"
169  self._attr_device_info_attr_device_info = DeviceInfo(
170  identifiers={(DOMAIN, self.envoy_serial_numenvoy_serial_num)},
171  manufacturer="Enphase",
172  model=coordinator.envoy.envoy_model,
173  name=coordinator.name,
174  sw_version=str(coordinator.envoy.firmware),
175  hw_version=coordinator.envoy.part_number,
176  serial_number=self.envoy_serial_numenvoy_serial_num,
177  )
178 
179  @property
180  def native_value(self) -> float:
181  """Return the state of the storage setting entity."""
182  assert self.datadatadata.tariff is not None
183  assert self.datadatadata.tariff.storage_settings is not None
184  return self.entity_descriptionentity_description.value_fn(self.datadatadata.tariff.storage_settings)
185 
186  async def async_set_native_value(self, value: float) -> None:
187  """Update the storage setting."""
188  await self.entity_descriptionentity_description.update_fn(self.envoyenvoyenvoy, value)
189  await self.coordinator.async_request_refresh()
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyRelayNumberEntityDescription description, str relay_id)
Definition: number.py:108
None __init__(self, EnphaseUpdateCoordinator coordinator, EnvoyStorageSettingsNumberEntityDescription description)
Definition: number.py:150
None async_setup_entry(HomeAssistant hass, EnphaseConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: number.py:75