diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 668fe479e1f..8f8bcab8ede 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -621,3 +621,23 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity): if not is_on: self._event = None self._attr_extra_state_attributes = {} + + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the is_on, _attr_extra_state_attributes, and available are ever + updated for these entities, and since the websocket update for the + device will trigger an update for all entities connected to the device, + we want to avoid writing state unless something has actually changed. + """ + previous_is_on = self._attr_is_on + previous_available = self._attr_available + previous_extra_state_attributes = self._attr_extra_state_attributes + self._async_update_device_from_protect(device) + if ( + self._attr_is_on != previous_is_on + or self._attr_extra_state_attributes != previous_extra_state_attributes + or self._attr_available != previous_available + ): + self.async_write_ha_state() diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 3306743b707..bc93c156866 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -193,3 +193,17 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity): if self.entity_description.ufp_press is not None: await getattr(self.device, self.entity_description.ufp_press)() + + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only available is updated for these entities, and since the websocket + update for the device will trigger an update for all entities connected + to the device, we want to avoid writing state unless something has + actually changed. + """ + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if self._attr_available != previous_available: + self.async_write_ha_state() diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index c3f4e58e247..df5ea40d4a9 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -115,6 +115,26 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): ) self._attr_available = is_connected and updated_device.feature_flags.has_speaker + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the state, volume, and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_state = self._attr_state + previous_available = self._attr_available + previous_volume_level = self._attr_volume_level + self._async_update_device_from_protect(device) + if ( + self._attr_state != previous_state + or self._attr_volume_level != previous_volume_level + or self._attr_available != previous_available + ): + self.async_write_ha_state() + async def async_set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 247e401b2ca..08bc9f38527 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -268,3 +268,21 @@ class ProtectNumbers(ProtectDeviceEntity, NumberEntity): async def async_set_native_value(self, value: float) -> None: """Set new value.""" await self.entity_description.ufp_set(self.device, value) + + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the native value and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_value = self._attr_native_value + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if ( + self._attr_native_value != previous_value + or self._attr_available != previous_available + ): + self.async_write_ha_state() diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 26a03fb7967..7605be17fc9 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -349,9 +349,9 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity): description: ProtectSelectEntityDescription, ) -> None: """Initialize the unifi protect select entity.""" + self._async_set_options(data, description) super().__init__(data, device, description) self._attr_name = f"{self.device.display_name} {self.entity_description.name}" - self._async_set_options() @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: @@ -366,31 +366,28 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity): _LOGGER.debug( "Updating dynamic select options for %s", entity_description.name ) - self._async_set_options() + self._async_set_options(self.data, entity_description) + if (unifi_value := entity_description.get_ufp_value(device)) is None: + unifi_value = TYPE_EMPTY_VALUE + self._attr_current_option = self._unifi_to_hass_options.get( + unifi_value, unifi_value + ) @callback - def _async_set_options(self) -> None: + def _async_set_options( + self, data: ProtectData, description: ProtectSelectEntityDescription + ) -> None: """Set options attributes from UniFi Protect device.""" - - if self.entity_description.ufp_options is not None: - options = self.entity_description.ufp_options + if (ufp_options := description.ufp_options) is not None: + options = ufp_options else: - assert self.entity_description.ufp_options_fn is not None - options = self.entity_description.ufp_options_fn(self.data.api) + assert description.ufp_options_fn is not None + options = description.ufp_options_fn(data.api) self._attr_options = [item["name"] for item in options] self._hass_to_unifi_options = {item["name"]: item["id"] for item in options} self._unifi_to_hass_options = {item["id"]: item["name"] for item in options} - @property - def current_option(self) -> str: - """Return the current selected option.""" - - unifi_value = self.entity_description.get_ufp_value(self.device) - if unifi_value is None: - unifi_value = TYPE_EMPTY_VALUE - return self._unifi_to_hass_options.get(unifi_value, unifi_value) - async def async_select_option(self, option: str) -> None: """Change the Select Entity Option.""" @@ -404,3 +401,23 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity): if self.entity_description.ufp_enum_type is not None: unifi_value = self.entity_description.ufp_enum_type(unifi_value) await self.entity_description.ufp_set(self.device, unifi_value) + + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the options, option, and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_option = self._attr_current_option + previous_options = self._attr_options + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if ( + self._attr_current_option != previous_option + or self._attr_options != previous_options + or self._attr_available != previous_available + ): + self.async_write_ha_state() diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index d842b13b015..756da49eb4d 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -710,22 +710,56 @@ class ProtectDeviceSensor(ProtectDeviceEntity, SensorEntity): entity_description: ProtectSensorEntityDescription - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) self._attr_native_value = self.entity_description.get_ufp_value(self.device) + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the native value and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_value = self._attr_native_value + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if ( + self._attr_native_value != previous_value + or self._attr_available != previous_available + ): + self.async_write_ha_state() + class ProtectNVRSensor(ProtectNVREntity, SensorEntity): """A Ubiquiti UniFi Protect Sensor.""" entity_description: ProtectSensorEntityDescription - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) self._attr_native_value = self.entity_description.get_ufp_value(self.device) + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the native value and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_value = self._attr_native_value + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if ( + self._attr_native_value != previous_value + or self._attr_available != previous_available + ): + self.async_write_ha_state() + class ProtectEventSensor(EventEntityMixin, SensorEntity): """A UniFi Protect Device Sensor with access tokens.""" diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index ea2d8256cbe..f1e6185b010 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -420,21 +420,36 @@ class ProtectSwitch(ProtectDeviceEntity, SwitchEntity): self._attr_name = f"{self.device.display_name} {self.entity_description.name}" self._switch_type = self.entity_description.key - @property - def is_on(self) -> bool: - """Return true if device is on.""" - return self.entity_description.get_ufp_value(self.device) is True + def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + super()._async_update_device_from_protect(device) + self._attr_is_on = self.entity_description.get_ufp_value(self.device) is True async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" - await self.entity_description.ufp_set(self.device, True) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - await self.entity_description.ufp_set(self.device, False) + @callback + def _async_updated_event(self, device: ProtectModelWithId) -> None: + """Call back for incoming data that only writes when state has changed. + + Only the is_on and available are ever updated for these + entities, and since the websocket update for the device will trigger + an update for all entities connected to the device, we want to avoid + writing state unless something has actually changed. + """ + previous_is_on = self._attr_is_on + previous_available = self._attr_available + self._async_update_device_from_protect(device) + if ( + self._attr_is_on != previous_is_on + or self._attr_available != previous_available + ): + self.async_write_ha_state() + class ProtectNVRSwitch(ProtectNVREntity, SwitchEntity): """A UniFi Protect NVR Switch."""