Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for a ScreenLogic Sensor."""
2 
3 from collections.abc import Callable
4 from copy import copy
5 import dataclasses
6 import logging
7 
8 from screenlogicpy.const.data import ATTR, DEVICE, GROUP, VALUE
9 from screenlogicpy.const.msg import CODE
10 from screenlogicpy.device_const.chemistry import DOSE_STATE
11 from screenlogicpy.device_const.pump import PUMP_TYPE
12 from screenlogicpy.device_const.system import EQUIPMENT_FLAG
13 
15  DOMAIN as SENSOR_DOMAIN,
16  SensorDeviceClass,
17  SensorEntity,
18  SensorEntityDescription,
19  SensorStateClass,
20 )
21 from homeassistant.const import EntityCategory
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from .coordinator import ScreenlogicDataUpdateCoordinator
26 from .entity import (
27  ScreenLogicEntity,
28  ScreenLogicEntityDescription,
29  ScreenLogicPushEntity,
30  ScreenLogicPushEntityDescription,
31 )
32 from .types import ScreenLogicConfigEntry
33 from .util import cleanup_excluded_entity, get_ha_unit
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 
38 @dataclasses.dataclass(frozen=True, kw_only=True)
40  SensorEntityDescription, ScreenLogicEntityDescription
41 ):
42  """Describes a ScreenLogic sensor."""
43 
44  value_mod: Callable[[int | str], int | str] | None = None
45 
46 
47 @dataclasses.dataclass(frozen=True, kw_only=True)
49  ScreenLogicSensorDescription, ScreenLogicPushEntityDescription
50 ):
51  """Describes a ScreenLogic push sensor."""
52 
53 
54 SUPPORTED_CORE_SENSORS = [
56  subscription_code=CODE.STATUS_CHANGED,
57  data_root=(DEVICE.CONTROLLER, GROUP.SENSOR),
58  key=VALUE.AIR_TEMPERATURE,
59  device_class=SensorDeviceClass.TEMPERATURE,
60  state_class=SensorStateClass.MEASUREMENT,
61  ),
62 ]
63 
64 SUPPORTED_PUMP_SENSORS = [
66  data_root=(DEVICE.PUMP,),
67  key=VALUE.WATTS_NOW,
68  device_class=SensorDeviceClass.POWER,
69  ),
71  data_root=(DEVICE.PUMP,),
72  key=VALUE.GPM_NOW,
73  enabled_lambda=lambda type: type != PUMP_TYPE.INTELLIFLO_VS,
74  ),
76  data_root=(DEVICE.PUMP,),
77  key=VALUE.RPM_NOW,
78  enabled_lambda=lambda type: type != PUMP_TYPE.INTELLIFLO_VF,
79  ),
80 ]
81 
82 SUPPORTED_INTELLICHEM_SENSORS = [
84  subscription_code=CODE.STATUS_CHANGED,
85  data_root=(DEVICE.CONTROLLER, GROUP.SENSOR),
86  key=VALUE.ORP,
87  state_class=SensorStateClass.MEASUREMENT,
88  ),
90  subscription_code=CODE.STATUS_CHANGED,
91  data_root=(DEVICE.CONTROLLER, GROUP.SENSOR),
92  key=VALUE.PH,
93  state_class=SensorStateClass.MEASUREMENT,
94  ),
96  subscription_code=CODE.CHEMISTRY_CHANGED,
97  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
98  key=VALUE.ORP_NOW,
99  state_class=SensorStateClass.MEASUREMENT,
100  ),
102  subscription_code=CODE.CHEMISTRY_CHANGED,
103  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
104  key=VALUE.PH_NOW,
105  state_class=SensorStateClass.MEASUREMENT,
106  ),
108  subscription_code=CODE.CHEMISTRY_CHANGED,
109  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
110  key=VALUE.ORP_SUPPLY_LEVEL,
111  state_class=SensorStateClass.MEASUREMENT,
112  value_mod=lambda val: int(val) - 1,
113  ),
115  subscription_code=CODE.CHEMISTRY_CHANGED,
116  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
117  key=VALUE.PH_SUPPLY_LEVEL,
118  state_class=SensorStateClass.MEASUREMENT,
119  value_mod=lambda val: int(val) - 1,
120  ),
122  subscription_code=CODE.CHEMISTRY_CHANGED,
123  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
124  key=VALUE.PH_PROBE_WATER_TEMP,
125  device_class=SensorDeviceClass.TEMPERATURE,
126  state_class=SensorStateClass.MEASUREMENT,
127  ),
129  subscription_code=CODE.CHEMISTRY_CHANGED,
130  data_root=(DEVICE.INTELLICHEM, GROUP.SENSOR),
131  key=VALUE.SATURATION,
132  state_class=SensorStateClass.MEASUREMENT,
133  ),
135  subscription_code=CODE.CHEMISTRY_CHANGED,
136  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
137  key=VALUE.CALCIUM_HARDNESS,
138  entity_registry_enabled_default=False, # Superseded by number entity
139  ),
141  subscription_code=CODE.CHEMISTRY_CHANGED,
142  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
143  key=VALUE.CYA,
144  entity_registry_enabled_default=False, # Superseded by number entity
145  ),
147  subscription_code=CODE.CHEMISTRY_CHANGED,
148  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
149  key=VALUE.ORP_SETPOINT,
150  ),
152  subscription_code=CODE.CHEMISTRY_CHANGED,
153  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
154  key=VALUE.PH_SETPOINT,
155  ),
157  subscription_code=CODE.CHEMISTRY_CHANGED,
158  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
159  key=VALUE.TOTAL_ALKALINITY,
160  entity_registry_enabled_default=False, # Superseded by number entity
161  ),
163  subscription_code=CODE.CHEMISTRY_CHANGED,
164  data_root=(DEVICE.INTELLICHEM, GROUP.CONFIGURATION),
165  key=VALUE.SALT_TDS_PPM,
166  entity_registry_enabled_default=False, # Superseded by number entity
167  ),
169  subscription_code=CODE.CHEMISTRY_CHANGED,
170  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
171  key=VALUE.ORP_DOSING_STATE,
172  device_class=SensorDeviceClass.ENUM,
173  options=["Dosing", "Mixing", "Monitoring"],
174  value_mod=lambda val: DOSE_STATE(val).title,
175  ),
177  subscription_code=CODE.CHEMISTRY_CHANGED,
178  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
179  key=VALUE.ORP_LAST_DOSE_TIME,
180  device_class=SensorDeviceClass.DURATION,
181  state_class=SensorStateClass.TOTAL_INCREASING,
182  ),
184  subscription_code=CODE.CHEMISTRY_CHANGED,
185  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
186  key=VALUE.ORP_LAST_DOSE_VOLUME,
187  device_class=SensorDeviceClass.VOLUME,
188  state_class=SensorStateClass.TOTAL_INCREASING,
189  ),
191  subscription_code=CODE.CHEMISTRY_CHANGED,
192  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
193  key=VALUE.PH_DOSING_STATE,
194  device_class=SensorDeviceClass.ENUM,
195  options=["Dosing", "Mixing", "Monitoring"],
196  value_mod=lambda val: DOSE_STATE(val).title,
197  ),
199  subscription_code=CODE.CHEMISTRY_CHANGED,
200  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
201  key=VALUE.PH_LAST_DOSE_TIME,
202  device_class=SensorDeviceClass.DURATION,
203  state_class=SensorStateClass.TOTAL_INCREASING,
204  ),
206  subscription_code=CODE.CHEMISTRY_CHANGED,
207  data_root=(DEVICE.INTELLICHEM, GROUP.DOSE_STATUS),
208  key=VALUE.PH_LAST_DOSE_VOLUME,
209  device_class=SensorDeviceClass.VOLUME,
210  state_class=SensorStateClass.TOTAL_INCREASING,
211  ),
212 ]
213 
214 SUPPORTED_SCG_SENSORS = [
216  data_root=(DEVICE.SCG, GROUP.SENSOR),
217  key=VALUE.SALT_PPM,
218  state_class=SensorStateClass.MEASUREMENT,
219  ),
221  data_root=(DEVICE.SCG, GROUP.CONFIGURATION),
222  key=VALUE.SUPER_CHLOR_TIMER,
223  ),
224 ]
225 
226 
228  hass: HomeAssistant,
229  config_entry: ScreenLogicConfigEntry,
230  async_add_entities: AddEntitiesCallback,
231 ) -> None:
232  """Set up entry."""
233  coordinator = config_entry.runtime_data
234  gateway = coordinator.gateway
235 
236  entities: list[ScreenLogicSensor] = [
237  ScreenLogicPushSensor(coordinator, core_sensor_description)
238  for core_sensor_description in SUPPORTED_CORE_SENSORS
239  if (
240  gateway.get_data(
241  *core_sensor_description.data_root, core_sensor_description.key
242  )
243  is not None
244  )
245  ]
246 
247  for pump_index, pump_data in gateway.get_data(DEVICE.PUMP).items():
248  if not pump_data or not pump_data.get(VALUE.DATA):
249  continue
250  pump_type = pump_data[VALUE.TYPE]
251  for proto_pump_sensor_description in SUPPORTED_PUMP_SENSORS:
252  if not pump_data.get(proto_pump_sensor_description.key):
253  continue
254  entities.append(
256  coordinator,
257  copy(proto_pump_sensor_description),
258  pump_index,
259  pump_type,
260  )
261  )
262 
263  chem_sensor_description: ScreenLogicPushSensorDescription
264  for chem_sensor_description in SUPPORTED_INTELLICHEM_SENSORS:
265  chem_sensor_data_path = (
266  *chem_sensor_description.data_root,
267  chem_sensor_description.key,
268  )
269  if EQUIPMENT_FLAG.INTELLICHEM not in gateway.equipment_flags:
270  cleanup_excluded_entity(coordinator, SENSOR_DOMAIN, chem_sensor_data_path)
271  continue
272  if gateway.get_data(*chem_sensor_data_path):
273  chem_sensor_description = dataclasses.replace(
274  chem_sensor_description, entity_category=EntityCategory.DIAGNOSTIC
275  )
276  entities.append(ScreenLogicPushSensor(coordinator, chem_sensor_description))
277 
278  scg_sensor_description: ScreenLogicSensorDescription
279  for scg_sensor_description in SUPPORTED_SCG_SENSORS:
280  scg_sensor_data_path = (
281  *scg_sensor_description.data_root,
282  scg_sensor_description.key,
283  )
284  if EQUIPMENT_FLAG.CHLORINATOR not in gateway.equipment_flags:
285  cleanup_excluded_entity(coordinator, SENSOR_DOMAIN, scg_sensor_data_path)
286  continue
287  if gateway.get_data(*scg_sensor_data_path):
288  scg_sensor_description = dataclasses.replace(
289  scg_sensor_description, entity_category=EntityCategory.DIAGNOSTIC
290  )
291  entities.append(ScreenLogicSensor(coordinator, scg_sensor_description))
292 
293  async_add_entities(entities)
294 
295 
297  """Representation of a ScreenLogic sensor entity."""
298 
299  entity_description: ScreenLogicSensorDescription
300  _attr_has_entity_name = True
301 
302  def __init__(
303  self,
304  coordinator: ScreenlogicDataUpdateCoordinator,
305  entity_description: ScreenLogicSensorDescription,
306  ) -> None:
307  """Initialize of the entity."""
308  super().__init__(coordinator, entity_description)
309  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = get_ha_unit(
310  self.entity_dataentity_data.get(ATTR.UNIT)
311  )
312 
313  @property
314  def native_value(self) -> str | int | float:
315  """State of the sensor."""
316  val = self.entity_dataentity_data[ATTR.VALUE]
317  value_mod = self.entity_descriptionentity_description.value_mod
318  return value_mod(val) if value_mod else val
319 
320 
322  """Representation of a ScreenLogic push sensor entity."""
323 
324  entity_description: ScreenLogicPushSensorDescription
325 
326 
328  """Representation of a ScreenLogic pump sensor."""
329 
330  _attr_entity_category = EntityCategory.DIAGNOSTIC
331  _attr_state_class = SensorStateClass.MEASUREMENT
332 
333  def __init__(
334  self,
335  coordinator: ScreenlogicDataUpdateCoordinator,
336  entity_description: ScreenLogicSensorDescription,
337  pump_index: int,
338  pump_type: int,
339  ) -> None:
340  """Initialize of the entity."""
341  entity_description = dataclasses.replace(
342  entity_description, data_root=(DEVICE.PUMP, pump_index)
343  )
344  super().__init__(coordinator, entity_description)
345  if entity_description.enabled_lambda:
346  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = (
347  entity_description.enabled_lambda(pump_type)
348  )
None __init__(self, ScreenlogicDataUpdateCoordinator coordinator, ScreenLogicSensorDescription entity_description, int pump_index, int pump_type)
Definition: sensor.py:339
None __init__(self, ScreenlogicDataUpdateCoordinator coordinator, ScreenLogicSensorDescription entity_description)
Definition: sensor.py:306
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ScreenLogicConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:231
None cleanup_excluded_entity(ScreenlogicDataUpdateCoordinator coordinator, str platform_domain, ScreenLogicDataPath data_path)
Definition: util.py:38