Improve the transmission integration (#34223)

* Update state after adding a new torrent

* Use cached torrents list in check_started_torrent_info

* Add torrent_info to all sensors

* Add torrent_info for active torrents

* Fix typo

* Update codeowners

* Do not set eta if it's unknown

* Fix codeowners

* Extract TransmissionSpeedSensor

* Extract TransmissionStatusSensor

* Extract TransmissionTorrentsSensor

* Refactor device_state_attributes() and update()

* Remove unused methods

* Use async_on_remove

* Fix sensor update

* Add transmission.remove_torrent service

* Add transmission_removed_torrent event

* Fix naming

* Fix typo in services.yaml
This commit is contained in:
Gleb Sinyavskiy 2020-04-20 15:07:26 +02:00 committed by GitHub
parent c87ecf0ff6
commit 9062d6e5e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 182 additions and 130 deletions

View file

@ -1,12 +1,12 @@
"""Support for monitoring the Transmission BitTorrent client API."""
import logging
from homeassistant.const import CONF_NAME, STATE_IDLE
from homeassistant.const import CONF_NAME, DATA_RATE_MEGABYTES_PER_SECOND, STATE_IDLE
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, SENSOR_TYPES, STATE_ATTR_TORRENT_INFO
from .const import DOMAIN, STATE_ATTR_TORRENT_INFO
_LOGGER = logging.getLogger(__name__)
@ -17,41 +17,35 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
tm_client = hass.data[DOMAIN][config_entry.entry_id]
name = config_entry.data[CONF_NAME]
dev = []
for sensor_type in SENSOR_TYPES:
dev.append(
TransmissionSensor(
sensor_type,
tm_client,
name,
SENSOR_TYPES[sensor_type][0],
SENSOR_TYPES[sensor_type][1],
)
)
dev = [
TransmissionSpeedSensor(tm_client, name, "Down Speed", "download"),
TransmissionSpeedSensor(tm_client, name, "Up Speed", "upload"),
TransmissionStatusSensor(tm_client, name, "Status"),
TransmissionTorrentsSensor(tm_client, name, "Active Torrents", "active"),
TransmissionTorrentsSensor(tm_client, name, "Paused Torrents", "paused"),
TransmissionTorrentsSensor(tm_client, name, "Total Torrents", "total"),
TransmissionTorrentsSensor(tm_client, name, "Completed Torrents", "completed"),
TransmissionTorrentsSensor(tm_client, name, "Started Torrents", "started"),
]
async_add_entities(dev, True)
class TransmissionSensor(Entity):
"""Representation of a Transmission sensor."""
"""A base class for all Transmission sensors."""
def __init__(
self, sensor_type, tm_client, client_name, sensor_name, unit_of_measurement
):
def __init__(self, tm_client, client_name, sensor_name, sub_type=None):
"""Initialize the sensor."""
self._name = sensor_name
self._state = None
self._tm_client = tm_client
self._unit_of_measurement = unit_of_measurement
self._data = None
self.client_name = client_name
self.type = sensor_type
self.unsub_update = None
self._client_name = client_name
self._name = sensor_name
self._sub_type = sub_type
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return f"{self.client_name} {self._name}"
return f"{self._client_name} {self._name}"
@property
def unique_id(self):
@ -68,77 +62,109 @@ class TransmissionSensor(Entity):
"""Return the polling requirement for this sensor."""
return False
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def available(self):
"""Could the device be accessed during the last update call."""
return self._tm_client.api.available
@property
def device_state_attributes(self):
"""Return the state attributes, if any."""
if self._tm_client.api.started_torrent_dict and self.type == "started_torrents":
return {STATE_ATTR_TORRENT_INFO: self._tm_client.api.started_torrent_dict}
return None
async def async_added_to_hass(self):
"""Handle entity which will be added."""
self.unsub_update = async_dispatcher_connect(
self.hass,
self._tm_client.api.signal_update,
self._schedule_immediate_update,
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self.async_on_remove(
async_dispatcher_connect(
self.hass, self._tm_client.api.signal_update, update
)
)
@callback
def _schedule_immediate_update(self):
self.async_schedule_update_ha_state(True)
async def will_remove_from_hass(self):
"""Unsubscribe from update dispatcher."""
if self.unsub_update:
self.unsub_update()
self.unsub_update = None
class TransmissionSpeedSensor(TransmissionSensor):
"""Representation of a Transmission speed sensor."""
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return DATA_RATE_MEGABYTES_PER_SECOND
def update(self):
"""Get the latest data from Transmission and updates the state."""
self._data = self._tm_client.api.data
data = self._tm_client.api.data
if data:
mb_spd = (
float(data.downloadSpeed)
if self._sub_type == "download"
else float(data.uploadSpeed)
)
mb_spd = mb_spd / 1024 / 1024
self._state = round(mb_spd, 2 if mb_spd < 0.1 else 1)
if self.type == "completed_torrents":
self._state = self._tm_client.api.get_completed_torrent_count()
elif self.type == "started_torrents":
self._state = self._tm_client.api.get_started_torrent_count()
if self.type == "current_status":
if self._data:
upload = self._data.uploadSpeed
download = self._data.downloadSpeed
if upload > 0 and download > 0:
self._state = "Up/Down"
elif upload > 0 and download == 0:
self._state = "Seeding"
elif upload == 0 and download > 0:
self._state = "Downloading"
else:
self._state = STATE_IDLE
class TransmissionStatusSensor(TransmissionSensor):
"""Representation of a Transmission status sensor."""
def update(self):
"""Get the latest data from Transmission and updates the state."""
data = self._tm_client.api.data
if data:
upload = data.uploadSpeed
download = data.downloadSpeed
if upload > 0 and download > 0:
self._state = "Up/Down"
elif upload > 0 and download == 0:
self._state = "Seeding"
elif upload == 0 and download > 0:
self._state = "Downloading"
else:
self._state = None
self._state = STATE_IDLE
else:
self._state = None
if self._data:
if self.type == "download_speed":
mb_spd = float(self._data.downloadSpeed)
mb_spd = mb_spd / 1024 / 1024
self._state = round(mb_spd, 2 if mb_spd < 0.1 else 1)
elif self.type == "upload_speed":
mb_spd = float(self._data.uploadSpeed)
mb_spd = mb_spd / 1024 / 1024
self._state = round(mb_spd, 2 if mb_spd < 0.1 else 1)
elif self.type == "active_torrents":
self._state = self._data.activeTorrentCount
elif self.type == "paused_torrents":
self._state = self._data.pausedTorrentCount
elif self.type == "total_torrents":
self._state = self._data.torrentCount
class TransmissionTorrentsSensor(TransmissionSensor):
"""Representation of a Transmission torrents sensor."""
SUBTYPE_MODES = {
"started": ("downloading"),
"completed": ("seeding"),
"paused": ("stopped"),
"active": ("seeding", "downloading"),
"total": None,
}
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return "Torrents"
@property
def device_state_attributes(self):
"""Return the state attributes, if any."""
info = _torrents_info(
self._tm_client.api.torrents, self.SUBTYPE_MODES[self._sub_type]
)
return {STATE_ATTR_TORRENT_INFO: info}
def update(self):
"""Get the latest data from Transmission and updates the state."""
self._state = len(self.device_state_attributes[STATE_ATTR_TORRENT_INFO])
def _torrents_info(torrents, statuses=None):
infos = {}
for torrent in torrents:
if statuses is None or torrent.status in statuses:
info = infos[torrent.name] = {
"added_date": torrent.addedDate,
"percent_done": f"{torrent.percentDone * 100:.2f}",
"status": torrent.status,
"id": torrent.id,
}
try:
info["eta"] = str(torrent.eta)
except ValueError:
pass
return infos