mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add qBittorrent torrent sensors (#105781)
* Upgrade QBittorrent integration to show torrents This brings the QBittorrent integration to be more in line with the Transmission integration. It updates how the integration is written, along with adding sensors for Active Torrents, Inactive Torrents, Paused Torrents, Total Torrents, Seeding Torrents, Started Torrents. * Remove unused stuff * Fix codeowners * Correct name in comments * Update __init__.py * Make get torrents a service with a response * Update sensor.py * Update sensor.py * Update sensor.py * Add new sensors * remove service * more removes * more * Address comments * cleanup * Update coordinator.py * Fix most lint issues * Update sensor.py * Update sensor.py * Update manifest.json * Update sensor class * Update sensor.py * Fix lint issue with sensor class * Adding codeowners * Update homeassistant/components/qbittorrent/__init__.py --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
c7eab49c70
commit
d33ad57dd3
@ -1020,8 +1020,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/pvoutput/ @frenck
|
||||
/homeassistant/components/pvpc_hourly_pricing/ @azogue
|
||||
/tests/components/pvpc_hourly_pricing/ @azogue
|
||||
/homeassistant/components/qbittorrent/ @geoffreylagaisse
|
||||
/tests/components/qbittorrent/ @geoffreylagaisse
|
||||
/homeassistant/components/qbittorrent/ @geoffreylagaisse @finder39
|
||||
/tests/components/qbittorrent/ @geoffreylagaisse @finder39
|
||||
/homeassistant/components/qingping/ @bdraco @skgsergio
|
||||
/tests/components/qingping/ @bdraco @skgsergio
|
||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||
|
@ -19,21 +19,21 @@ from .const import DOMAIN
|
||||
from .coordinator import QBittorrentDataCoordinator
|
||||
from .helpers import setup_client
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up qBittorrent from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
try:
|
||||
client = await hass.async_add_executor_job(
|
||||
setup_client,
|
||||
entry.data[CONF_URL],
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
entry.data[CONF_VERIFY_SSL],
|
||||
config_entry.data[CONF_URL],
|
||||
config_entry.data[CONF_USERNAME],
|
||||
config_entry.data[CONF_PASSWORD],
|
||||
config_entry.data[CONF_VERIFY_SSL],
|
||||
)
|
||||
except LoginRequired as err:
|
||||
raise ConfigEntryNotReady("Invalid credentials") from err
|
||||
@ -42,16 +42,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = QBittorrentDataCoordinator(hass, client)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload qBittorrent config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
):
|
||||
del hass.data[DOMAIN][config_entry.entry_id]
|
||||
if not hass.data[DOMAIN]:
|
||||
del hass.data[DOMAIN]
|
||||
return unload_ok
|
||||
|
@ -5,3 +5,7 @@ DOMAIN: Final = "qbittorrent"
|
||||
|
||||
DEFAULT_NAME = "qBittorrent"
|
||||
DEFAULT_URL = "http://127.0.0.1:8080"
|
||||
|
||||
STATE_UP_DOWN = "up_down"
|
||||
STATE_SEEDING = "seeding"
|
||||
STATE_DOWNLOADING = "downloading"
|
||||
|
@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QBittorrentDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""QBittorrent update coordinator."""
|
||||
"""Coordinator for updating QBittorrent data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: Client) -> None:
|
||||
"""Initialize coordinator."""
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "qbittorrent",
|
||||
"name": "qBittorrent",
|
||||
"codeowners": ["@geoffreylagaisse"],
|
||||
"codeowners": ["@geoffreylagaisse", "@finder39"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/qbittorrent",
|
||||
"integration_type": "service",
|
||||
|
@ -4,22 +4,21 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_IDLE, UnitOfDataRate
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, STATE_DOWNLOADING, STATE_SEEDING, STATE_UP_DOWN
|
||||
from .coordinator import QBittorrentDataCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -27,62 +26,94 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_TYPE_CURRENT_STATUS = "current_status"
|
||||
SENSOR_TYPE_DOWNLOAD_SPEED = "download_speed"
|
||||
SENSOR_TYPE_UPLOAD_SPEED = "upload_speed"
|
||||
SENSOR_TYPE_ALL_TORRENTS = "all_torrents"
|
||||
SENSOR_TYPE_PAUSED_TORRENTS = "paused_torrents"
|
||||
SENSOR_TYPE_ACTIVE_TORRENTS = "active_torrents"
|
||||
SENSOR_TYPE_INACTIVE_TORRENTS = "inactive_torrents"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class QBittorrentMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class QBittorrentSensorEntityDescription(SensorEntityDescription, QBittorrentMixin):
|
||||
"""Describes QBittorrent sensor entity."""
|
||||
|
||||
|
||||
def _get_qbittorrent_state(data: dict[str, Any]) -> str:
|
||||
download = data["server_state"]["dl_info_speed"]
|
||||
upload = data["server_state"]["up_info_speed"]
|
||||
def get_state(coordinator: QBittorrentDataCoordinator) -> str:
|
||||
"""Get current download/upload state."""
|
||||
upload = coordinator.data["server_state"]["up_info_speed"]
|
||||
download = coordinator.data["server_state"]["dl_info_speed"]
|
||||
|
||||
if upload > 0 and download > 0:
|
||||
return "up_down"
|
||||
return STATE_UP_DOWN
|
||||
if upload > 0 and download == 0:
|
||||
return "seeding"
|
||||
return STATE_SEEDING
|
||||
if upload == 0 and download > 0:
|
||||
return "downloading"
|
||||
return STATE_DOWNLOADING
|
||||
return STATE_IDLE
|
||||
|
||||
|
||||
def format_speed(speed):
|
||||
"""Return a bytes/s measurement as a human readable string."""
|
||||
kb_spd = float(speed) / 1024
|
||||
return round(kb_spd, 2 if kb_spd < 0.1 else 1)
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class QBittorrentSensorEntityDescription(SensorEntityDescription):
|
||||
"""Entity description class for qBittorent sensors."""
|
||||
|
||||
value_fn: Callable[[QBittorrentDataCoordinator], StateType]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = (
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_CURRENT_STATUS,
|
||||
name="Status",
|
||||
value_fn=_get_qbittorrent_state,
|
||||
translation_key="current_status",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[STATE_IDLE, STATE_UP_DOWN, STATE_SEEDING, STATE_DOWNLOADING],
|
||||
value_fn=get_state,
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_DOWNLOAD_SPEED,
|
||||
name="Down Speed",
|
||||
translation_key="download_speed",
|
||||
icon="mdi:cloud-download",
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
native_unit_of_measurement=UnitOfDataRate.KIBIBYTES_PER_SECOND,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: format_speed(data["server_state"]["dl_info_speed"]),
|
||||
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
||||
suggested_display_precision=2,
|
||||
suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
|
||||
value_fn=lambda coordinator: float(
|
||||
coordinator.data["server_state"]["dl_info_speed"]
|
||||
),
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_UPLOAD_SPEED,
|
||||
name="Up Speed",
|
||||
translation_key="upload_speed",
|
||||
icon="mdi:cloud-upload",
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
native_unit_of_measurement=UnitOfDataRate.KIBIBYTES_PER_SECOND,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: format_speed(data["server_state"]["up_info_speed"]),
|
||||
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
||||
suggested_display_precision=2,
|
||||
suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
|
||||
value_fn=lambda coordinator: float(
|
||||
coordinator.data["server_state"]["up_info_speed"]
|
||||
),
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_ALL_TORRENTS,
|
||||
translation_key="all_torrents",
|
||||
native_unit_of_measurement="torrents",
|
||||
value_fn=lambda coordinator: count_torrents_in_states(coordinator, []),
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_ACTIVE_TORRENTS,
|
||||
translation_key="active_torrents",
|
||||
native_unit_of_measurement="torrents",
|
||||
value_fn=lambda coordinator: count_torrents_in_states(
|
||||
coordinator, ["downloading", "uploading"]
|
||||
),
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_INACTIVE_TORRENTS,
|
||||
translation_key="inactive_torrents",
|
||||
native_unit_of_measurement="torrents",
|
||||
value_fn=lambda coordinator: count_torrents_in_states(
|
||||
coordinator, ["stalledDL", "stalledUP"]
|
||||
),
|
||||
),
|
||||
QBittorrentSensorEntityDescription(
|
||||
key=SENSOR_TYPE_PAUSED_TORRENTS,
|
||||
translation_key="paused_torrents",
|
||||
native_unit_of_measurement="torrents",
|
||||
value_fn=lambda coordinator: count_torrents_in_states(
|
||||
coordinator, ["pausedDL", "pausedUP"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@ -90,36 +121,54 @@ SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = (
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entites: AddEntitiesCallback,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up qBittorrent sensor entries."""
|
||||
|
||||
coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
entities = [
|
||||
QBittorrentSensor(description, coordinator, config_entry)
|
||||
|
||||
async_add_entities(
|
||||
QBittorrentSensor(coordinator, config_entry, description)
|
||||
for description in SENSOR_TYPES
|
||||
]
|
||||
async_add_entites(entities)
|
||||
)
|
||||
|
||||
|
||||
class QBittorrentSensor(CoordinatorEntity[QBittorrentDataCoordinator], SensorEntity):
|
||||
"""Representation of a qBittorrent sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: QBittorrentSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: QBittorrentSensorEntityDescription,
|
||||
coordinator: QBittorrentDataCoordinator,
|
||||
config_entry: ConfigEntry,
|
||||
entity_description: QBittorrentSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the qBittorrent sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{config_entry.entry_id}-{description.key}"
|
||||
self._attr_name = f"{config_entry.title} {description.name}"
|
||||
self._attr_available = False
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{config_entry.entry_id}-{entity_description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, config_entry.entry_id)},
|
||||
manufacturer="QBittorrent",
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return value of sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
"""Return the value of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator)
|
||||
|
||||
|
||||
def count_torrents_in_states(
|
||||
coordinator: QBittorrentDataCoordinator, states: list[str]
|
||||
) -> int:
|
||||
"""Count the number of torrents in specified states."""
|
||||
return len(
|
||||
[
|
||||
torrent
|
||||
for torrent in coordinator.data["torrents"].values()
|
||||
if torrent["state"] in states
|
||||
]
|
||||
)
|
||||
|
@ -17,5 +17,36 @@
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"download_speed": {
|
||||
"name": "Download speed"
|
||||
},
|
||||
"upload_speed": {
|
||||
"name": "Upload speed"
|
||||
},
|
||||
"transmission_status": {
|
||||
"name": "Status",
|
||||
"state": {
|
||||
"idle": "[%key:common::state::idle%]",
|
||||
"up_down": "Up/Down",
|
||||
"seeding": "Seeding",
|
||||
"downloading": "Downloading"
|
||||
}
|
||||
},
|
||||
"active_torrents": {
|
||||
"name": "Active torrents"
|
||||
},
|
||||
"inactive_torrents": {
|
||||
"name": "Inactive torrents"
|
||||
},
|
||||
"paused_torrents": {
|
||||
"name": "Paused torrents"
|
||||
},
|
||||
"all_torrents": {
|
||||
"name": "All torrents"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user