Add QBittorent switch to control alternative speed (#107637)

* Fix key in strings.json for current_status in QBittorrent

* Add switch on QBittorent to control alternative speed

* Add switch file to .coveragerc

* Fix some typo

* Use coordinator for switch

* Update to mach new lib

* Import annotation

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Remove quoted coordinator

* Revert "Fix key in strings.json for current_status in QBittorrent"

This reverts commit 962fd0474f0c9d6053bcf34898f68e48cf2bb715.

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Sébastien Clément 2024-07-30 11:23:55 +02:00 committed by GitHub
parent d825ac346e
commit d78acd480a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 3 deletions

View File

@ -34,7 +34,7 @@ _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
PLATFORMS = [Platform.SENSOR]
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
CONF_ENTRY = "entry"

View File

@ -30,6 +30,7 @@ class QBittorrentDataCoordinator(DataUpdateCoordinator[SyncMainDataDictionary]):
def __init__(self, hass: HomeAssistant, client: Client) -> None:
"""Initialize coordinator."""
self.client = client
self._is_alternative_mode_enabled = False
# self.main_data: dict[str, int] = {}
self.total_torrents: dict[str, int] = {}
self.active_torrents: dict[str, int] = {}
@ -47,7 +48,13 @@ class QBittorrentDataCoordinator(DataUpdateCoordinator[SyncMainDataDictionary]):
async def _async_update_data(self) -> SyncMainDataDictionary:
try:
return await self.hass.async_add_executor_job(self.client.sync_maindata)
data = await self.hass.async_add_executor_job(self.client.sync_maindata)
self._is_alternative_mode_enabled = (
await self.hass.async_add_executor_job(
self.client.transfer_speed_limits_mode
)
== "1"
)
except (LoginFailed, Forbidden403Error) as exc:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="login_error"
@ -56,6 +63,19 @@ class QBittorrentDataCoordinator(DataUpdateCoordinator[SyncMainDataDictionary]):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="cannot_connect"
) from exc
return data
def set_alt_speed_enabled(self, is_enabled: bool) -> None:
"""Set the alternative speed mode."""
self.client.transfer_toggle_speed_limits_mode(is_enabled)
def toggle_alt_speed_enabled(self) -> None:
"""Toggle the alternative speed mode."""
self.client.transfer_toggle_speed_limits_mode()
def get_alt_speed_enabled(self) -> bool:
"""Get the alternative speed mode."""
return self._is_alternative_mode_enabled
async def get_torrents(self, torrent_filter: TorrentStatusesT) -> TorrentInfoList:
"""Async method to get QBittorrent torrents."""

View File

@ -8,7 +8,6 @@ from qbittorrentapi import Client, TorrentDictionary, TorrentInfoList
def setup_client(url: str, username: str, password: str, verify_ssl: bool) -> Client:
"""Create a qBittorrent client."""
client = Client(
url, username=username, password=password, VERIFY_WEBUI_CERTIFICATE=verify_ssl
)

View File

@ -47,6 +47,11 @@
"all_torrents": {
"name": "All torrents"
}
},
"switch": {
"alternative_speed": {
"name": "Alternative speed"
}
}
},
"services": {

View File

@ -0,0 +1,104 @@
"""Support for monitoring the qBittorrent API."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import QBittorrentDataCoordinator
@dataclass(frozen=True, kw_only=True)
class QBittorrentSwitchEntityDescription(SwitchEntityDescription):
"""Describes qBittorren switch."""
is_on_func: Callable[[QBittorrentDataCoordinator], bool]
turn_on_fn: Callable[[QBittorrentDataCoordinator], None]
turn_off_fn: Callable[[QBittorrentDataCoordinator], None]
toggle_func: Callable[[QBittorrentDataCoordinator], None]
SWITCH_TYPES: tuple[QBittorrentSwitchEntityDescription, ...] = (
QBittorrentSwitchEntityDescription(
key="alternative_speed",
translation_key="alternative_speed",
icon="mdi:speedometer-slow",
is_on_func=lambda coordinator: coordinator.get_alt_speed_enabled(),
turn_on_fn=lambda coordinator: coordinator.set_alt_speed_enabled(True),
turn_off_fn=lambda coordinator: coordinator.set_alt_speed_enabled(False),
toggle_func=lambda coordinator: coordinator.toggle_alt_speed_enabled(),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up qBittorrent switch entries."""
coordinator: QBittorrentDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
QBittorrentSwitch(coordinator, config_entry, description)
for description in SWITCH_TYPES
)
class QBittorrentSwitch(CoordinatorEntity[QBittorrentDataCoordinator], SwitchEntity):
"""Representation of a qBittorrent switch."""
_attr_has_entity_name = True
entity_description: QBittorrentSwitchEntityDescription
def __init__(
self,
coordinator: QBittorrentDataCoordinator,
config_entry: ConfigEntry,
entity_description: QBittorrentSwitchEntityDescription,
) -> None:
"""Initialize qBittorrent switch."""
super().__init__(coordinator)
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 is_on(self) -> bool:
"""Return true if device is on."""
return self.entity_description.is_on_func(self.coordinator)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on this switch."""
await self.hass.async_add_executor_job(
self.entity_description.turn_on_fn, self.coordinator
)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off this switch."""
await self.hass.async_add_executor_job(
self.entity_description.turn_off_fn, self.coordinator
)
await self.coordinator.async_request_refresh()
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the device."""
await self.hass.async_add_executor_job(
self.entity_description.toggle_func, self.coordinator
)
await self.coordinator.async_request_refresh()