diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 56d6a2d5d67a..d32c5e548bb4 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -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 diff --git a/homeassistant/components/transmission/binary_sensor.py b/homeassistant/components/transmission/binary_sensor.py new file mode 100644 index 000000000000..a00291eb3ec2 --- /dev/null +++ b/homeassistant/components/transmission/binary_sensor.py @@ -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) diff --git a/homeassistant/components/transmission/coordinator.py b/homeassistant/components/transmission/coordinator.py index 34d7b48d6210..e47b0171a5cb 100644 --- a/homeassistant/components/transmission/coordinator.py +++ b/homeassistant/components/transmission/coordinator.py @@ -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 diff --git a/homeassistant/components/transmission/icons.json b/homeassistant/components/transmission/icons.json index 20b296e9fc08..3193c0f85aa3 100644 --- a/homeassistant/components/transmission/icons.json +++ b/homeassistant/components/transmission/icons.json @@ -1,5 +1,13 @@ { "entity": { + "binary_sensor": { + "port_forwarding": { + "default": "mdi:shield-check", + "state": { + "off": "mdi:shield-off" + } + } + }, "sensor": { "active_torrents": { "default": "mdi:counter" diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index f462b9a6d1a4..588deb3f76ce 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -41,6 +41,15 @@ } }, "entity": { + "binary_sensor": { + "port_forwarding": { + "name": "Port forwarding", + "state": { + "off": "Closed", + "on": "Open" + } + } + }, "sensor": { "active_torrents": { "name": "Active torrents", diff --git a/tests/components/transmission/conftest.py b/tests/components/transmission/conftest.py index 2adb1bf67b29..17071fe179f4 100644 --- a/tests/components/transmission/conftest.py +++ b/tests/components/transmission/conftest.py @@ -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 diff --git a/tests/components/transmission/snapshots/test_binary_sensor.ambr b/tests/components/transmission/snapshots/test_binary_sensor.ambr new file mode 100644 index 000000000000..a1d9aafb99db --- /dev/null +++ b/tests/components/transmission/snapshots/test_binary_sensor.ambr @@ -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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.transmission_port_forwarding', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Port forwarding', + 'options': dict({ + }), + 'original_device_class': , + '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': , + 'entity_id': 'binary_sensor.transmission_port_forwarding', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/transmission/test_binary_sensor.py b/tests/components/transmission/test_binary_sensor.py new file mode 100644 index 000000000000..a306dc4c4323 --- /dev/null +++ b/tests/components/transmission/test_binary_sensor.py @@ -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)