Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Platform for sensor integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 import logging
8 from typing import Any
9 
10 from mypermobil import (
11  BATTERY_AMPERE_HOURS_LEFT,
12  BATTERY_CHARGE_TIME_LEFT,
13  BATTERY_DISTANCE_LEFT,
14  BATTERY_INDOOR_DRIVE_TIME,
15  BATTERY_MAX_AMPERE_HOURS,
16  BATTERY_MAX_DISTANCE_LEFT,
17  BATTERY_STATE_OF_CHARGE,
18  BATTERY_STATE_OF_HEALTH,
19  RECORDS_DISTANCE,
20  RECORDS_DISTANCE_UNIT,
21  RECORDS_SEATING,
22  USAGE_ADJUSTMENTS,
23  USAGE_DISTANCE,
24 )
25 
26 from homeassistant import config_entries
28  SensorDeviceClass,
29  SensorEntity,
30  SensorEntityDescription,
31  SensorStateClass,
32 )
33 from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfLength, UnitOfTime
34 from homeassistant.core import HomeAssistant
35 from homeassistant.helpers.entity_platform import AddEntitiesCallback
36 
37 from .const import BATTERY_ASSUMED_VOLTAGE, DOMAIN, KM, MILES
38 from .coordinator import MyPermobilCoordinator
39 from .entity import PermobilEntity
40 
41 _LOGGER = logging.getLogger(__name__)
42 
43 
44 @dataclass(frozen=True, kw_only=True)
46  """Describes Permobil sensor entity."""
47 
48  value_fn: Callable[[Any], float | int]
49  available_fn: Callable[[Any], bool]
50 
51 
52 SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = (
54  # Current battery as a percentage
55  value_fn=lambda data: data.battery[BATTERY_STATE_OF_CHARGE[0]],
56  available_fn=lambda data: BATTERY_STATE_OF_CHARGE[0] in data.battery,
57  key="state_of_charge",
58  translation_key="state_of_charge",
59  native_unit_of_measurement=PERCENTAGE,
60  device_class=SensorDeviceClass.BATTERY,
61  state_class=SensorStateClass.MEASUREMENT,
62  ),
64  # Current battery health as a percentage of original capacity
65  value_fn=lambda data: data.battery[BATTERY_STATE_OF_HEALTH[0]],
66  available_fn=lambda data: BATTERY_STATE_OF_HEALTH[0] in data.battery,
67  key="state_of_health",
68  translation_key="state_of_health",
69  native_unit_of_measurement=PERCENTAGE,
70  state_class=SensorStateClass.MEASUREMENT,
71  ),
73  # Time until fully charged (displays 0 if not charging)
74  value_fn=lambda data: data.battery[BATTERY_CHARGE_TIME_LEFT[0]],
75  available_fn=lambda data: BATTERY_CHARGE_TIME_LEFT[0] in data.battery,
76  key="charge_time_left",
77  translation_key="charge_time_left",
78  native_unit_of_measurement=UnitOfTime.HOURS,
79  device_class=SensorDeviceClass.DURATION,
80  ),
82  # Distance possible on current change (km)
83  value_fn=lambda data: data.battery[BATTERY_DISTANCE_LEFT[0]],
84  available_fn=lambda data: BATTERY_DISTANCE_LEFT[0] in data.battery,
85  key="distance_left",
86  translation_key="distance_left",
87  native_unit_of_measurement=UnitOfLength.KILOMETERS,
88  device_class=SensorDeviceClass.DISTANCE,
89  ),
91  # Drive time possible on current charge
92  value_fn=lambda data: data.battery[BATTERY_INDOOR_DRIVE_TIME[0]],
93  available_fn=lambda data: BATTERY_INDOOR_DRIVE_TIME[0] in data.battery,
94  key="indoor_drive_time",
95  translation_key="indoor_drive_time",
96  native_unit_of_measurement=UnitOfTime.HOURS,
97  device_class=SensorDeviceClass.DURATION,
98  ),
100  # Watt hours the battery can store given battery health
101  value_fn=lambda data: data.battery[BATTERY_MAX_AMPERE_HOURS[0]]
102  * BATTERY_ASSUMED_VOLTAGE,
103  available_fn=lambda data: BATTERY_MAX_AMPERE_HOURS[0] in data.battery,
104  key="max_watt_hours",
105  translation_key="max_watt_hours",
106  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
107  device_class=SensorDeviceClass.ENERGY_STORAGE,
108  state_class=SensorStateClass.MEASUREMENT,
109  ),
111  # Current amount of watt hours in battery
112  value_fn=lambda data: data.battery[BATTERY_AMPERE_HOURS_LEFT[0]]
113  * BATTERY_ASSUMED_VOLTAGE,
114  available_fn=lambda data: BATTERY_AMPERE_HOURS_LEFT[0] in data.battery,
115  key="watt_hours_left",
116  translation_key="watt_hours_left",
117  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
118  device_class=SensorDeviceClass.ENERGY_STORAGE,
119  state_class=SensorStateClass.MEASUREMENT,
120  ),
122  # Distance that can be traveled with full charge given battery health (km)
123  value_fn=lambda data: data.battery[BATTERY_MAX_DISTANCE_LEFT[0]],
124  available_fn=lambda data: BATTERY_MAX_DISTANCE_LEFT[0] in data.battery,
125  key="max_distance_left",
126  translation_key="max_distance_left",
127  native_unit_of_measurement=UnitOfLength.KILOMETERS,
128  device_class=SensorDeviceClass.DISTANCE,
129  ),
131  # Distance traveled today monotonically increasing, resets every 24h (km)
132  value_fn=lambda data: data.daily_usage[USAGE_DISTANCE[0]],
133  available_fn=lambda data: USAGE_DISTANCE[0] in data.daily_usage,
134  key="usage_distance",
135  translation_key="usage_distance",
136  native_unit_of_measurement=UnitOfLength.KILOMETERS,
137  device_class=SensorDeviceClass.DISTANCE,
138  state_class=SensorStateClass.TOTAL_INCREASING,
139  ),
141  # Number of adjustments monotonically increasing, resets every 24h
142  value_fn=lambda data: data.daily_usage[USAGE_ADJUSTMENTS[0]],
143  available_fn=lambda data: USAGE_ADJUSTMENTS[0] in data.daily_usage,
144  key="usage_adjustments",
145  translation_key="usage_adjustments",
146  native_unit_of_measurement="adjustments",
147  state_class=SensorStateClass.TOTAL_INCREASING,
148  ),
150  # Largest number of adjustemnts in a single 24h period, monotonically increasing, never resets
151  value_fn=lambda data: data.records[RECORDS_SEATING[0]],
152  available_fn=lambda data: RECORDS_SEATING[0] in data.records,
153  key="record_adjustments",
154  translation_key="record_adjustments",
155  native_unit_of_measurement="adjustments",
156  state_class=SensorStateClass.TOTAL_INCREASING,
157  ),
159  # Record of largest distance travelled in a day, monotonically increasing, never resets
160  value_fn=lambda data: data.records[RECORDS_DISTANCE[0]],
161  available_fn=lambda data: RECORDS_DISTANCE[0] in data.records,
162  key="record_distance",
163  translation_key="record_distance",
164  device_class=SensorDeviceClass.DISTANCE,
165  state_class=SensorStateClass.TOTAL_INCREASING,
166  ),
167 )
168 
169 DISTANCE_UNITS: dict[Any, UnitOfLength] = {
170  KM: UnitOfLength.KILOMETERS,
171  MILES: UnitOfLength.MILES,
172 }
173 
174 
176  hass: HomeAssistant,
177  config_entry: config_entries.ConfigEntry,
178  async_add_entities: AddEntitiesCallback,
179 ) -> None:
180  """Create sensors from a config entry created in the integrations UI."""
181 
182  coordinator: MyPermobilCoordinator = hass.data[DOMAIN][config_entry.entry_id]
183 
185  PermobilSensor(coordinator=coordinator, description=description)
186  for description in SENSOR_DESCRIPTIONS
187  )
188 
189 
191  """Representation of a Sensor.
192 
193  This implements the common functions of all sensors.
194  """
195 
196  _attr_suggested_display_precision = 0
197  entity_description: PermobilSensorEntityDescription
198 
199  @property
200  def native_unit_of_measurement(self) -> str | None:
201  """Return the unit of measurement of the sensor."""
202  if self.entity_descriptionentity_description.key == "record_distance":
203  return DISTANCE_UNITS.get(
204  self.coordinator.data.records[RECORDS_DISTANCE_UNIT[0]]
205  )
206  return self.entity_descriptionentity_description.native_unit_of_measurement
207 
208  @property
209  def available(self) -> bool:
210  """Return True if the sensor has value."""
211  return super().available and self.entity_descriptionentity_description.available_fn(
212  self.coordinator.data
213  )
214 
215  @property
216  def native_value(self) -> float | int:
217  """Return the value of the sensor."""
218  return self.entity_descriptionentity_description.value_fn(self.coordinator.data)
None async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:179