Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
9  SensorDeviceClass,
10  SensorEntity,
11  SensorEntityDescription,
12 )
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import PERCENTAGE, UnitOfDataRate, UnitOfTemperature
15 from homeassistant.core import HomeAssistant, callback
16 from homeassistant.helpers.device_registry import DeviceInfo
17 from homeassistant.helpers.dispatcher import async_dispatcher_connect
18 from homeassistant.helpers.entity_platform import AddEntitiesCallback
19 import homeassistant.util.dt as dt_util
20 
21 from .const import DOMAIN
22 from .entity import FreeboxHomeEntity
23 from .router import FreeboxRouter
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 CONNECTION_SENSORS: tuple[SensorEntityDescription, ...] = (
29  key="rate_down",
30  name="Freebox download speed",
31  device_class=SensorDeviceClass.DATA_RATE,
32  native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
33  icon="mdi:download-network",
34  ),
36  key="rate_up",
37  name="Freebox upload speed",
38  device_class=SensorDeviceClass.DATA_RATE,
39  native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
40  icon="mdi:upload-network",
41  ),
42 )
43 
44 CALL_SENSORS: tuple[SensorEntityDescription, ...] = (
46  key="missed",
47  name="Freebox missed calls",
48  icon="mdi:phone-missed",
49  ),
50 )
51 
52 DISK_PARTITION_SENSORS: tuple[SensorEntityDescription, ...] = (
54  key="partition_free_space",
55  name="free space",
56  native_unit_of_measurement=PERCENTAGE,
57  icon="mdi:harddisk",
58  ),
59 )
60 
61 
63  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
64 ) -> None:
65  """Set up the sensors."""
66  router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
67  entities: list[SensorEntity] = []
68 
69  _LOGGER.debug(
70  "%s - %s - %s temperature sensors",
71  router.name,
72  router.mac,
73  len(router.sensors_temperature),
74  )
75  entities = [
77  router,
79  key=sensor_name,
80  name=f"Freebox {sensor_name}",
81  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
82  device_class=SensorDeviceClass.TEMPERATURE,
83  ),
84  )
85  for sensor_name in router.sensors_temperature
86  ]
87 
88  entities.extend(
89  [FreeboxSensor(router, description) for description in CONNECTION_SENSORS]
90  )
91  entities.extend(
92  [FreeboxCallSensor(router, description) for description in CALL_SENSORS]
93  )
94 
95  _LOGGER.debug("%s - %s - %s disk(s)", router.name, router.mac, len(router.disks))
96  entities.extend(
97  FreeboxDiskSensor(router, disk, partition, description)
98  for disk in router.disks.values()
99  for partition in disk["partitions"].values()
100  for description in DISK_PARTITION_SENSORS
101  )
102 
103  for node in router.home_devices.values():
104  for endpoint in node["show_endpoints"]:
105  if (
106  endpoint["name"] == "battery"
107  and endpoint["ep_type"] == "signal"
108  and endpoint.get("value") is not None
109  ):
110  entities.append(FreeboxBatterySensor(hass, router, node, endpoint))
111 
112  if entities:
113  async_add_entities(entities, True)
114 
115 
117  """Representation of a Freebox sensor."""
118 
119  _attr_should_poll = False
120 
121  def __init__(
122  self, router: FreeboxRouter, description: SensorEntityDescription
123  ) -> None:
124  """Initialize a Freebox sensor."""
125  self.entity_descriptionentity_description = description
126  self._router_router = router
127  self._attr_unique_id_attr_unique_id = f"{router.mac} {description.name}"
128  self._attr_device_info_attr_device_info = router.device_info
129 
130  @callback
131  def async_update_state(self) -> None:
132  """Update the Freebox sensor."""
133  state = self._router_router.sensors[self.entity_descriptionentity_description.key]
134  if self.native_unit_of_measurementnative_unit_of_measurementnative_unit_of_measurement == UnitOfDataRate.KILOBYTES_PER_SECOND:
135  self._attr_native_value_attr_native_value = round(state / 1000, 2)
136  else:
137  self._attr_native_value_attr_native_value = state
138 
139  @callback
140  def async_on_demand_update(self) -> None:
141  """Update state."""
142  self.async_update_stateasync_update_state()
143  self.async_write_ha_stateasync_write_ha_state()
144 
145  async def async_added_to_hass(self) -> None:
146  """Register state update callback."""
147  self.async_update_stateasync_update_state()
148  self.async_on_removeasync_on_remove(
150  self.hasshass,
151  self._router_router.signal_sensor_update,
152  self.async_on_demand_updateasync_on_demand_update,
153  )
154  )
155 
156 
158  """Representation of a Freebox call sensor."""
159 
160  def __init__(
161  self, router: FreeboxRouter, description: SensorEntityDescription
162  ) -> None:
163  """Initialize a Freebox call sensor."""
164  super().__init__(router, description)
165  self._call_list_for_type_call_list_for_type: list[dict[str, Any]] = []
166 
167  @callback
168  def async_update_state(self) -> None:
169  """Update the Freebox call sensor."""
170  self._call_list_for_type_call_list_for_type = []
171  if self._router_router.call_list:
172  for call in self._router_router.call_list:
173  if not call["new"]:
174  continue
175  if self.entity_descriptionentity_description.key == call["type"]:
176  self._call_list_for_type_call_list_for_type.append(call)
177 
178  self._attr_native_value_attr_native_value_attr_native_value = len(self._call_list_for_type_call_list_for_type)
179 
180  @property
181  def extra_state_attributes(self) -> dict[str, Any]:
182  """Return device specific state attributes."""
183  return {
184  dt_util.utc_from_timestamp(call["datetime"]).isoformat(): call["name"]
185  for call in self._call_list_for_type_call_list_for_type
186  }
187 
188 
190  """Representation of a Freebox disk sensor."""
191 
192  def __init__(
193  self,
194  router: FreeboxRouter,
195  disk: dict[str, Any],
196  partition: dict[str, Any],
197  description: SensorEntityDescription,
198  ) -> None:
199  """Initialize a Freebox disk sensor."""
200  super().__init__(router, description)
201  self._disk_id_disk_id = disk["id"]
202  self._partition_id_partition_id = partition["id"]
203  self._attr_name_attr_name = f"{partition['label']} {description.name}"
204  self._attr_unique_id_attr_unique_id_attr_unique_id = (
205  f"{router.mac} {description.key} {disk['id']} {partition['id']}"
206  )
207 
209  identifiers={(DOMAIN, disk["id"])},
210  model=disk["model"],
211  name=f"Disk {disk['id']}",
212  sw_version=disk["firmware"],
213  via_device=(
214  DOMAIN,
215  router.mac,
216  ),
217  )
218 
219  @callback
220  def async_update_state(self) -> None:
221  """Update the Freebox disk sensor."""
222  value = None
223  disk: dict[str, Any] = self._router_router.disks[self._disk_id_disk_id]
224  partition: dict[str, Any] = disk["partitions"][self._partition_id_partition_id]
225  if partition.get("total_bytes"):
226  value = round(partition["free_bytes"] * 100 / partition["total_bytes"], 2)
227  self._attr_native_value_attr_native_value_attr_native_value = value
228 
229 
231  """Representation of a Freebox battery sensor."""
232 
233  _attr_device_class = SensorDeviceClass.BATTERY
234  _attr_native_unit_of_measurement = PERCENTAGE
235 
236  @property
237  def native_value(self) -> int:
238  """Return the current state of the device."""
239  return self.get_valueget_value("signal", "battery")
def get_value(self, str ep_type, str name)
Definition: entity.py:136
None __init__(self, FreeboxRouter router, SensorEntityDescription description)
Definition: sensor.py:162
None __init__(self, FreeboxRouter router, dict[str, Any] disk, dict[str, Any] partition, SensorEntityDescription description)
Definition: sensor.py:198
None __init__(self, FreeboxRouter router, SensorEntityDescription description)
Definition: sensor.py:123
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:64
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103