Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for monitoring the Transmission BitTorrent client API."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from contextlib import suppress
7 from dataclasses import dataclass
8 from typing import Any
9 
10 from transmission_rpc.torrent import Torrent
11 
13  SensorDeviceClass,
14  SensorEntity,
15  SensorEntityDescription,
16 )
17 from homeassistant.const import STATE_IDLE, UnitOfDataRate
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import StateType
22 from homeassistant.helpers.update_coordinator import CoordinatorEntity
23 
24 from . import TransmissionConfigEntry
25 from .const import (
26  DOMAIN,
27  STATE_ATTR_TORRENT_INFO,
28  STATE_DOWNLOADING,
29  STATE_SEEDING,
30  STATE_UP_DOWN,
31  SUPPORTED_ORDER_MODES,
32 )
33 from .coordinator import TransmissionDataUpdateCoordinator
34 
35 MODES: dict[str, list[str] | None] = {
36  "started_torrents": ["downloading"],
37  "completed_torrents": ["seeding"],
38  "paused_torrents": ["stopped"],
39  "active_torrents": [
40  "seeding",
41  "downloading",
42  ],
43  "total_torrents": None,
44 }
45 
46 
47 @dataclass(frozen=True, kw_only=True)
49  """Entity description class for Transmission sensors."""
50 
51  val_func: Callable[[TransmissionDataUpdateCoordinator], StateType]
52  extra_state_attr_func: Callable[[Any], dict[str, str]] | None = None
53 
54 
55 SENSOR_TYPES: tuple[TransmissionSensorEntityDescription, ...] = (
57  key="download",
58  translation_key="download_speed",
59  device_class=SensorDeviceClass.DATA_RATE,
60  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
61  suggested_display_precision=2,
62  suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
63  val_func=lambda coordinator: float(coordinator.data.download_speed),
64  ),
66  key="upload",
67  translation_key="upload_speed",
68  device_class=SensorDeviceClass.DATA_RATE,
69  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
70  suggested_display_precision=2,
71  suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
72  val_func=lambda coordinator: float(coordinator.data.upload_speed),
73  ),
75  key="status",
76  translation_key="transmission_status",
77  device_class=SensorDeviceClass.ENUM,
78  options=[STATE_IDLE, STATE_UP_DOWN, STATE_SEEDING, STATE_DOWNLOADING],
79  val_func=lambda coordinator: get_state(
80  coordinator.data.upload_speed, coordinator.data.download_speed
81  ),
82  ),
84  key="active_torrents",
85  translation_key="active_torrents",
86  val_func=lambda coordinator: coordinator.data.active_torrent_count,
87  extra_state_attr_func=lambda coordinator: _torrents_info_attr(
88  coordinator=coordinator, key="active_torrents"
89  ),
90  ),
92  key="paused_torrents",
93  translation_key="paused_torrents",
94  val_func=lambda coordinator: coordinator.data.paused_torrent_count,
95  extra_state_attr_func=lambda coordinator: _torrents_info_attr(
96  coordinator=coordinator, key="paused_torrents"
97  ),
98  ),
100  key="total_torrents",
101  translation_key="total_torrents",
102  val_func=lambda coordinator: coordinator.data.torrent_count,
103  extra_state_attr_func=lambda coordinator: _torrents_info_attr(
104  coordinator=coordinator, key="total_torrents"
105  ),
106  ),
108  key="completed_torrents",
109  translation_key="completed_torrents",
110  val_func=lambda coordinator: len(
111  _filter_torrents(coordinator.torrents, MODES["completed_torrents"])
112  ),
113  extra_state_attr_func=lambda coordinator: _torrents_info_attr(
114  coordinator=coordinator, key="completed_torrents"
115  ),
116  ),
118  key="started_torrents",
119  translation_key="started_torrents",
120  val_func=lambda coordinator: len(
121  _filter_torrents(coordinator.torrents, MODES["started_torrents"])
122  ),
123  extra_state_attr_func=lambda coordinator: _torrents_info_attr(
124  coordinator=coordinator, key="started_torrents"
125  ),
126  ),
127 )
128 
129 
131  hass: HomeAssistant,
132  config_entry: TransmissionConfigEntry,
133  async_add_entities: AddEntitiesCallback,
134 ) -> None:
135  """Set up the Transmission sensors."""
136 
137  coordinator = config_entry.runtime_data
138 
140  TransmissionSensor(coordinator, description) for description in SENSOR_TYPES
141  )
142 
143 
145  CoordinatorEntity[TransmissionDataUpdateCoordinator], SensorEntity
146 ):
147  """A base class for all Transmission sensors."""
148 
149  entity_description: TransmissionSensorEntityDescription
150  _attr_has_entity_name = True
151 
152  def __init__(
153  self,
154  coordinator: TransmissionDataUpdateCoordinator,
155  entity_description: TransmissionSensorEntityDescription,
156  ) -> None:
157  """Initialize the sensor."""
158  super().__init__(coordinator)
159  self.entity_descriptionentity_description = entity_description
160  self._attr_unique_id_attr_unique_id = (
161  f"{coordinator.config_entry.entry_id}-{entity_description.key}"
162  )
163  self._attr_device_info_attr_device_info = DeviceInfo(
164  entry_type=DeviceEntryType.SERVICE,
165  identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
166  manufacturer="Transmission",
167  )
168 
169  @property
170  def native_value(self) -> StateType:
171  """Return the value of the sensor."""
172  return self.entity_descriptionentity_description.val_func(self.coordinator)
173 
174  @property
175  def extra_state_attributes(self) -> dict[str, Any] | None:
176  """Return the state attributes, if any."""
177  if attr_func := self.entity_descriptionentity_description.extra_state_attr_func:
178  return attr_func(self.coordinator)
179  return None
180 
181 
182 def get_state(upload: int, download: int) -> str:
183  """Get current download/upload state."""
184  if upload > 0 and download > 0:
185  return STATE_UP_DOWN
186  if upload > 0 and download == 0:
187  return STATE_SEEDING
188  if upload == 0 and download > 0:
189  return STATE_DOWNLOADING
190  return STATE_IDLE
191 
192 
194  torrents: list[Torrent], statuses: list[str] | None = None
195 ) -> list[Torrent]:
196  return [
197  torrent
198  for torrent in torrents
199  if statuses is None or torrent.status in statuses
200  ]
201 
202 
204  coordinator: TransmissionDataUpdateCoordinator, key: str
205 ) -> dict[str, Any]:
206  infos = {}
207  torrents = _filter_torrents(coordinator.torrents, MODES[key])
208  torrents = SUPPORTED_ORDER_MODES[coordinator.order](torrents)
209  for torrent in torrents[: coordinator.limit]:
210  info = infos[torrent.name] = {
211  "added_date": torrent.added_date,
212  "percent_done": f"{torrent.percent_done * 100:.2f}",
213  "status": torrent.status,
214  "id": torrent.id,
215  }
216  with suppress(ValueError):
217  info["eta"] = str(torrent.eta)
218  return {STATE_ATTR_TORRENT_INFO: infos}
None __init__(self, TransmissionDataUpdateCoordinator coordinator, TransmissionSensorEntityDescription entity_description)
Definition: sensor.py:156
list[Torrent] _filter_torrents(list[Torrent] torrents, list[str]|None statuses=None)
Definition: sensor.py:195
str get_state(int upload, int download)
Definition: sensor.py:182
dict[str, Any] _torrents_info_attr(TransmissionDataUpdateCoordinator coordinator, str key)
Definition: sensor.py:205
None async_setup_entry(HomeAssistant hass, TransmissionConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:134