From 993ba633f45d676f95df6ea561baad5f95036afb Mon Sep 17 00:00:00 2001 From: RenierM26 <66512715+RenierM26@users.noreply.github.com> Date: Sat, 7 Jan 2023 15:50:11 +0200 Subject: [PATCH] Add update entity. --- custom_components/ezviz_cloud/__init__.py | 10 +- .../ezviz_cloud/alarm_control_panel.py | 5 +- .../ezviz_cloud/binary_sensor.py | 4 - custom_components/ezviz_cloud/camera.py | 2 +- custom_components/ezviz_cloud/config_flow.py | 18 +-- custom_components/ezviz_cloud/coordinator.py | 8 +- custom_components/ezviz_cloud/entity.py | 4 + custom_components/ezviz_cloud/manifest.json | 4 +- custom_components/ezviz_cloud/switch.py | 5 +- custom_components/ezviz_cloud/update.py | 103 ++++++++++++++++++ 10 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 custom_components/ezviz_cloud/update.py diff --git a/custom_components/ezviz_cloud/__init__.py b/custom_components/ezviz_cloud/__init__.py index 60f4c29..54d70ae 100644 --- a/custom_components/ezviz_cloud/__init__.py +++ b/custom_components/ezviz_cloud/__init__.py @@ -1,6 +1,10 @@ """Support for EZVIZ camera.""" import logging +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_TIMEOUT, CONF_TYPE, CONF_URL, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from pyezviz.client import EzvizClient from pyezviz.exceptions import ( EzvizAuthTokenExpired, @@ -10,11 +14,6 @@ PyEzvizError, ) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_TIMEOUT, CONF_TYPE, CONF_URL, Platform -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady - from .const import ( ATTR_TYPE_CAMERA, ATTR_TYPE_CLOUD, @@ -38,6 +37,7 @@ Platform.CAMERA, Platform.SENSOR, Platform.SWITCH, + Platform.UPDATE, ], } diff --git a/custom_components/ezviz_cloud/alarm_control_panel.py b/custom_components/ezviz_cloud/alarm_control_panel.py index 91fc243..5615795 100644 --- a/custom_components/ezviz_cloud/alarm_control_panel.py +++ b/custom_components/ezviz_cloud/alarm_control_panel.py @@ -3,9 +3,6 @@ from typing import Any -from pyezviz.constants import DefenseModeType -from pyezviz.exceptions import HTTPError - from homeassistant.components.alarm_control_panel import ( AlarmControlPanelEntity, AlarmControlPanelEntityFeature, @@ -21,6 +18,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity +from pyezviz.constants import DefenseModeType +from pyezviz.exceptions import HTTPError from .const import ( ATTR_AWAY, diff --git a/custom_components/ezviz_cloud/binary_sensor.py b/custom_components/ezviz_cloud/binary_sensor.py index 43ac914..a90e3b5 100644 --- a/custom_components/ezviz_cloud/binary_sensor.py +++ b/custom_components/ezviz_cloud/binary_sensor.py @@ -25,10 +25,6 @@ key="alarm_schedules_enabled" ), "encrypted": BinarySensorEntityDescription(key="encrypted"), - "upgrade_available": BinarySensorEntityDescription( - key="upgrade_available", - device_class=BinarySensorDeviceClass.UPDATE, - ), } diff --git a/custom_components/ezviz_cloud/camera.py b/custom_components/ezviz_cloud/camera.py index 3deede5..2103213 100644 --- a/custom_components/ezviz_cloud/camera.py +++ b/custom_components/ezviz_cloud/camera.py @@ -3,7 +3,6 @@ import logging -from pyezviz.exceptions import HTTPError, InvalidHost, PyEzvizError import voluptuous as vol from homeassistant.components import ffmpeg @@ -22,6 +21,7 @@ discovery_flow, entity_platform, ) +from pyezviz.exceptions import HTTPError, InvalidHost, PyEzvizError from .const import ( ATTR_DIRECTION, diff --git a/custom_components/ezviz_cloud/config_flow.py b/custom_components/ezviz_cloud/config_flow.py index 0361a68..f134b92 100644 --- a/custom_components/ezviz_cloud/config_flow.py +++ b/custom_components/ezviz_cloud/config_flow.py @@ -5,15 +5,6 @@ import logging from typing import Any -from pyezviz.client import EzvizClient -from pyezviz.exceptions import ( - AuthTestResultFailed, - EzvizAuthVerificationCode, - InvalidHost, - InvalidURL, - PyEzvizError, -) -from pyezviz.test_cam_rtsp import TestRTSPAuth import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow @@ -28,6 +19,15 @@ ) from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from pyezviz.client import EzvizClient +from pyezviz.exceptions import ( + AuthTestResultFailed, + EzvizAuthVerificationCode, + InvalidHost, + InvalidURL, + PyEzvizError, +) +from pyezviz.test_cam_rtsp import TestRTSPAuth from .const import ( ATTR_SERIAL, diff --git a/custom_components/ezviz_cloud/coordinator.py b/custom_components/ezviz_cloud/coordinator.py index 906b539..85c8a8a 100644 --- a/custom_components/ezviz_cloud/coordinator.py +++ b/custom_components/ezviz_cloud/coordinator.py @@ -3,6 +3,10 @@ import logging from async_timeout import timeout + +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from pyezviz.client import EzvizClient from pyezviz.exceptions import ( EzvizAuthTokenExpired, @@ -12,10 +16,6 @@ PyEzvizError, ) -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed - from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/ezviz_cloud/entity.py b/custom_components/ezviz_cloud/entity.py index 2ab42a9..d6fcf0f 100644 --- a/custom_components/ezviz_cloud/entity.py +++ b/custom_components/ezviz_cloud/entity.py @@ -3,6 +3,7 @@ from typing import Any +from homeassistant.helpers import device_registry from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -24,6 +25,9 @@ def __init__( self._camera_name = self.data["name"] self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, serial)}, + connections={ + (device_registry.CONNECTION_NETWORK_MAC, self.data["mac_address"]), + }, manufacturer=MANUFACTURER, model=self.data["device_sub_category"], name=self.data["name"], diff --git a/custom_components/ezviz_cloud/manifest.json b/custom_components/ezviz_cloud/manifest.json index a292fae..d8ec140 100644 --- a/custom_components/ezviz_cloud/manifest.json +++ b/custom_components/ezviz_cloud/manifest.json @@ -1,11 +1,11 @@ { "domain": "ezviz_cloud", "name": "Ezviz(Beta)", - "version": "0.1.0.4", + "version": "0.1.0.5", "documentation": "https://www.home-assistant.io/integrations/ezviz", "dependencies": ["ffmpeg"], "codeowners": ["@RenierM26", "@baqs"], - "requirements": ["pyezviz==0.2.0.10"], + "requirements": ["pyezviz==0.2.0.12"], "config_flow": true, "iot_class": "cloud_polling" } diff --git a/custom_components/ezviz_cloud/switch.py b/custom_components/ezviz_cloud/switch.py index 55a946f..eb36d7a 100644 --- a/custom_components/ezviz_cloud/switch.py +++ b/custom_components/ezviz_cloud/switch.py @@ -3,13 +3,12 @@ from typing import Any -from pyezviz.constants import DeviceSwitchType -from pyezviz.exceptions import HTTPError, PyEzvizError - from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from pyezviz.constants import DeviceSwitchType +from pyezviz.exceptions import HTTPError, PyEzvizError from .const import DATA_COORDINATOR, DOMAIN from .coordinator import EzvizDataUpdateCoordinator diff --git a/custom_components/ezviz_cloud/update.py b/custom_components/ezviz_cloud/update.py new file mode 100644 index 0000000..a144ec3 --- /dev/null +++ b/custom_components/ezviz_cloud/update.py @@ -0,0 +1,103 @@ +"""Support for Ezviz sensors.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.update import ( + UpdateDeviceClass, + UpdateEntity, + UpdateEntityDescription, + UpdateEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from pyezviz import HTTPError, PyEzvizError + +from .const import DATA_COORDINATOR, DOMAIN +from .coordinator import EzvizDataUpdateCoordinator +from .entity import EzvizEntity + +PARALLEL_UPDATES = 1 + +UPDATE_ENTITY_TYPES: dict[str, UpdateEntityDescription] = { + "version": UpdateEntityDescription( + key="version", + name="Firmware Update", + device_class=UpdateDeviceClass.FIRMWARE, + ) +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Ezviz sensors based on a config entry.""" + coordinator: EzvizDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + + async_add_entities( + [ + EzvizUpdateEntity(coordinator, camera, sensor) + for camera in coordinator.data + for sensor, value in coordinator.data[camera].items() + if sensor in UPDATE_ENTITY_TYPES + if value + ] + ) + + +class EzvizUpdateEntity(EzvizEntity, UpdateEntity): + """Representation of a Ezviz Update entity.""" + + coordinator: EzvizDataUpdateCoordinator + _attr_has_entity_name = True + _attr_supported_features = ( + UpdateEntityFeature.INSTALL + | UpdateEntityFeature.PROGRESS + | UpdateEntityFeature.RELEASE_NOTES + ) + + def __init__( + self, coordinator: EzvizDataUpdateCoordinator, serial: str, sensor: str + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, serial) + self._attr_installed_version = self.data["version"] + self._attr_unique_id = f"{serial}_{sensor}" + self.entity_description = UPDATE_ENTITY_TYPES[sensor] + + @property + def in_progress(self) -> bool | int | None: + """Update installation progress.""" + if self.data["upgrade_in_progress"]: + return self.data["upgrade_percent"] + return False + + @property + def latest_version(self) -> str | None: + """Latest version available for install.""" + if self.data["upgrade_available"]: + return self.data["latest_firmware_info"]["version"] + + return self.installed_version + + def release_notes(self) -> str | None: + """Returns full release notes.""" + if self.data["latest_firmware_info"]: + return self.data["latest_firmware_info"].get("desc") + + async def async_install( + self, version: str | None, backup: bool, **kwargs: Any + ) -> None: + """Install an update.""" + try: + await self.hass.async_add_executor_job( + self.coordinator.ezviz_client.upgrade_device, self._serial + ) + + except (HTTPError, PyEzvizError) as err: + raise PyEzvizError( + f"Failed to update firmware on {self._attr_name}" + ) from err