Skip to content

Commit

Permalink
Merge pull request #168 from JurajNyiri/updateEntity
Browse files Browse the repository at this point in the history
Add: Update camera via Home Assistant
  • Loading branch information
JurajNyiri authored Apr 7, 2022
2 parents 5039fa8 + 5ef8800 commit 6dda7b1
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 4 deletions.
22 changes: 22 additions & 0 deletions custom_components/tapo_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
SOUND_DETECTION_PEAK,
SOUND_DETECTION_RESET,
TIME_SYNC_PERIOD,
UPDATE_CHECK_PERIOD,
)
from .utils import (
registerController,
Expand All @@ -32,6 +33,7 @@
update_listener,
initOnvifEvents,
syncTime,
getLatestFirmwareVersion,
)


Expand Down Expand Up @@ -194,6 +196,16 @@ async def async_update_data():
> TIME_SYNC_PERIOD
):
await syncTime(hass, entry)
ts = datetime.datetime.utcnow().timestamp()
if (
ts - hass.data[DOMAIN][entry.entry_id]["lastFirmwareCheck"]
> UPDATE_CHECK_PERIOD
):
hass.data[DOMAIN][entry.entry_id][
"latestFirmwareVersion"
] = await getLatestFirmwareVersion(
hass, entry, hass.data[DOMAIN][entry.entry_id]["controller"]
)

# cameras state
someCameraEnabled = False
Expand All @@ -217,6 +229,11 @@ async def async_update_data():
and entity._enable_sound_detection
):
await entity.startNoiseDetection()
if hass.data[DOMAIN][entry.entry_id]["updateEntity"]._enabled:
hass.data[DOMAIN][entry.entry_id]["updateEntity"].updateCam(camData)
hass.data[DOMAIN][entry.entry_id][
"updateEntity"
].async_schedule_update_ha_state(True)

tapoCoordinator = DataUpdateCoordinator(
hass, LOGGER, name="Tapo resource status", update_method=async_update_data,
Expand All @@ -230,6 +247,8 @@ async def async_update_data():
"coordinator": tapoCoordinator,
"camData": camData,
"lastTimeSync": 0,
"lastFirmwareCheck": 0,
"latestFirmwareVersion": False,
"motionSensorCreated": False,
"eventsDevice": False,
"onvifManagement": False,
Expand All @@ -252,6 +271,9 @@ async def async_update_data():
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "camera")
)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "update")
)

async def unsubscribe(event):
if hass.data[DOMAIN][entry.entry_id]["events"]:
Expand Down
1 change: 1 addition & 0 deletions custom_components/tapo_control/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@
LOGGER = logging.getLogger("custom_components." + DOMAIN)

TIME_SYNC_PERIOD = 3600
UPDATE_CHECK_PERIOD = 86400
6 changes: 3 additions & 3 deletions custom_components/tapo_control/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"documentation": "https://github.com/JurajNyiri/HomeAssistant-Tapo-Control",
"issue_tracker": "https://github.com/JurajNyiri/HomeAssistant-Tapo-Control/issues",
"codeowners": ["@JurajNyiri"],
"version": "3.4.1",
"requirements": ["pytapo==1.2.1", "onvif-zeep-async==1.2.0"],
"version": "3.5.0",
"requirements": ["pytapo==2.1", "onvif-zeep-async==1.2.0"],
"dependencies": ["ffmpeg"],
"config_flow": true,
"homeassistant": "2021.2.0",
"homeassistant": "2022.4.0",
"dhcp": [
{
"hostname": "c200_*",
Expand Down
141 changes: 141 additions & 0 deletions custom_components/tapo_control/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
from typing import Callable
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
from .const import DOMAIN, LOGGER
from homeassistant.util import slugify
from pytapo import Tapo


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: Callable
):
hass.data[DOMAIN][entry.entry_id]["updateEntity"] = TapoCamUpdate(
hass, entry, hass.data[DOMAIN][entry.entry_id]
)
async_add_entities([hass.data[DOMAIN][entry.entry_id]["updateEntity"]])


class TapoCamUpdate(UpdateEntity):
def __init__(
self, hass: HomeAssistant, entry: dict, tapoData: Tapo,
):
super().__init__()
self._controller = tapoData["controller"]
self._coordinator = tapoData["coordinator"]
self._entry = entry
self._hass = hass
self._enabled = False
self._attributes = tapoData["camData"]["basic_info"]
self._in_progress = False

def updateCam(self, camData):
if not camData:
self._state = "unavailable"
else:
self._attributes = camData["basic_info"]
if (
self._in_progress
and "firmwareUpdateStatus" in camData
and "upgrade_status" in camData["firmwareUpdateStatus"]
and "state" in camData["firmwareUpdateStatus"]["upgrade_status"]
and camData["firmwareUpdateStatus"]["upgrade_status"]["state"]
== "normal"
):
self._in_progress = False

async def async_added_to_hass(self) -> None:
self._enabled = True

async def async_will_remove_from_hass(self) -> None:
self._enabled = False

@property
def supported_features(self):
return UpdateEntityFeature.INSTALL | UpdateEntityFeature.RELEASE_NOTES

async def async_release_notes(self) -> str:
"""Return the release notes."""
if (
self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
and "release_log"
in self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
):
return self._hass.data[DOMAIN][self._entry.entry_id][
"latestFirmwareVersion"
]["release_log"].replace("\\n", "\n")
else:
return None

@property
def name(self) -> str:
return "Camera - " + self._attributes["device_alias"]

@property
def device_info(self):
return {
"identifiers": {
(DOMAIN, slugify(f"{self._attributes['mac']}_tapo_control"))
},
"name": self._attributes["device_alias"],
"manufacturer": "TP-Link",
"model": self._attributes["device_model"],
"sw_version": self._attributes["sw_version"],
}

@property
def in_progress(self) -> bool:
return self._in_progress

@property
def installed_version(self) -> str:
return self._attributes["sw_version"]

@property
def latest_version(self) -> str:
if (
self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
and "version"
in self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
):
return self._hass.data[DOMAIN][self._entry.entry_id][
"latestFirmwareVersion"
]["version"]
else:
return self._attributes["sw_version"]

@property
def release_summary(self) -> str:
if (
self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
and "release_log"
in self._hass.data[DOMAIN][self._entry.entry_id]["latestFirmwareVersion"]
):
maxLength = 255
releaseLog = self._hass.data[DOMAIN][self._entry.entry_id][
"latestFirmwareVersion"
]["release_log"].replace("\\n", "\n")
return (
(releaseLog[: maxLength - 3] + "...")
if len(releaseLog) > maxLength
else releaseLog
)
else:
return None

@property
def title(self) -> str:
return "Tapo Camera: {0}".format(self._attributes["device_alias"])

async def async_install(
self, version, backup,
):
try:
await self.hass.async_add_executor_job(
self._controller.startFirmwareUpgrade
)
self._in_progress = True
await self._coordinator.async_request_refresh()
except Exception as e:
LOGGER.error(e)

31 changes: 31 additions & 0 deletions custom_components/tapo_control/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ async def getCamData(hass, controller):
else:
camData["presets"] = {}

try:
firmwareUpdateStatus = await hass.async_add_executor_job(
controller.getFirmwareUpdateStatus
)
firmwareUpdateStatus = firmwareUpdateStatus["cloud_config"]
except Exception:
firmwareUpdateStatus = None
camData["firmwareUpdateStatus"] = firmwareUpdateStatus

return camData


Expand Down Expand Up @@ -207,6 +216,28 @@ async def update_listener(hass, entry):
await setupOnvif(hass, entry)


async def getLatestFirmwareVersion(hass, entry, controller):
hass.data[DOMAIN][entry.entry_id][
"lastFirmwareCheck"
] = datetime.datetime.utcnow().timestamp()
try:
updateInfo = await hass.async_add_executor_job(controller.isUpdateAvailable)
if (
"version"
in updateInfo["result"]["responses"][1]["result"]["cloud_config"][
"upgrade_info"
]
):
updateInfo = updateInfo["result"]["responses"][1]["result"]["cloud_config"][
"upgrade_info"
]
else:
updateInfo = False
except Exception:
updateInfo = False
return updateInfo


async def syncTime(hass, entry):
device_mgmt = hass.data[DOMAIN][entry.entry_id]["onvifManagement"]
if device_mgmt:
Expand Down
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "name": "Tapo: Cameras Control", "homeassistant": "2021.3.0" }
{ "name": "Tapo: Cameras Control", "homeassistant": "2022.4.0" }

0 comments on commit 6dda7b1

Please sign in to comment.