Fix unifiprotect supported features being set too late (#129850)

This commit is contained in:
J. Nick Koston 2024-11-04 15:13:56 -06:00 committed by GitHub
parent 0b56ef5699
commit 3584c710b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 79 additions and 15 deletions

View File

@ -156,7 +156,8 @@ async def async_setup_entry(
async_add_entities(_async_camera_entities(hass, entry, data))
_EMPTY_CAMERA_FEATURES = CameraEntityFeature(0)
_DISABLE_FEATURE = CameraEntityFeature(0)
_ENABLE_FEATURE = CameraEntityFeature.STREAM
class ProtectCamera(ProtectDeviceEntity, Camera):
@ -195,24 +196,20 @@ class ProtectCamera(ProtectDeviceEntity, Camera):
self._attr_name = f"{camera_name} (insecure)"
# only the default (first) channel is enabled by default
self._attr_entity_registry_enabled_default = is_default and secure
# Set the stream source before finishing the init
# because async_added_to_hass is too late and camera
# integration uses async_internal_added_to_hass to access
# the stream source which is called before async_added_to_hass
self._async_set_stream_source()
@callback
def _async_set_stream_source(self) -> None:
disable_stream = self._disable_stream
channel = self.channel
if not channel.is_rtsp_enabled:
disable_stream = False
enable_stream = not self._disable_stream and channel.is_rtsp_enabled
rtsp_url = channel.rtsps_url if self._secure else channel.rtsp_url
# _async_set_stream_source called by __init__
# pylint: disable-next=attribute-defined-outside-init
self._stream_source = None if disable_stream else rtsp_url
if self._stream_source:
self._attr_supported_features = CameraEntityFeature.STREAM
else:
self._attr_supported_features = _EMPTY_CAMERA_FEATURES
source = rtsp_url if enable_stream else None
self._attr_supported_features = _ENABLE_FEATURE if source else _DISABLE_FEATURE
self._stream_source = source
@callback
def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from unittest.mock import AsyncMock, Mock
import pytest
from uiprotect.api import DEVICE_UPDATE_INTERVAL
from uiprotect.data import Camera as ProtectCamera, CameraChannel, StateType
from uiprotect.exceptions import NvrError
@ -12,8 +13,13 @@ from uiprotect.websocket import WebsocketState
from homeassistant.components.camera import (
CameraEntityFeature,
CameraState,
CameraWebRTCProvider,
RTCIceCandidate,
StreamType,
WebRTCSendMessage,
async_get_image,
async_get_stream_source,
async_register_webrtc_provider,
)
from homeassistant.components.unifiprotect.const import (
ATTR_BITRATE,
@ -22,6 +28,7 @@ from homeassistant.components.unifiprotect.const import (
ATTR_HEIGHT,
ATTR_WIDTH,
DEFAULT_ATTRIBUTION,
DOMAIN,
)
from homeassistant.components.unifiprotect.utils import get_camera_base_name
from homeassistant.const import (
@ -31,11 +38,12 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from .utils import (
Camera,
MockUFPFixture,
adopt_devices,
assert_entity_counts,
@ -46,6 +54,45 @@ from .utils import (
)
class MockWebRTCProvider(CameraWebRTCProvider):
"""WebRTC provider."""
@property
def domain(self) -> str:
"""Return the integration domain of the provider."""
return DOMAIN
@callback
def async_is_supported(self, stream_source: str) -> bool:
"""Return if this provider is supports the Camera as source."""
return True
async def async_handle_async_webrtc_offer(
self,
camera: Camera,
offer_sdp: str,
session_id: str,
send_message: WebRTCSendMessage,
) -> None:
"""Handle the WebRTC offer and return the answer via the provided callback."""
async def async_on_webrtc_candidate(
self, session_id: str, candidate: RTCIceCandidate
) -> None:
"""Handle the WebRTC candidate."""
@callback
def async_close_session(self, session_id: str) -> None:
"""Close the session."""
@pytest.fixture
async def web_rtc_provider(hass: HomeAssistant) -> None:
"""Fixture to enable WebRTC provider for camera entities."""
await async_setup_component(hass, "camera", {})
async_register_webrtc_provider(hass, MockWebRTCProvider())
def validate_default_camera_entity(
hass: HomeAssistant,
camera_obj: ProtectCamera,
@ -283,6 +330,26 @@ async def test_basic_setup(
await validate_no_stream_camera_state(hass, doorbell, 3, entity_id, features=0)
@pytest.mark.usefixtures("web_rtc_provider")
async def test_webrtc_support(
hass: HomeAssistant,
ufp: MockUFPFixture,
camera_all: ProtectCamera,
) -> None:
"""Test webrtc support is available."""
camera_high_only = camera_all.copy()
camera_high_only.channels = [c.copy() for c in camera_all.channels]
camera_high_only.name = "Test Camera 1"
camera_high_only.channels[0].is_rtsp_enabled = True
camera_high_only.channels[1].is_rtsp_enabled = False
camera_high_only.channels[2].is_rtsp_enabled = False
await init_entry(hass, ufp, [camera_high_only])
entity_id = validate_default_camera_entity(hass, camera_high_only, 0)
state = hass.states.get(entity_id)
assert state
assert StreamType.WEB_RTC in state.attributes["frontend_stream_type"]
async def test_adopt(
hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera
) -> None: