Add protocol upload / download sensors to Deluge (#119203)
* 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 mypypull/127331/head
parent
3184951625
commit
c265c91ef2
|
@ -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"
|
||||
|
|
|
@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
|
|||
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
|
||||
|
@ -46,7 +46,7 @@ class DelugeDataUpdateCoordinator(
|
|||
_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(
|
||||
|
|
|
@ -18,16 +18,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
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:
|
||||
|
@ -35,7 +39,20 @@ def get_state(data: dict[str, float], key: str) -> str | float:
|
|||
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)
|
||||
|
||||
|
||||
|
@ -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, DOWNLOAD_SPEED),
|
||||
value=lambda data: get_state(
|
||||
data, DelugeSensorType.DOWNLOAD_SPEED_SENSOR.value
|
||||
),
|
||||
),
|
||||
DelugeSensorEntityDescription(
|
||||
key=UPLOAD_SPEED,
|
||||
translation_key="upload_speed",
|
||||
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, UPLOAD_SPEED),
|
||||
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, DelugeSensorType.PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR.value
|
||||
),
|
||||
),
|
||||
DelugeSensorEntityDescription(
|
||||
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, DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR.value
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -14,3 +14,10 @@ CONF_DATA = {
|
|||
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,
|
||||
}
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue