mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 01:11:51 -04:00
Add event entity platform to SimpliSafe (#174367)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import asyncio
|
||||
from typing import Any
|
||||
|
||||
from simplipy import API
|
||||
from simplipy.device.camera import Camera
|
||||
from simplipy.errors import (
|
||||
EndpointUnavailableError,
|
||||
InvalidCredentialsError,
|
||||
@@ -12,6 +13,7 @@ from simplipy.errors import (
|
||||
WebsocketError,
|
||||
)
|
||||
from simplipy.system import SystemNotification
|
||||
from simplipy.system.v3 import SystemV3
|
||||
from simplipy.websocket import (
|
||||
EVENT_AUTOMATIC_TEST,
|
||||
EVENT_CAMERA_MOTION_DETECTED,
|
||||
@@ -77,11 +79,15 @@ PLATFORMS = [
|
||||
Platform.ALARM_CONTROL_PANEL,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.EVENT,
|
||||
Platform.LOCK,
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
|
||||
# These events are fired on the HA bus as SIMPLISAFE_EVENT for backwards
|
||||
# compatibility. Do not use SIMPLISAFE_EVENT and do not copy this pattern when
|
||||
# making new integrations.
|
||||
WEBSOCKET_EVENTS_TO_FIRE_HASS_EVENT = [
|
||||
EVENT_AUTOMATIC_TEST,
|
||||
EVENT_CAMERA_MOTION_DETECTED,
|
||||
@@ -114,6 +120,7 @@ def _async_register_base_station(
|
||||
manufacturer="SimpliSafe",
|
||||
model=str(system.version),
|
||||
name=system.address,
|
||||
serial_number=system.serial,
|
||||
)
|
||||
|
||||
# Check for an old system ID format and remove it:
|
||||
@@ -132,6 +139,34 @@ def _async_register_base_station(
|
||||
device_registry.async_remove_device(old_base_station.id)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_register_camera(
|
||||
hass: HomeAssistant, entry: ConfigEntry, system: SystemType, camera: Camera
|
||||
) -> None:
|
||||
"""Register a camera device."""
|
||||
if not isinstance(system, SystemV3):
|
||||
return
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
model_name = camera.camera_type.name.capitalize().replace("_", " ")
|
||||
model_id = system.camera_data[camera.serial]["model"]
|
||||
device_name = f"{camera.name.capitalize()} {model_name}"
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, camera.serial)},
|
||||
manufacturer="SimpliSafe",
|
||||
model=model_name,
|
||||
model_id=model_id,
|
||||
name=device_name,
|
||||
serial_number=camera.serial,
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
hass, (DOMAIN, str(system.system_id)), config_entry_id=entry.entry_id
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_standardize_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Bring a config entry up to current standards."""
|
||||
@@ -340,6 +375,9 @@ class SimpliSafe:
|
||||
self._hass, DISPATCHER_TOPIC_WEBSOCKET_EVENT.format(event.system_id), event
|
||||
)
|
||||
|
||||
# Deprecated: SIMPLISAFE_EVENT bus events are maintained for backwards
|
||||
# compatibility. Use the system event entity and event.received trigger
|
||||
# instead.
|
||||
if event.event_type not in WEBSOCKET_EVENTS_TO_FIRE_HASS_EVENT:
|
||||
return
|
||||
|
||||
@@ -377,6 +415,11 @@ class SimpliSafe:
|
||||
|
||||
_async_register_base_station(self._hass, self.entry, system)
|
||||
|
||||
# Register each camera as a device:
|
||||
if isinstance(system, SystemV3):
|
||||
for camera in system.cameras.values():
|
||||
_async_register_camera(self._hass, self.entry, system, camera)
|
||||
|
||||
# Future events will come from the websocket, but since subscription to the
|
||||
# websocket doesn't provide the most recent event, we grab it from the REST
|
||||
# API to ensure event-related attributes aren't empty on startup:
|
||||
|
||||
@@ -88,6 +88,8 @@ class SimpliSafeEntity(CoordinatorEntity[SimpliSafeDataUpdateCoordinator]):
|
||||
else:
|
||||
device_type = DeviceTypes.UNKNOWN
|
||||
|
||||
# Deprecated: last_event_* attributes are maintained for backwards
|
||||
# compatibility. Use the event entity's event attributes instead.
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_LAST_EVENT_INFO: event.get("info"),
|
||||
ATTR_LAST_EVENT_SENSOR_NAME: event.get("sensorName"),
|
||||
@@ -102,6 +104,7 @@ class SimpliSafeEntity(CoordinatorEntity[SimpliSafeDataUpdateCoordinator]):
|
||||
manufacturer="SimpliSafe",
|
||||
model=model,
|
||||
name=device_name,
|
||||
serial_number=serial,
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.coordinator.hass,
|
||||
(DOMAIN, str(system.system_id)),
|
||||
@@ -181,6 +184,8 @@ class SimpliSafeEntity(CoordinatorEntity[SimpliSafeDataUpdateCoordinator]):
|
||||
else:
|
||||
sensor_type = None
|
||||
|
||||
# Deprecated: last_event_* attributes are maintained for backwards
|
||||
# compatibility. Use the event entity's event attributes instead.
|
||||
self._attr_extra_state_attributes.update(
|
||||
{
|
||||
ATTR_LAST_EVENT_INFO: event.info,
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
"""Support for SimpliSafe events."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from simplipy.device import Device
|
||||
from simplipy.device.camera import CameraTypes
|
||||
from simplipy.system.v3 import SystemV3
|
||||
from simplipy.websocket import (
|
||||
EVENT_CAMERA_MOTION_DETECTED,
|
||||
EVENT_DOORBELL_DETECTED,
|
||||
WebsocketEvent,
|
||||
)
|
||||
|
||||
from homeassistant.components.event import (
|
||||
DoorbellEventType,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import WEBSOCKET_EVENTS_TO_FIRE_HASS_EVENT, SimpliSafe, SimpliSafeConfigEntry
|
||||
from .entity import SimpliSafeEntity
|
||||
from .typing import SystemType
|
||||
|
||||
SYSTEM_EVENT_TYPES = [
|
||||
event
|
||||
for event in WEBSOCKET_EVENTS_TO_FIRE_HASS_EVENT
|
||||
if event not in (EVENT_CAMERA_MOTION_DETECTED, EVENT_DOORBELL_DETECTED)
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SimpliSafeCameraEventDescription(EventEntityDescription):
|
||||
"""Describe a SimpliSafe camera event entity."""
|
||||
|
||||
raw_event_type: str
|
||||
|
||||
|
||||
CAMERA_EVENT_DESCRIPTIONS: dict[CameraTypes, list[SimpliSafeCameraEventDescription]] = {
|
||||
CameraTypes.CAMERA: [
|
||||
SimpliSafeCameraEventDescription(
|
||||
key="motion",
|
||||
device_class=EventDeviceClass.MOTION,
|
||||
event_types=[EVENT_CAMERA_MOTION_DETECTED],
|
||||
raw_event_type=EVENT_CAMERA_MOTION_DETECTED,
|
||||
),
|
||||
],
|
||||
CameraTypes.OUTDOOR_CAMERA: [
|
||||
SimpliSafeCameraEventDescription(
|
||||
key="motion",
|
||||
device_class=EventDeviceClass.MOTION,
|
||||
event_types=[EVENT_CAMERA_MOTION_DETECTED],
|
||||
raw_event_type=EVENT_CAMERA_MOTION_DETECTED,
|
||||
),
|
||||
],
|
||||
CameraTypes.DOORBELL: [
|
||||
SimpliSafeCameraEventDescription(
|
||||
key="ring",
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
event_types=[DoorbellEventType.RING],
|
||||
raw_event_type=EVENT_DOORBELL_DETECTED,
|
||||
),
|
||||
SimpliSafeCameraEventDescription(
|
||||
key="motion",
|
||||
device_class=EventDeviceClass.MOTION,
|
||||
event_types=[EVENT_CAMERA_MOTION_DETECTED],
|
||||
raw_event_type=EVENT_CAMERA_MOTION_DETECTED,
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: SimpliSafeConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up SimpliSafe events based on a config entry."""
|
||||
simplisafe = entry.runtime_data
|
||||
entities: list[SimpliSafeEvent] = []
|
||||
|
||||
for system in simplisafe.systems.values():
|
||||
entities.append(
|
||||
SimpliSafeEvent(
|
||||
simplisafe,
|
||||
system,
|
||||
entity_description=EventEntityDescription(
|
||||
key="system_events",
|
||||
event_types=SYSTEM_EVENT_TYPES,
|
||||
),
|
||||
unique_id=f"{system.serial}-system_events",
|
||||
)
|
||||
)
|
||||
|
||||
if not isinstance(system, SystemV3):
|
||||
continue
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(system, SystemV3)
|
||||
for uuid, camera in system.cameras.items():
|
||||
ws_serial = system.camera_data[uuid]["serial"]
|
||||
entities.extend(
|
||||
SimpliSafeEvent(
|
||||
simplisafe,
|
||||
system,
|
||||
entity_description=description,
|
||||
device=camera,
|
||||
ws_serial=ws_serial,
|
||||
unique_id=f"{camera.serial}-{description.key}",
|
||||
)
|
||||
for description in CAMERA_EVENT_DESCRIPTIONS.get(
|
||||
camera.camera_type, CAMERA_EVENT_DESCRIPTIONS[CameraTypes.CAMERA]
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class SimpliSafeEvent(SimpliSafeEntity, EventEntity):
|
||||
"""Define a SimpliSafe event entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
simplisafe: SimpliSafe,
|
||||
system: SystemType,
|
||||
*,
|
||||
entity_description: EventEntityDescription,
|
||||
device: Device | None = None,
|
||||
ws_serial: str | None = None,
|
||||
unique_id: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self.entity_description = entity_description
|
||||
self._attr_translation_key = entity_description.key
|
||||
self._ws_serial = ws_serial
|
||||
|
||||
super().__init__(
|
||||
simplisafe,
|
||||
system,
|
||||
device=device,
|
||||
additional_websocket_events=(
|
||||
[entity_description.raw_event_type]
|
||||
if isinstance(entity_description, SimpliSafeCameraEventDescription)
|
||||
else entity_description.event_types
|
||||
),
|
||||
)
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@callback
|
||||
@override
|
||||
def _handle_websocket_update(self, event: WebsocketEvent) -> None:
|
||||
"""Update the entity with new websocket data."""
|
||||
if self._ws_serial and event.sensor_serial != self._ws_serial:
|
||||
return
|
||||
|
||||
super()._handle_websocket_update(event)
|
||||
|
||||
@callback
|
||||
@override
|
||||
def async_update_from_websocket_event(self, event: WebsocketEvent) -> None:
|
||||
"""Update the entity when new data comes from the websocket."""
|
||||
assert event.event_type is not None
|
||||
event_attributes: dict[str, str | None] = {
|
||||
"changed_by": event.changed_by,
|
||||
"info": event.info,
|
||||
}
|
||||
if not self._ws_serial:
|
||||
event_attributes["sensor_name"] = event.sensor_name
|
||||
event_attributes["sensor_serial"] = event.sensor_serial
|
||||
event_attributes["sensor_type"] = (
|
||||
event.sensor_type.name if event.sensor_type else None
|
||||
)
|
||||
self._trigger_event(
|
||||
DoorbellEventType.RING
|
||||
if event.event_type == EVENT_DOORBELL_DETECTED
|
||||
else event.event_type,
|
||||
event_attributes=event_attributes,
|
||||
)
|
||||
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"entity": {
|
||||
"event": {
|
||||
"system_events": {
|
||||
"default": "mdi:shield-home"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"remove_pin": {
|
||||
"service": "mdi:alarm-panel-outline"
|
||||
|
||||
@@ -25,6 +25,42 @@
|
||||
"clear_notifications": {
|
||||
"name": "Clear notifications"
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"motion": {
|
||||
"name": "Motion",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"camera_motion_detected": "Motion detected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ring": {
|
||||
"name": "Ring",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"ring": "[%key:component::event::entity_component::doorbell::state_attributes::event_type::state::ring%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_events": {
|
||||
"name": "System events",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"automatic_test": "Automatic test",
|
||||
"device_test": "Device test",
|
||||
"secret_alert_triggered": "Secret alert triggered",
|
||||
"sensor_paired_and_named": "Sensor paired and named",
|
||||
"user_initiated_test": "User initiated test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
"upgradeWhitelisted": false,
|
||||
"model": "SS001",
|
||||
"uuid": "1234567890",
|
||||
"serial": "1234567890",
|
||||
"uid": 12345,
|
||||
"sid": 12345,
|
||||
"cameraSettings": {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# serializer version: 1
|
||||
# name: test_event_entities[event.alarm_control_panel_system_events-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<EventEntityCapabilityAttribute.EVENT_TYPES: 'event_types'>: list([
|
||||
'automatic_test',
|
||||
'device_test',
|
||||
'secret_alert_triggered',
|
||||
'sensor_paired_and_named',
|
||||
'user_initiated_test',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.alarm_control_panel_system_events',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'System events',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'System events',
|
||||
'platform': 'simplisafe',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'system_events',
|
||||
'unique_id': '1234ABCD-system_events',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_event_entities[event.alarm_control_panel_system_events-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EventEntityStateAttribute.EVENT_TYPE: 'event_type'>: None,
|
||||
<EventEntityCapabilityAttribute.EVENT_TYPES: 'event_types'>: list([
|
||||
'automatic_test',
|
||||
'device_test',
|
||||
'secret_alert_triggered',
|
||||
'sensor_paired_and_named',
|
||||
'user_initiated_test',
|
||||
]),
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Alarm control panel System events',
|
||||
'last_event_info': 'System Disarmed by PIN 2',
|
||||
'last_event_sensor_name': 'Kitchen',
|
||||
'last_event_sensor_type': 'keypad',
|
||||
'last_event_timestamp': 1564018073,
|
||||
'system_id': 12345,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.alarm_control_panel_system_events',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_event_entities[event.camera_camera_motion-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<EventEntityCapabilityAttribute.EVENT_TYPES: 'event_types'>: list([
|
||||
'camera_motion_detected',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.camera_camera_motion',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Motion',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.MOTION: 'motion'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Motion',
|
||||
'platform': 'simplisafe',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'motion',
|
||||
'unique_id': '1234567890-motion',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_event_entities[event.camera_camera_motion-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'motion',
|
||||
<EventEntityStateAttribute.EVENT_TYPE: 'event_type'>: None,
|
||||
<EventEntityCapabilityAttribute.EVENT_TYPES: 'event_types'>: list([
|
||||
'camera_motion_detected',
|
||||
]),
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Camera Camera Motion',
|
||||
'last_event_info': 'System Disarmed by PIN 2',
|
||||
'last_event_sensor_name': 'Kitchen',
|
||||
'last_event_sensor_type': 'keypad',
|
||||
'last_event_timestamp': 1564018073,
|
||||
'system_id': 12345,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.camera_camera_motion',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Define tests for SimpliSafe event entities."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
from simplipy.websocket import EVENT_CAMERA_MOTION_DETECTED, WebsocketEvent
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
CAMERA_SERIAL = "1234567890"
|
||||
CAMERA_EVENT_ENTITY_ID = "event.camera_camera_motion"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_event_entities(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
patch_simplisafe_api,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test that all event entities are created."""
|
||||
with patch("homeassistant.components.simplisafe.PLATFORMS", [Platform.EVENT]):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_camera_event_triggers_on_matching_serial(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
patch_simplisafe_api,
|
||||
websocket: Mock,
|
||||
) -> None:
|
||||
"""Test that camera event entity triggers for events with matching serial."""
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event_callback = websocket.add_event_callback.call_args[0][0]
|
||||
|
||||
# Fire a camera motion event for the camera's serial:
|
||||
event_callback(
|
||||
WebsocketEvent(
|
||||
event_cid=1170,
|
||||
info="Camera motion detected",
|
||||
system_id=12345,
|
||||
_raw_timestamp=0,
|
||||
_video=None,
|
||||
_vid=None,
|
||||
sensor_serial=CAMERA_SERIAL,
|
||||
)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(CAMERA_EVENT_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.attributes.get("event_type") == EVENT_CAMERA_MOTION_DETECTED
|
||||
|
||||
|
||||
async def test_camera_event_ignores_mismatched_serial(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
patch_simplisafe_api,
|
||||
websocket: Mock,
|
||||
) -> None:
|
||||
"""Test that camera event entity ignores events for a different serial."""
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event_callback = websocket.add_event_callback.call_args[0][0]
|
||||
|
||||
# Fire a camera motion event for a DIFFERENT serial:
|
||||
event_callback(
|
||||
WebsocketEvent(
|
||||
event_cid=1170,
|
||||
info="Camera motion detected",
|
||||
system_id=12345,
|
||||
_raw_timestamp=0,
|
||||
_video=None,
|
||||
_vid=None,
|
||||
sensor_serial="different_serial",
|
||||
)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(CAMERA_EVENT_ENTITY_ID)
|
||||
assert state is not None
|
||||
# State should remain unknown and no event should have been triggered:
|
||||
assert state.state == "unknown"
|
||||
assert state.attributes.get("event_type") is None
|
||||
@@ -127,6 +127,32 @@ async def test_coordinator_update_failure_keeps_entity_available(
|
||||
assert hass.states.get("lock.front_door_lock").state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_camera_device_registration(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
patch_simplisafe_api,
|
||||
) -> None:
|
||||
"""Test that camera devices are registered in the device registry."""
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
camera_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, "1234567890"), config_entry.entry_id
|
||||
)
|
||||
assert camera_device is not None
|
||||
assert camera_device.manufacturer == "SimpliSafe"
|
||||
assert camera_device.model == "Camera"
|
||||
assert camera_device.name == "Camera Camera"
|
||||
|
||||
# Verify via_device points to the base station:
|
||||
base_station = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, "12345"), config_entry.entry_id
|
||||
)
|
||||
assert base_station is not None
|
||||
assert camera_device.via_device_id == base_station.id
|
||||
|
||||
|
||||
async def test_websocket_event_updates_entity_state(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
|
||||
Reference in New Issue
Block a user