Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for monitoring the qBittorrent API."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Mapping
6 from dataclasses import dataclass
7 import logging
8 from typing import Any, cast
9 
11  SensorDeviceClass,
12  SensorEntity,
13  SensorEntityDescription,
14  SensorStateClass,
15 )
16 from homeassistant.config_entries import ConfigEntry
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 .const import DOMAIN, STATE_DOWNLOADING, STATE_SEEDING, STATE_UP_DOWN
25 from .coordinator import QBittorrentDataCoordinator
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 SENSOR_TYPE_CURRENT_STATUS = "current_status"
30 SENSOR_TYPE_DOWNLOAD_SPEED = "download_speed"
31 SENSOR_TYPE_UPLOAD_SPEED = "upload_speed"
32 SENSOR_TYPE_ALL_TORRENTS = "all_torrents"
33 SENSOR_TYPE_PAUSED_TORRENTS = "paused_torrents"
34 SENSOR_TYPE_ACTIVE_TORRENTS = "active_torrents"
35 SENSOR_TYPE_INACTIVE_TORRENTS = "inactive_torrents"
36 
37 
38 def get_state(coordinator: QBittorrentDataCoordinator) -> str:
39  """Get current download/upload state."""
40  server_state = cast(Mapping, coordinator.data.get("server_state"))
41  upload = cast(int, server_state.get("up_info_speed"))
42  download = cast(int, server_state.get("dl_info_speed"))
43 
44  if upload > 0 and download > 0:
45  return STATE_UP_DOWN
46  if upload > 0 and download == 0:
47  return STATE_SEEDING
48  if upload == 0 and download > 0:
49  return STATE_DOWNLOADING
50  return STATE_IDLE
51 
52 
53 def get_dl(coordinator: QBittorrentDataCoordinator) -> int:
54  """Get current download speed."""
55  server_state = cast(Mapping, coordinator.data.get("server_state"))
56  return cast(int, server_state.get("dl_info_speed"))
57 
58 
59 def get_up(coordinator: QBittorrentDataCoordinator) -> int:
60  """Get current upload speed."""
61  server_state = cast(Mapping[str, Any], coordinator.data.get("server_state"))
62  return cast(int, server_state.get("up_info_speed"))
63 
64 
65 @dataclass(frozen=True, kw_only=True)
67  """Entity description class for qBittorent sensors."""
68 
69  value_fn: Callable[[QBittorrentDataCoordinator], StateType]
70 
71 
72 SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = (
74  key=SENSOR_TYPE_CURRENT_STATUS,
75  translation_key="current_status",
76  device_class=SensorDeviceClass.ENUM,
77  options=[STATE_IDLE, STATE_UP_DOWN, STATE_SEEDING, STATE_DOWNLOADING],
78  value_fn=get_state,
79  ),
81  key=SENSOR_TYPE_DOWNLOAD_SPEED,
82  translation_key="download_speed",
83  state_class=SensorStateClass.MEASUREMENT,
84  device_class=SensorDeviceClass.DATA_RATE,
85  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
86  suggested_display_precision=2,
87  suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
88  value_fn=get_dl,
89  ),
91  key=SENSOR_TYPE_UPLOAD_SPEED,
92  translation_key="upload_speed",
93  state_class=SensorStateClass.MEASUREMENT,
94  device_class=SensorDeviceClass.DATA_RATE,
95  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
96  suggested_display_precision=2,
97  suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
98  value_fn=get_up,
99  ),
101  key=SENSOR_TYPE_ALL_TORRENTS,
102  translation_key="all_torrents",
103  value_fn=lambda coordinator: count_torrents_in_states(coordinator, []),
104  ),
106  key=SENSOR_TYPE_ACTIVE_TORRENTS,
107  translation_key="active_torrents",
108  value_fn=lambda coordinator: count_torrents_in_states(
109  coordinator, ["downloading", "uploading"]
110  ),
111  ),
113  key=SENSOR_TYPE_INACTIVE_TORRENTS,
114  translation_key="inactive_torrents",
115  value_fn=lambda coordinator: count_torrents_in_states(
116  coordinator, ["stalledDL", "stalledUP"]
117  ),
118  ),
120  key=SENSOR_TYPE_PAUSED_TORRENTS,
121  translation_key="paused_torrents",
122  value_fn=lambda coordinator: count_torrents_in_states(
123  coordinator, ["pausedDL", "pausedUP"]
124  ),
125  ),
126 )
127 
128 
130  hass: HomeAssistant,
131  config_entry: ConfigEntry,
132  async_add_entities: AddEntitiesCallback,
133 ) -> None:
134  """Set up qBittorrent sensor entries."""
135 
136  coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
137 
139  QBittorrentSensor(coordinator, config_entry, description)
140  for description in SENSOR_TYPES
141  )
142 
143 
144 class QBittorrentSensor(CoordinatorEntity[QBittorrentDataCoordinator], SensorEntity):
145  """Representation of a qBittorrent sensor."""
146 
147  _attr_has_entity_name = True
148  entity_description: QBittorrentSensorEntityDescription
149 
150  def __init__(
151  self,
152  coordinator: QBittorrentDataCoordinator,
153  config_entry: ConfigEntry,
154  entity_description: QBittorrentSensorEntityDescription,
155  ) -> None:
156  """Initialize the qBittorrent sensor."""
157  super().__init__(coordinator)
158  self.entity_descriptionentity_description = entity_description
159  self._attr_unique_id_attr_unique_id = f"{config_entry.entry_id}-{entity_description.key}"
160  self._attr_device_info_attr_device_info = DeviceInfo(
161  entry_type=DeviceEntryType.SERVICE,
162  identifiers={(DOMAIN, config_entry.entry_id)},
163  manufacturer="QBittorrent",
164  )
165 
166  @property
167  def native_value(self) -> StateType:
168  """Return the value of the sensor."""
169  return self.entity_descriptionentity_description.value_fn(self.coordinator)
170 
171 
173  coordinator: QBittorrentDataCoordinator, states: list[str]
174 ) -> int:
175  """Count the number of torrents in specified states."""
176  # When torrents are not in the returned data, there are none, return 0.
177  try:
178  torrents = cast(Mapping[str, Mapping], coordinator.data.get("torrents"))
179  if torrents is None:
180  return 0
181 
182  if not states:
183  return len(torrents)
184 
185  return len(
186  [torrent for torrent in torrents.values() if torrent.get("state") in states]
187  )
188  except AttributeError:
189  return 0
None __init__(self, QBittorrentDataCoordinator coordinator, ConfigEntry config_entry, QBittorrentSensorEntityDescription entity_description)
Definition: sensor.py:155
str get_state(QBittorrentDataCoordinator coordinator)
Definition: sensor.py:38
int get_up(QBittorrentDataCoordinator coordinator)
Definition: sensor.py:59
int get_dl(QBittorrentDataCoordinator coordinator)
Definition: sensor.py:53
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:133
int count_torrents_in_states(QBittorrentDataCoordinator coordinator, list[str] states)
Definition: sensor.py:174