From f18e4bab60604e286c03d7d64003ab159cadb9a7 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Thu, 30 Sep 2021 07:38:18 -0400 Subject: [PATCH] Add resolution to Amcrest camera unique id (#56207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- homeassistant/components/amcrest/__init__.py | 1 + .../components/amcrest/binary_sensor.py | 12 +++++--- homeassistant/components/amcrest/camera.py | 28 +++++++++++++++++-- homeassistant/components/amcrest/sensor.py | 9 +++++- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index bb8956f8b15..18aa2006f72 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -380,3 +380,4 @@ class AmcrestDevice: stream_source: str resolution: int control_light: bool + channel: int = 0 diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index ea8f15d838c..8d2535a142b 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -170,7 +170,7 @@ class AmcrestBinarySensor(BinarySensorEntity): """Initialize entity.""" self._signal_name = name self._api = device.api - self._channel = 0 # Used in unique id, reserved for future use + self._channel = device.channel self.entity_description: AmcrestSensorEntityDescription = entity_description self._attr_name = f"{name} {entity_description.name}" @@ -195,14 +195,13 @@ class AmcrestBinarySensor(BinarySensorEntity): return _LOGGER.debug(_UPDATE_MSG, self.name) - self._update_unique_id() - if self._api.available: # Send a command to the camera to test if we can still communicate with it. # Override of Http.command() in __init__.py will set self._api.available # accordingly. with suppress(AmcrestError): self._api.current_time # pylint: disable=pointless-statement + self._update_unique_id() self._attr_is_on = self._api.available def _update_others(self) -> None: @@ -210,7 +209,11 @@ class AmcrestBinarySensor(BinarySensorEntity): return _LOGGER.debug(_UPDATE_MSG, self.name) - self._update_unique_id() + try: + self._update_unique_id() + except AmcrestError as error: + log_update_error(_LOGGER, "update", self.name, "binary sensor", error) + return event_code = self.entity_description.event_code if event_code is None: @@ -221,6 +224,7 @@ class AmcrestBinarySensor(BinarySensorEntity): self._attr_is_on = len(self._api.event_channels_happened(event_code)) > 0 except AmcrestError as error: log_update_error(_LOGGER, "update", self.name, "binary sensor", error) + return def _update_unique_id(self) -> None: """Set the unique id.""" diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 3c91607f96d..8333ece1030 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -14,9 +14,11 @@ from haffmpeg.camera import CameraMjpeg import voluptuous as vol from homeassistant.components.camera import SUPPORT_ON_OFF, SUPPORT_STREAM, Camera +from homeassistant.components.camera.const import DOMAIN as CAMERA_DOMAIN from homeassistant.components.ffmpeg import DATA_FFMPEG, FFmpegManager from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream, async_aiohttp_proxy_web, @@ -33,6 +35,7 @@ from .const import ( COMM_TIMEOUT, DATA_AMCREST, DEVICES, + DOMAIN, SERVICE_UPDATE, SNAPSHOT_TIMEOUT, ) @@ -133,7 +136,21 @@ async def async_setup_platform( name = discovery_info[CONF_NAME] device = hass.data[DATA_AMCREST][DEVICES][name] - async_add_entities([AmcrestCam(name, device, hass.data[DATA_FFMPEG])], True) + entity = AmcrestCam(name, device, hass.data[DATA_FFMPEG]) + + # 2021.9.0 introduced unique id's for the camera entity, but these were not + # unique for different resolution streams. If any cameras were configured + # with this version, update the old entity with the new unique id. + serial_number = await hass.async_add_executor_job(lambda: device.api.serial_number) # type: ignore[no-any-return] + serial_number = serial_number.strip() + registry = entity_registry.async_get(hass) + entity_id = registry.async_get_entity_id(CAMERA_DOMAIN, DOMAIN, serial_number) + if entity_id is not None: + _LOGGER.debug("Updating unique id for camera %s", entity_id) + new_unique_id = f"{serial_number}-{device.resolution}-{device.channel}" + registry.async_update_entity(entity_id, new_unique_id=new_unique_id) + + async_add_entities([entity], True) class CannotSnapshot(Exception): @@ -156,6 +173,7 @@ class AmcrestCam(Camera): self._ffmpeg_arguments = device.ffmpeg_arguments self._stream_source = device.stream_source self._resolution = device.resolution + self._channel = device.channel self._token = self._auth = device.authentication self._control_light = device.control_light self._is_recording: bool = False @@ -388,8 +406,12 @@ class AmcrestCam(Camera): else: self._model = "unknown" if self._attr_unique_id is None: - self._attr_unique_id = self._api.serial_number.strip() - _LOGGER.debug("Assigned unique_id=%s", self._attr_unique_id) + serial_number = self._api.serial_number.strip() + if serial_number: + self._attr_unique_id = ( + f"{serial_number}-{self._resolution}-{self._channel}" + ) + _LOGGER.debug("Assigned unique_id=%s", self._attr_unique_id) self.is_streaming = self._get_video() self._is_recording = self._get_recording() self._motion_detection_enabled = self._get_motion_detection() diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 752aabc2c92..f2048654da6 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -78,7 +78,7 @@ class AmcrestSensor(SensorEntity): self.entity_description = description self._signal_name = name self._api = device.api - self._channel = 0 # Used in unique id, reserved for future use + self._channel = device.channel self._unsub_dispatcher: Callable[[], None] | None = None self._attr_name = f"{name} {description.name}" @@ -102,6 +102,13 @@ class AmcrestSensor(SensorEntity): self._attr_unique_id = f"{serial_number}-{sensor_type}-{self._channel}" try: + if self._attr_unique_id is None: + serial_number = self._api.serial_number + if serial_number: + self._attr_unique_id = ( + f"{serial_number}-{sensor_type}-{self._channel}" + ) + if sensor_type == SENSOR_PTZ_PRESET: self._attr_native_value = self._api.ptz_presets_count