Skip to content

Commit

Permalink
Add protocol upload / download sensors to Deluge (#119203)
Browse files Browse the repository at this point in the history
* Add Protocol Upload/Download for Deluge

* add unit test and fix typo in sensor.py

* remove unneeded import

* rename/unify the translation keys and entries in const.py

* split out const.py items into DelugeSensorType to avoid confusion with DelugeGetSessionStatusKeys

* change DelugeGetSessionStatusKeys to be a regular enum to satisfy mypy
  • Loading branch information
mgrandi authored Oct 2, 2024
1 parent 3184951 commit c265c91
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 20 deletions.
38 changes: 33 additions & 5 deletions homeassistant/components/deluge/const.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
"""Constants for the Deluge integration."""

import enum
import logging
from typing import Final

CONF_WEB_PORT = "web_port"
CURRENT_STATUS = "current_status"
DATA_KEYS = ["upload_rate", "download_rate", "dht_upload_rate", "dht_download_rate"]
DEFAULT_NAME = "Deluge"
DEFAULT_RPC_PORT = 58846
DEFAULT_WEB_PORT = 8112
DOMAIN: Final = "deluge"
DOWNLOAD_SPEED = "download_speed"

LOGGER = logging.getLogger(__package__)

UPLOAD_SPEED = "upload_speed"

class DelugeGetSessionStatusKeys(enum.Enum):
"""Enum representing the keys that get passed into the Deluge RPC `core.get_session_status` xml rpc method.
You can call `core.get_session_status` with no keys (so an empty list in deluge-client.DelugeRPCClient.call)
to get the full list of possible keys, but it seems to basically be a all of the session statistics
listed on this page: https://www.rasterbar.com/products/libtorrent/manual-ref.html#session-statistics
and a few others
there is also a list of deprecated keys that deluge will translate for you and issue a warning in the log:
https://github.com/deluge-torrent/deluge/blob/7f3f7f69ee78610e95bea07d99f699e9310c4e08/deluge/core/core.py#L58
"""

DHT_DOWNLOAD_RATE = "dht_download_rate"
DHT_UPLOAD_RATE = "dht_upload_rate"
DOWNLOAD_RATE = "download_rate"
UPLOAD_RATE = "upload_rate"


class DelugeSensorType(enum.StrEnum):
"""Enum that distinguishes the different sensor types that the Deluge integration has.
This is mainly used to avoid passing strings around and to distinguish between similarly
named strings in `DelugeGetSessionStatusKeys`.
"""

CURRENT_STATUS_SENSOR = "current_status"
DOWNLOAD_SPEED_SENSOR = "download_speed"
UPLOAD_SPEED_SENSOR = "upload_speed"
PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR = "protocol_traffic_upload_speed"
PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR = "protocol_traffic_download_speed"
4 changes: 2 additions & 2 deletions homeassistant/components/deluge/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DATA_KEYS, LOGGER
from .const import LOGGER, DelugeGetSessionStatusKeys

if TYPE_CHECKING:
from . import DelugeConfigEntry
Expand Down Expand Up @@ -46,7 +46,7 @@ async def _async_update_data(self) -> dict[Platform, dict[str, Any]]:
_data = await self.hass.async_add_executor_job(
self.api.call,
"core.get_session_status",
DATA_KEYS,
[iter_member.value for iter_member in list(DelugeGetSessionStatusKeys)],
)
data[Platform.SENSOR] = {k.decode(): v for k, v in _data.items()}
data[Platform.SWITCH] = await self.hass.async_add_executor_job(
Expand Down
67 changes: 54 additions & 13 deletions homeassistant/components/deluge/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,41 @@
from homeassistant.helpers.typing import StateType

from . import DelugeConfigEntry
from .const import CURRENT_STATUS, DATA_KEYS, DOWNLOAD_SPEED, UPLOAD_SPEED
from .const import DelugeGetSessionStatusKeys, DelugeSensorType
from .coordinator import DelugeDataUpdateCoordinator
from .entity import DelugeEntity


def get_state(data: dict[str, float], key: str) -> str | float:
"""Get current download/upload state."""
upload = data[DATA_KEYS[0]] - data[DATA_KEYS[2]]
download = data[DATA_KEYS[1]] - data[DATA_KEYS[3]]
if key == CURRENT_STATUS:
upload = data[DelugeGetSessionStatusKeys.UPLOAD_RATE.value]
download = data[DelugeGetSessionStatusKeys.DOWNLOAD_RATE.value]
protocol_upload = data[DelugeGetSessionStatusKeys.DHT_UPLOAD_RATE.value]
protocol_download = data[DelugeGetSessionStatusKeys.DHT_DOWNLOAD_RATE.value]

# if key is CURRENT_STATUS, we just return whether we are uploading / downloading / idle
if key == DelugeSensorType.CURRENT_STATUS_SENSOR:
if upload > 0 and download > 0:
return "seeding_and_downloading"
if upload > 0 and download == 0:
return "seeding"
if upload == 0 and download > 0:
return "downloading"
return STATE_IDLE
kb_spd = float(upload if key == UPLOAD_SPEED else download) / 1024

# if not, return the transfer rate for the given key
rate = 0.0
if key == DelugeSensorType.DOWNLOAD_SPEED_SENSOR:
rate = download
elif key == DelugeSensorType.UPLOAD_SPEED_SENSOR:
rate = upload
elif key == DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR:
rate = protocol_download
else:
rate = protocol_upload

# convert to KiB/s and round
kb_spd = rate / 1024
return round(kb_spd, 2 if kb_spd < 0.1 else 1)


Expand All @@ -48,27 +65,51 @@ class DelugeSensorEntityDescription(SensorEntityDescription):

SENSOR_TYPES: tuple[DelugeSensorEntityDescription, ...] = (
DelugeSensorEntityDescription(
key=CURRENT_STATUS,
key=DelugeSensorType.CURRENT_STATUS_SENSOR.value,
translation_key="status",
value=lambda data: get_state(data, CURRENT_STATUS),
value=lambda data: get_state(
data, DelugeSensorType.CURRENT_STATUS_SENSOR.value
),
device_class=SensorDeviceClass.ENUM,
options=["seeding_and_downloading", "seeding", "downloading", "idle"],
),
DelugeSensorEntityDescription(
key=DOWNLOAD_SPEED,
translation_key="download_speed",
key=DelugeSensorType.DOWNLOAD_SPEED_SENSOR.value,
translation_key=DelugeSensorType.DOWNLOAD_SPEED_SENSOR.value,
device_class=SensorDeviceClass.DATA_RATE,
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: get_state(
data, DelugeSensorType.DOWNLOAD_SPEED_SENSOR.value
),
),
DelugeSensorEntityDescription(
key=DelugeSensorType.UPLOAD_SPEED_SENSOR.value,
translation_key=DelugeSensorType.UPLOAD_SPEED_SENSOR.value,
device_class=SensorDeviceClass.DATA_RATE,
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: get_state(data, DelugeSensorType.UPLOAD_SPEED_SENSOR.value),
),
DelugeSensorEntityDescription(
key=DelugeSensorType.PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR.value,
translation_key=DelugeSensorType.PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR.value,
device_class=SensorDeviceClass.DATA_RATE,
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: get_state(data, DOWNLOAD_SPEED),
value=lambda data: get_state(
data, DelugeSensorType.PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR.value
),
),
DelugeSensorEntityDescription(
key=UPLOAD_SPEED,
translation_key="upload_speed",
key=DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR.value,
translation_key=DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR.value,
device_class=SensorDeviceClass.DATA_RATE,
native_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: get_state(data, UPLOAD_SPEED),
value=lambda data: get_state(
data, DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR.value
),
),
)

Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/deluge/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
"download_speed": {
"name": "Download speed"
},
"protocol_traffic_download_speed": {
"name": "Protocol traffic download speed"
},
"protocol_traffic_upload_speed": {
"name": "Protocol traffic upload speed"
},
"upload_speed": {
"name": "Upload speed"
}
Expand Down
7 changes: 7 additions & 0 deletions tests/components/deluge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@
CONF_PORT: DEFAULT_RPC_PORT,
CONF_WEB_PORT: DEFAULT_WEB_PORT,
}

GET_TORRENT_STATUS_RESPONSE = {
"upload_rate": 3462.0,
"download_rate": 98.5,
"dht_upload_rate": 7818.0,
"dht_download_rate": 2658.0,
}
32 changes: 32 additions & 0 deletions tests/components/deluge/test_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Test Deluge sensor.py methods."""

from homeassistant.components.deluge.const import DelugeSensorType
from homeassistant.components.deluge.sensor import get_state

from . import GET_TORRENT_STATUS_RESPONSE


def test_get_state() -> None:
"""Tests get_state() with different keys."""

download_result = get_state(
GET_TORRENT_STATUS_RESPONSE, DelugeSensorType.DOWNLOAD_SPEED_SENSOR
)
assert download_result == 0.1 # round(98.5 / 1024, 2)

upload_result = get_state(
GET_TORRENT_STATUS_RESPONSE, DelugeSensorType.UPLOAD_SPEED_SENSOR
)
assert upload_result == 3.4 # round(3462.0 / 1024, 1)

protocol_upload_result = get_state(
GET_TORRENT_STATUS_RESPONSE,
DelugeSensorType.PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR,
)
assert protocol_upload_result == 7.6 # round(7818.0 / 1024, 1)

protocol_download_result = get_state(
GET_TORRENT_STATUS_RESPONSE,
DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR,
)
assert protocol_download_result == 2.6 # round(2658.0/1024, 1)

0 comments on commit c265c91

Please sign in to comment.