1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-10 08:15:14 +01:00

Add port forwarding binary sensor to Transmission (#166108)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Eniot
2026-03-24 19:44:24 +01:00
committed by GitHub
parent dec6f955f3
commit d488bdad8a
8 changed files with 168 additions and 4 deletions
@@ -40,7 +40,7 @@ from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
MIGRATION_NAME_TO_KEY = {
# Sensors
@@ -139,10 +139,8 @@ async def async_migrate_entry(
)
if config_entry.version == 1:
# Version 1.2 adds ssl and path
if config_entry.minor_version < 2:
new = {**config_entry.data}
new[CONF_PATH] = DEFAULT_PATH
new[CONF_SSL] = DEFAULT_SSL
@@ -0,0 +1,64 @@
"""Binary sensor platform for Transmission integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
from .entity import TransmissionEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class TransmissionBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describe a Transmission binary sensor entity."""
is_on_fn: Callable[[TransmissionDataUpdateCoordinator], bool | None]
BINARY_SENSOR_TYPES: tuple[TransmissionBinarySensorEntityDescription, ...] = (
TransmissionBinarySensorEntityDescription(
key="port_forwarding",
translation_key="port_forwarding",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
is_on_fn=lambda coordinator: coordinator.port_forwarding,
entity_registry_enabled_default=False,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: TransmissionConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Transmission binary sensors from a config entry."""
coordinator = config_entry.runtime_data
async_add_entities(
TransmissionBinarySensor(coordinator, description)
for description in BINARY_SENSOR_TYPES
)
class TransmissionBinarySensor(TransmissionEntity, BinarySensorEntity):
"""Representation of a Transmission binary sensor."""
entity_description: TransmissionBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""Return True if the port is open."""
return self.entity_description.is_on_fn(self.coordinator)
@@ -45,6 +45,7 @@ class TransmissionDataUpdateCoordinator(DataUpdateCoordinator[SessionStats]):
self.api = api
self.host = entry.data[CONF_HOST]
self._session: transmission_rpc.Session | None = None
self.port_forwarding: bool | None = None
self._all_torrents: list[transmission_rpc.Torrent] = []
self._completed_torrents: list[transmission_rpc.Torrent] = []
self._started_torrents: list[transmission_rpc.Torrent] = []
@@ -77,6 +78,7 @@ class TransmissionDataUpdateCoordinator(DataUpdateCoordinator[SessionStats]):
data = self.api.session_stats()
self.torrents = self.api.get_torrents()
self._session = self.api.get_session()
self.port_forwarding = self.api.port_test()
except transmission_rpc.TransmissionError as err:
raise UpdateFailed("Unable to connect to Transmission client") from err
@@ -179,5 +181,4 @@ class TransmissionDataUpdateCoordinator(DataUpdateCoordinator[SessionStats]):
"""Get the alternative speed flag."""
if self._session is None:
return None
return self._session.alt_speed_enabled
@@ -1,5 +1,13 @@
{
"entity": {
"binary_sensor": {
"port_forwarding": {
"default": "mdi:shield-check",
"state": {
"off": "mdi:shield-off"
}
}
},
"sensor": {
"active_torrents": {
"default": "mdi:counter"
@@ -41,6 +41,15 @@
}
},
"entity": {
"binary_sensor": {
"port_forwarding": {
"name": "Port forwarding",
"state": {
"off": "Closed",
"on": "Open"
}
}
},
"sensor": {
"active_torrents": {
"name": "Active torrents",
@@ -62,6 +62,7 @@ def mock_transmission_client() -> Generator[AsyncMock]:
client.get_session.return_value = Session(fields=session_data)
client.get_torrents.return_value = []
client.port_test.return_value = True
yield mock_client_class
@@ -0,0 +1,52 @@
# serializer version: 1
# name: test_binary_sensors[binary_sensor.transmission_port_forwarding-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.transmission_port_forwarding',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Port forwarding',
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.CONNECTIVITY: 'connectivity'>,
'original_icon': None,
'original_name': 'Port forwarding',
'platform': 'transmission',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'port_forwarding',
'unique_id': '01J0BC4QM2YBRP6H5G933AETT7-port_forwarding',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.transmission_port_forwarding-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'connectivity',
'friendly_name': 'Transmission Port forwarding',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.transmission_port_forwarding',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
@@ -0,0 +1,31 @@
"""Tests for the Transmission binary sensor platform."""
from unittest.mock import AsyncMock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_binary_sensors(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_transmission_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the binary sensor entities."""
with patch(
"homeassistant.components.transmission.PLATFORMS", [Platform.BINARY_SENSOR]
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)