From d979617e5dc5f569b73ebe1c6e36e653b2da91b5 Mon Sep 17 00:00:00 2001 From: Thomas Lomas Date: Wed, 7 Feb 2024 00:55:13 +0000 Subject: [PATCH 1/3] base camera func --- .github/workflows/validate.yml | 2 - custom_components/starling_home_hub/api.py | 46 +++-- .../starling_home_hub/binary_sensor.py | 13 +- custom_components/starling_home_hub/camera.py | 186 ++++++++++++++++++ custom_components/starling_home_hub/const.py | 3 +- .../starling_home_hub/coordinator.py | 14 +- custom_components/starling_home_hub/entity.py | 20 +- custom_components/starling_home_hub/models.py | 15 ++ .../starling_home_hub/placeholder.png | Bin 0 -> 2689 bytes 9 files changed, 273 insertions(+), 26 deletions(-) create mode 100644 custom_components/starling_home_hub/camera.py create mode 100644 custom_components/starling_home_hub/placeholder.png diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 502f5dd..19aa6f2 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -33,5 +33,3 @@ jobs: uses: "hacs/action@main" with: category: "integration" - # Remove this 'ignore' key when you have added brand images for your integration to https://github.com/home-assistant/brands - ignore: "brands" diff --git a/custom_components/starling_home_hub/api.py b/custom_components/starling_home_hub/api.py index fda0ccd..fb75e65 100644 --- a/custom_components/starling_home_hub/api.py +++ b/custom_components/starling_home_hub/api.py @@ -1,13 +1,13 @@ """Starling Home Hub Developer Connect API Client.""" from __future__ import annotations -from .models import Device, Devices, Status, SpecificDevice +from .models import Device, Devices, Status, SpecificDevice, StartStream, StreamStatus import asyncio import socket - import aiohttp import async_timeout - +import base64 +from .const import LOGGER class StarlingHomeHubApiClientError(Exception): """Exception to indicate a general API error.""" @@ -67,14 +67,38 @@ async def async_get_devices(self) -> list[Device]: return Devices(**devices_response).devices - # async def async_set_title(self, value: str) -> any: - # """Get data from the API.""" - # return await self._api_wrapper( - # method="patch", - # url="https://jsonplaceholder.typicode.com/posts/1", - # data={"title": value}, - # headers={"Content-type": "application/json; charset=UTF-8"}, - # ) + async def async_start_stream(self, device_id: str, sdp_offer: str) -> StartStream: + """Start a WebRTC Stream.""" + start_stream_response = await self._api_wrapper( + method="post", + url=self.get_api_url_for_endpoint(f"devices/{device_id}/stream"), + data={"offer": base64.b64encode(sdp_offer.encode()).decode()}, + headers={"Content-type": "application/json; charset=UTF-8"} + ) + + return StartStream(**start_stream_response) + + async def async_stop_stream(self, device_id: str, stream_id: str) -> StreamStatus: + """stop a WebRTC Stream.""" + stop_stream_response = await self._api_wrapper( + method="post", + url=self.get_api_url_for_endpoint(f"devices/{device_id}/stream/{stream_id}/stop"), + headers={"Content-type": "application/json; charset=UTF-8"}, + data={} + ) + + return StreamStatus(**stop_stream_response) + + async def async_extend_stream(self, device_id: str, stream_id: str) -> StreamStatus: + """Extend a WebRTC Stream.""" + extend_stream_response = await self._api_wrapper( + method="post", + url=self.get_api_url_for_endpoint(f"devices/{device_id}/stream/{stream_id}/extend"), + headers={"Content-type": "application/json; charset=UTF-8"}, + data={} + ) + + return StreamStatus(**extend_stream_response) async def _api_wrapper( self, diff --git a/custom_components/starling_home_hub/binary_sensor.py b/custom_components/starling_home_hub/binary_sensor.py index f56127d..0e053c1 100644 --- a/custom_components/starling_home_hub/binary_sensor.py +++ b/custom_components/starling_home_hub/binary_sensor.py @@ -35,6 +35,12 @@ class StarlingHomeHubNestProtectBinarySensorDescription(BinarySensorEntityDescri value_fn=lambda device: device["coDetected"], device_class=BinarySensorDeviceClass.CO ), + StarlingHomeHubNestProtectBinarySensorDescription( + key="occupancy_detected", + name="Occupancy Detected", + value_fn=lambda device: device["occupancyDetected"], + device_class=BinarySensorDeviceClass.OCCUPANCY + ), StarlingHomeHubNestProtectBinarySensorDescription( key="battery_status", name="Battery Status", @@ -77,13 +83,10 @@ def __init__( self.device_id = device_id self.coordinator = coordinator - - super().__init__(coordinator, self.get_device(), entity_description) self.entity_description = entity_description + self._attr_unique_id = f"{device_id}-{self.entity_description.key}" - def get_device(self) -> SpecificDevice: - """Get the actual device data from coordinator.""" - return self.coordinator.data.devices.get(self.device_id) + super().__init__(coordinator) @property def is_on(self) -> bool: diff --git a/custom_components/starling_home_hub/camera.py b/custom_components/starling_home_hub/camera.py new file mode 100644 index 0000000..5ac602c --- /dev/null +++ b/custom_components/starling_home_hub/camera.py @@ -0,0 +1,186 @@ +"""Camera platform for starling_home_hub.""" +from __future__ import annotations +from collections.abc import Callable +from pathlib import Path +from datetime import timedelta, datetime, timezone +import asyncio +import functools +from typing import cast +import base64 + +from homeassistant.components.camera import Camera, CameraEntityFeature, StreamType +from homeassistant.components.stream import CONF_EXTRA_PART_WAIT_TIME +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.helpers.typing import StateType +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.config_entries import ConfigEntry + +from .const import DOMAIN, LOGGER +from .coordinator import StarlingHomeHubDataUpdateCoordinator +from .entity import StarlingHomeHubEntity +from .models import CoordinatorData, StartStream + +from dataclasses import dataclass + +PLACEHOLDER = Path(__file__).parent / "placeholder.png" +STREAM_EXPIRATION_BUFFER = timedelta(seconds=60) + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None: + """Set up the sensor platform.""" + + coordinator = hass.data[DOMAIN][entry.entry_id] + entities: list[StarlingHomeHubNestCamera] = [] + data: CoordinatorData = coordinator.data + + for device in filter(lambda device: device[1].properties["type"] == "cam", data.devices.items()): + entities.append( + StarlingHomeHubNestCamera( + device_id=device[0], + coordinator=coordinator + ) + ) + + async_add_entities(entities, True) + +class StarlingHomeHubNestCamera(StarlingHomeHubEntity, Camera): + """Starling Home Hub Nest Camera class.""" + + def __init__( + self, + device_id: str, + coordinator: StarlingHomeHubDataUpdateCoordinator + ) -> None: + """Initialize the Nest Protect Sensor class.""" + + self.device_id = device_id + self.coordinator = coordinator + self._attr_unique_id = f"{device_id}-camera" + + super().__init__(coordinator) + Camera.__init__(self) + + self._stream: StartStream | None = None + self._create_stream_url_lock = asyncio.Lock() + self._stream_refresh_unsub: Callable[[], None] | None = None + self._attr_is_streaming = False + self._attr_supported_features = CameraEntityFeature(0) + self.stream_options[CONF_EXTRA_PART_WAIT_TIME] = 3 + + device = self.get_device() + + if device.properties["supportsStreaming"]: + self._attr_is_streaming = True + self._attr_supported_features |= CameraEntityFeature.STREAM + + @property + def use_stream_for_stills(self) -> bool: + """Whether or not to use stream to generate stills.""" + + device = self.get_device() + return device.properties["supportsStreaming"] + + @property + def available(self) -> bool: + """Return True if entity is available.""" + + # Cameras are marked unavailable on stream errors in #54659 however nest + # streams have a high error rate (#60353). Given nest streams are so flaky, + # marking the stream unavailable has other side effects like not showing + # the camera image which sometimes are still able to work. Until the + # streams are fixed, just leave the streams as available. + + return True + + @property + def frontend_stream_type(self) -> StreamType | None: + """Return the type of stream supported by this camera.""" + + device = self.get_device() + if device.properties["supportsStreaming"]: + return StreamType.WEB_RTC + else: + return None + + async def stream_source(self) -> str | None: + """Return stream source for the camera.""" + + return None + + def _schedule_stream_refresh(self) -> None: + """Schedules an alarm to refresh the stream url before expiration.""" + + if not self._stream: + return + + refresh_time = datetime.now(timezone.utc) + STREAM_EXPIRATION_BUFFER + LOGGER.debug("New stream url expires at %s", refresh_time) + + if self._stream_refresh_unsub is not None: + self._stream_refresh_unsub() + + self._stream_refresh_unsub = async_track_point_in_utc_time( + self.hass, + self._handle_stream_refresh, + refresh_time, + ) + + async def _handle_stream_refresh(self, now: datetime) -> None: + """Alarm that fires to check if the stream should be refreshed.""" + + if not self._stream: + return + + try: + await self.coordinator.extend_stream(self.device_id, self._stream.streamId) + except Exception as err: + LOGGER.debug("Failed to extend stream: %s", err) + self._stream = None + if self.stream: + await self.stream.stop() + self.stream = None + return + + self._schedule_stream_refresh() + + async def async_will_remove_from_hass(self) -> None: + """Invalidate the RTSP token when unloaded.""" + + if self._stream: + LOGGER.debug("Invalidating stream") + await self.coordinator.stop_stream(self.device_id, self._stream.streamId) + + if self._stream_refresh_unsub: + self._stream_refresh_unsub() + + async def async_camera_image( + self, width: int | None = None, height: int | None = None + ) -> bytes | None: + """Return bytes of camera image.""" + + return await self.hass.async_add_executor_job(self.placeholder_image) + + async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None: + """Return the source of the stream.""" + + device = self.get_device() + + if not device.properties["supportsStreaming"]: + return await super().async_handle_web_rtc_offer(offer_sdp) + + self._stream = await self.coordinator.start_stream(device_id=self.device_id, sdp_offer=offer_sdp) + self._schedule_stream_refresh() + + return self.decode_stream_answer(self._stream.answer) + + def decode_stream_answer(self, stream_answer: str) -> str: + """Decode the stream RTC offer back to a string.""" + + return base64.decodebytes(stream_answer.encode()).decode() + + @classmethod + @functools.cache + def placeholder_image(cls) -> bytes: + """Return placeholder image to use when no stream is available.""" + return PLACEHOLDER.read_bytes() \ No newline at end of file diff --git a/custom_components/starling_home_hub/const.py b/custom_components/starling_home_hub/const.py index fcb8073..9e41c9b 100644 --- a/custom_components/starling_home_hub/const.py +++ b/custom_components/starling_home_hub/const.py @@ -11,5 +11,6 @@ ATTRIBUTION = "Based on the Starling Home Hub Developer Connect API" PLATFORMS: list[Platform] = [ - Platform.BINARY_SENSOR + Platform.BINARY_SENSOR, + Platform.CAMERA ] diff --git a/custom_components/starling_home_hub/coordinator.py b/custom_components/starling_home_hub/coordinator.py index d5d4e91..72740a0 100644 --- a/custom_components/starling_home_hub/coordinator.py +++ b/custom_components/starling_home_hub/coordinator.py @@ -17,7 +17,7 @@ StarlingHomeHubApiClientError, ) from .const import DOMAIN, LOGGER -from .models import CoordinatorData, SpecificDevice +from .models import CoordinatorData, SpecificDevice, StartStream, StreamStatus # https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities class StarlingHomeHubDataUpdateCoordinator(DataUpdateCoordinator): @@ -40,6 +40,18 @@ def __init__( ) self.client = client + async def start_stream(self, device_id: str, sdp_offer: str) -> StartStream: + """Start a stream.""" + return await self.client.async_start_stream(device_id=device_id, sdp_offer=sdp_offer) + + async def stop_stream(self, device_id: str, stream_id: str) -> StreamStatus: + """Stop a stream.""" + return await self.client.async_stop_stream(device_id=device_id, stream_id=stream_id) + + async def extend_stream(self, device_id: str, stream_id: str) -> StreamStatus: + """Extend a stream.""" + return await self.client.async_extend_stream(device_id=device_id, stream_id=stream_id) + async def fetch_data(self) -> CoordinatorData: """Fetch data for the devices.""" devices = await self.client.async_get_devices() diff --git a/custom_components/starling_home_hub/entity.py b/custom_components/starling_home_hub/entity.py index 8862679..e5e51d6 100644 --- a/custom_components/starling_home_hub/entity.py +++ b/custom_components/starling_home_hub/entity.py @@ -1,7 +1,7 @@ """StarlingHomeHubEntity class.""" from __future__ import annotations -from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ATTRIBUTION, DOMAIN @@ -11,28 +11,36 @@ class StarlingHomeHubEntity(CoordinatorEntity): """StarlingHomeHubEntity class.""" + device_id: str + _attr_attribution = ATTRIBUTION - def __init__(self, coordinator: StarlingHomeHubDataUpdateCoordinator, device: SpecificDevice, entity_description: EntityDescription) -> None: + def __init__(self, coordinator: StarlingHomeHubDataUpdateCoordinator) -> None: """Initialize.""" super().__init__(coordinator) - self.entity_description = entity_description - + device = self.get_device() device_properties = device.properties device_id = device_properties["id"] - self._attr_unique_id = f"{device_id}-{self.entity_description.key}" model = "Unknown" if device_properties["type"] == "protect": model = "Nest Protect" + if device_properties["type"] == "cam": + model = device_properties["cameraModel"] + self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, device_id)}, name=device_properties["name"], model=model, manufacturer="Google", - suggested_area=device_properties["where"] + suggested_area=device_properties["where"], + serial_number=device_properties["serialNumber"] ) + + def get_device(self) -> SpecificDevice: + """Get the actual device data from coordinator.""" + return self.coordinator.data.devices.get(self.device_id) diff --git a/custom_components/starling_home_hub/models.py b/custom_components/starling_home_hub/models.py index 76fdcad..bc00dbd 100644 --- a/custom_components/starling_home_hub/models.py +++ b/custom_components/starling_home_hub/models.py @@ -24,6 +24,7 @@ class Device: # Camera supportsStreaming: bool + cameraModel: str @dataclass class Permissions: @@ -64,3 +65,17 @@ class CoordinatorData: devices: dict[str, SpecificDevice] status: Status + +@dataclass +class StartStream: + """Class for handling starting a WebRTC stream.""" + + status: str + answer: str + streamId: str + +@dataclass +class StreamStatus: + """Class for stream status.""" + + status: str diff --git a/custom_components/starling_home_hub/placeholder.png b/custom_components/starling_home_hub/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..5ccc755abfd3ccda6c43b08cfc8352717a3208c2 GIT binary patch literal 2689 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 mC_k&e;!^T!U#yD2|KB3sag_F5=9bJ) literal 0 HcmV?d00001 From 8dd87166f09c4ca6bae9e7c11a7668af3720fb94 Mon Sep 17 00:00:00 2001 From: Thomas Lomas Date: Wed, 7 Feb 2024 01:07:02 +0000 Subject: [PATCH 2/3] fix lint --- custom_components/starling_home_hub/api.py | 3 +-- custom_components/starling_home_hub/binary_sensor.py | 2 +- custom_components/starling_home_hub/camera.py | 10 +++++----- custom_components/starling_home_hub/coordinator.py | 2 ++ 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/custom_components/starling_home_hub/api.py b/custom_components/starling_home_hub/api.py index fb75e65..7150089 100644 --- a/custom_components/starling_home_hub/api.py +++ b/custom_components/starling_home_hub/api.py @@ -7,7 +7,6 @@ import aiohttp import async_timeout import base64 -from .const import LOGGER class StarlingHomeHubApiClientError(Exception): """Exception to indicate a general API error.""" @@ -79,7 +78,7 @@ async def async_start_stream(self, device_id: str, sdp_offer: str) -> StartStrea return StartStream(**start_stream_response) async def async_stop_stream(self, device_id: str, stream_id: str) -> StreamStatus: - """stop a WebRTC Stream.""" + """Stop a WebRTC Stream.""" stop_stream_response = await self._api_wrapper( method="post", url=self.get_api_url_for_endpoint(f"devices/{device_id}/stream/{stream_id}/stop"), diff --git a/custom_components/starling_home_hub/binary_sensor.py b/custom_components/starling_home_hub/binary_sensor.py index 0e053c1..e4dfba7 100644 --- a/custom_components/starling_home_hub/binary_sensor.py +++ b/custom_components/starling_home_hub/binary_sensor.py @@ -12,7 +12,7 @@ from .const import DOMAIN, LOGGER from .coordinator import StarlingHomeHubDataUpdateCoordinator from .entity import StarlingHomeHubEntity -from .models import CoordinatorData, SpecificDevice, Device +from .models import CoordinatorData, Device from dataclasses import dataclass diff --git a/custom_components/starling_home_hub/camera.py b/custom_components/starling_home_hub/camera.py index 5ac602c..310c8da 100644 --- a/custom_components/starling_home_hub/camera.py +++ b/custom_components/starling_home_hub/camera.py @@ -5,14 +5,11 @@ from datetime import timedelta, datetime, timezone import asyncio import functools -from typing import cast import base64 from homeassistant.components.camera import Camera, CameraEntityFeature, StreamType from homeassistant.components.stream import CONF_EXTRA_PART_WAIT_TIME -from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.event import async_track_point_in_utc_time -from homeassistant.helpers.typing import StateType from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.config_entries import ConfigEntry @@ -79,7 +76,10 @@ def use_stream_for_stills(self) -> bool: """Whether or not to use stream to generate stills.""" device = self.get_device() - return device.properties["supportsStreaming"] + supports_streaming = device.properties["supportsStreaming"] + LOGGER.debug(f"supports streaming: {supports_streaming}") + + return supports_streaming @property def available(self) -> bool: @@ -183,4 +183,4 @@ def decode_stream_answer(self, stream_answer: str) -> str: @functools.cache def placeholder_image(cls) -> bytes: """Return placeholder image to use when no stream is available.""" - return PLACEHOLDER.read_bytes() \ No newline at end of file + return PLACEHOLDER.read_bytes() diff --git a/custom_components/starling_home_hub/coordinator.py b/custom_components/starling_home_hub/coordinator.py index 72740a0..ee94e60 100644 --- a/custom_components/starling_home_hub/coordinator.py +++ b/custom_components/starling_home_hub/coordinator.py @@ -54,6 +54,7 @@ async def extend_stream(self, device_id: str, stream_id: str) -> StreamStatus: async def fetch_data(self) -> CoordinatorData: """Fetch data for the devices.""" + devices = await self.client.async_get_devices() status = await self.client.async_get_status() @@ -68,6 +69,7 @@ async def fetch_data(self) -> CoordinatorData: async def _async_update_data(self) -> CoordinatorData: """Update data via library.""" + try: return await self.fetch_data() except StarlingHomeHubApiClientAuthenticationError as exception: From fb3172294a8f4fa2413b801ef8014a5c93cec393 Mon Sep 17 00:00:00 2001 From: Thomas Lomas Date: Wed, 7 Feb 2024 01:16:49 +0000 Subject: [PATCH 3/3] fix lint --- custom_components/starling_home_hub/camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/starling_home_hub/camera.py b/custom_components/starling_home_hub/camera.py index 310c8da..9e6386a 100644 --- a/custom_components/starling_home_hub/camera.py +++ b/custom_components/starling_home_hub/camera.py @@ -19,8 +19,6 @@ from .entity import StarlingHomeHubEntity from .models import CoordinatorData, StartStream -from dataclasses import dataclass - PLACEHOLDER = Path(__file__).parent / "placeholder.png" STREAM_EXPIRATION_BUFFER = timedelta(seconds=60)