Add binary sensor platform to Mastodon (#161056)

This commit is contained in:
Manu
2026-01-16 14:31:42 +01:00
committed by GitHub
parent bd24c27bc9
commit cee007b0b0
6 changed files with 573 additions and 1 deletions

View File

@@ -28,7 +28,7 @@ from .coordinator import MastodonConfigEntry, MastodonCoordinator, MastodonData
from .services import async_setup_services
from .utils import construct_mastodon_username, create_mastodon_client
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

View File

@@ -0,0 +1,128 @@
"""Binary sensor platform for the Mastodon integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import StrEnum
from mastodon.Mastodon import Account
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import MastodonConfigEntry
from .entity import MastodonEntity
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class MastodonBinarySensor(StrEnum):
"""Mastodon binary sensors."""
BOT = "bot"
SUSPENDED = "suspended"
DISCOVERABLE = "discoverable"
LOCKED = "locked"
INDEXABLE = "indexable"
LIMITED = "limited"
MEMORIAL = "memorial"
MOVED = "moved"
@dataclass(frozen=True, kw_only=True)
class MastodonBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Mastodon binary sensor description."""
is_on_fn: Callable[[Account], bool | None]
ENTITY_DESCRIPTIONS: tuple[MastodonBinarySensorEntityDescription, ...] = (
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.BOT,
translation_key=MastodonBinarySensor.BOT,
is_on_fn=lambda account: account.bot,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.DISCOVERABLE,
translation_key=MastodonBinarySensor.DISCOVERABLE,
is_on_fn=lambda account: account.discoverable,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.LOCKED,
translation_key=MastodonBinarySensor.LOCKED,
is_on_fn=lambda account: account.locked,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.MOVED,
translation_key=MastodonBinarySensor.MOVED,
is_on_fn=lambda account: account.moved is not None,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.INDEXABLE,
translation_key=MastodonBinarySensor.INDEXABLE,
is_on_fn=lambda account: account.indexable,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.LIMITED,
translation_key=MastodonBinarySensor.LIMITED,
is_on_fn=lambda account: account.limited is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.MEMORIAL,
translation_key=MastodonBinarySensor.MEMORIAL,
is_on_fn=lambda account: account.memorial is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
MastodonBinarySensorEntityDescription(
key=MastodonBinarySensor.SUSPENDED,
translation_key=MastodonBinarySensor.SUSPENDED,
is_on_fn=lambda account: account.suspended is True,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: MastodonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the binary sensor platform."""
coordinator = entry.runtime_data.coordinator
async_add_entities(
MastodonBinarySensorEntity(
coordinator=coordinator,
entity_description=entity_description,
data=entry,
)
for entity_description in ENTITY_DESCRIPTIONS
)
class MastodonBinarySensorEntity(MastodonEntity, BinarySensorEntity):
"""Mastodon binary sensor entity."""
entity_description: MastodonBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.entity_description.is_on_fn(self.coordinator.data)

View File

@@ -1,5 +1,18 @@
{
"entity": {
"binary_sensor": {
"bot": { "default": "mdi:robot" },
"discoverable": { "default": "mdi:magnify-scan" },
"indexable": { "default": "mdi:search-web" },
"limited": { "default": "mdi:volume-mute" },
"locked": {
"default": "mdi:account-lock",
"state": { "off": "mdi:account-lock-open" }
},
"memorial": { "default": "mdi:candle" },
"moved": { "default": "mdi:truck-delivery" },
"suspended": { "default": "mdi:account-off" }
},
"sensor": {
"followers": {
"default": "mdi:account-multiple"

View File

@@ -26,6 +26,16 @@
}
},
"entity": {
"binary_sensor": {
"bot": { "name": "Bot" },
"discoverable": { "name": "Discoverable" },
"indexable": { "name": "Indexable" },
"limited": { "name": "Muted" },
"locked": { "name": "Locked" },
"memorial": { "name": "Memorial" },
"moved": { "name": "Moved" },
"suspended": { "name": "Suspended" }
},
"sensor": {
"followers": {
"name": "Followers",

View File

@@ -0,0 +1,393 @@
# serializer version: 1
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_bot-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_bot',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Bot',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bot',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.BOT: 'bot'>,
'unique_id': 'trwnh_mastodon_social_bot',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_bot-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Bot',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_bot',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_discoverable-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_discoverable',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Discoverable',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Discoverable',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.DISCOVERABLE: 'discoverable'>,
'unique_id': 'trwnh_mastodon_social_discoverable',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_discoverable-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Discoverable',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_discoverable',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_indexable-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_indexable',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Indexable',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Indexable',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.INDEXABLE: 'indexable'>,
'unique_id': 'trwnh_mastodon_social_indexable',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_indexable-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Indexable',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_indexable',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_locked-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_locked',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Locked',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Locked',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.LOCKED: 'locked'>,
'unique_id': 'trwnh_mastodon_social_locked',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_locked-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Locked',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_locked',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_memorial-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_memorial',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Memorial',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Memorial',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.MEMORIAL: 'memorial'>,
'unique_id': 'trwnh_mastodon_social_memorial',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_memorial-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Memorial',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_memorial',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_moved-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_moved',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Moved',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Moved',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.MOVED: 'moved'>,
'unique_id': 'trwnh_mastodon_social_moved',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_moved-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Moved',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_moved',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_muted-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_muted',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Muted',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Muted',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.LIMITED: 'limited'>,
'unique_id': 'trwnh_mastodon_social_limited',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_muted-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Muted',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_muted',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_suspended-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'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.mastodon_trwnh_mastodon_social_suspended',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Suspended',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Suspended',
'platform': 'mastodon',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <MastodonBinarySensor.SUSPENDED: 'suspended'>,
'unique_id': 'trwnh_mastodon_social_suspended',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[binary_sensor.mastodon_trwnh_mastodon_social_suspended-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mastodon @trwnh@mastodon.social Suspended',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.mastodon_trwnh_mastodon_social_suspended',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@@ -0,0 +1,28 @@
"""Tests for the Mastodon binary sensors."""
from unittest.mock import 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("mock_mastodon_client", "entity_registry_enabled_by_default")
async def test_binary_sensors(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the binary sensor entities."""
with patch("homeassistant.components.mastodon.PLATFORMS", [Platform.BINARY_SENSOR]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)