Skip to content

Commit

Permalink
Fix #411: Update is shown although the update was already applied
Browse files Browse the repository at this point in the history
  • Loading branch information
JurajNyiri committed Nov 30, 2024
1 parent 3bdaadf commit a198d05
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 39 deletions.
39 changes: 20 additions & 19 deletions custom_components/tapo_control/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import datetime
import hashlib
import logging
import asyncio

from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -453,29 +452,29 @@ async def async_update_data():
):
await syncTime(hass, entry.entry_id)
ts = datetime.datetime.utcnow().timestamp()
if (
ts - hass.data[DOMAIN][entry.entry_id]["lastFirmwareCheck"]
> UPDATE_CHECK_PERIOD
):
hass.data[DOMAIN][entry.entry_id]["latestFirmwareVersion"] = (
if (
ts - hass.data[DOMAIN][entry.entry_id]["lastFirmwareCheck"]
> UPDATE_CHECK_PERIOD
):
LOGGER.debug("Getting latest firmware...")
hass.data[DOMAIN][entry.entry_id]["latestFirmwareVersion"] = (
await getLatestFirmwareVersion(
hass,
entry,
hass.data[DOMAIN][entry.entry_id],
tapoController,
)
)
LOGGER.debug(hass.data[DOMAIN][entry.entry_id]["latestFirmwareVersion"])
for childDevice in hass.data[DOMAIN][entry.entry_id]["childDevices"]:
childDevice["latestFirmwareVersion"] = (
await getLatestFirmwareVersion(
hass,
entry,
hass.data[DOMAIN][entry.entry_id],
tapoController,
childDevice["controller"],
)
)
for childDevice in hass.data[DOMAIN][entry.entry_id][
"childDevices"
]:
childDevice["latestFirmwareVersion"] = (
await getLatestFirmwareVersion(
hass,
entry,
hass.data[DOMAIN][entry.entry_id],
childDevice["controller"],
)
)

# cameras state
LOGGER.debug("async_update_data - before someEntityEnabled check")
Expand Down Expand Up @@ -934,7 +933,9 @@ async def mediaSync(time=None):
else:
LOGGER.warning(err)
except Exception as err:
hass.data[DOMAIN][entry.entry_id]["runningMediaSync"] = False
hass.data[DOMAIN][entry.entry_id][
"runningMediaSync"
] = False
LOGGER.error(err)
else:
LOGGER.debug(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tapo_control/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"codeowners": [
"@JurajNyiri"
],
"version": "5.8.4",
"version": "5.8.5",
"requirements": [
"pytapo==3.3.36"
],
Expand Down
9 changes: 6 additions & 3 deletions custom_components/tapo_control/tapo/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
from homeassistant.components.number import NumberEntity
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.entity import EntityCategory
from homeassistant.util import slugify
from homeassistant.const import (
STATE_UNAVAILABLE,
)

from ..const import BRAND, LOGGER, DOMAIN
from ..const import BRAND, LOGGER
from ..utils import build_device_info


Expand Down Expand Up @@ -79,11 +78,15 @@ def __init__(
self._attr_is_on = False
self._hass = hass
self._attr_icon = icon
self._config_entry = config_entry
self._attr_device_class = device_class
self.updateTapo(entry["camData"])
entry["entities"].append({"entity": self, "entry": entry})

TapoEntity.__init__(self, entry, name_suffix)
UpdateEntity.__init__(self)

self.updateTapo(entry["camData"])

LOGGER.debug(f"Tapo {name_suffix} - init - end")

@property
Expand Down
77 changes: 61 additions & 16 deletions custom_components/tapo_control/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers import device_registry as dr
import datetime

from .const import DOMAIN, LOGGER
from .utils import build_device_info
Expand Down Expand Up @@ -34,6 +36,10 @@ class TapoCamUpdate(UpdateEntity):
def __init__(self, entry: dict, hass: HomeAssistant, config_entry):
self._in_progress = False
self._hass = hass
self._lastDataUpdate = 0
# This is needed because some cameras still report normal firmware update process
# just after hitting update, even if they are in progress, we need to give them time.
self._installRequestedTime = 0
TapoUpdateEntity.__init__(self, "Update", entry, hass, config_entry)

@property
Expand All @@ -50,19 +56,44 @@ def unique_id(self) -> str:
return "{}-{}-{}".format(self._attributes["mac"], self._name, id_suffix).lower()

def updateTapo(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
# prevent instantinous internal refresh with old data triggering update
# on this entity and cancelling in progress update
LOGGER.debug("updateTapo in update entity")
if camData and camData["updated"] > self._lastDataUpdate:
LOGGER.debug(f"Processing new data (updated at {camData["updated"]})...")
self._lastDataUpdate = camData["updated"]
if not camData:
self._state = "unavailable"
else:
self._attributes = camData["basic_info"]
if self._in_progress:
LOGGER.debug("Status of firmware status:")
LOGGER.debug(camData["firmwareUpdateStatus"])
ts = datetime.datetime.utcnow().timestamp()
if (
ts > self._installRequestedTime + 60
and "firmwareUpdateStatus" in camData
and "upgrade_status" in camData["firmwareUpdateStatus"]
and "state" in camData["firmwareUpdateStatus"]["upgrade_status"]
and camData["firmwareUpdateStatus"]["upgrade_status"]["state"]
== "normal"
):
LOGGER.debug(
"Update has been finished, updating information in integration..."
)
# Update Device Registry with new information
deviceRegistry = dr.async_get(self._hass)
newDeviceInfo = build_device_info(camData["basic_info"])
device = deviceRegistry.async_get_device(
newDeviceInfo["identifiers"]
)
deviceRegistry.async_update_device(
device.id, sw_version=newDeviceInfo["sw_version"]
)
# Reset check for firmware check
self._entry["lastFirmwareCheck"] = 0
self._in_progress = False
LOGGER.debug("Update has been fully completed.")

async def async_added_to_hass(self) -> None:
self._enabled = True
Expand All @@ -72,7 +103,11 @@ async def async_will_remove_from_hass(self) -> None:

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

async def async_release_notes(self) -> str:
"""Return the release notes."""
Expand All @@ -84,7 +119,7 @@ async def async_release_notes(self) -> str:
"\\n", "\n"
)
else:
return None
return "No update available."

@property
def in_progress(self) -> bool:
Expand All @@ -100,8 +135,12 @@ def latest_version(self) -> str:
self._entry["latestFirmwareVersion"]
and "version" in self._entry["latestFirmwareVersion"]
):
LOGGER.debug("Latest version - coming from cloud")
LOGGER.debug(self._entry["latestFirmwareVersion"])
return self._entry["latestFirmwareVersion"]["version"]
else:
LOGGER.debug("Latest version - no cloud update")
LOGGER.debug(self._attributes["sw_version"])
return self._attributes["sw_version"]

@property
Expand All @@ -110,6 +149,7 @@ def release_summary(self) -> str:
self._entry["latestFirmwareVersion"]
and "release_log" in self._entry["latestFirmwareVersion"]
):
LOGGER.debug("Release_summary - coming from cloud")
maxLength = 255
releaseLog = self._entry["latestFirmwareVersion"]["release_log"].replace(
"\\n", "\n"
Expand All @@ -120,18 +160,23 @@ def release_summary(self) -> str:
else releaseLog
)
else:
LOGGER.debug("Release_summary - none")
return None

async def async_install(
self,
version,
backup,
):
LOGGER.debug("Install new firmware has been triggerred")
LOGGER.debug(version)
LOGGER.debug(backup)
try:
await self.hass.async_add_executor_job(
self._controller.startFirmwareUpgrade
)
self._installRequestedTime = datetime.datetime.utcnow().timestamp()
self._in_progress = True
await self._coordinator.async_request_refresh()
LOGGER.debug("Install is now in progress...")
except Exception as e:
LOGGER.error(e)
2 changes: 2 additions & 0 deletions custom_components/tapo_control/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,8 @@ async def getCamData(hass, controller):
videoQualities = None
camData["videoQualities"] = videoQualities

camData["updated"] = datetime.datetime.utcnow().timestamp()

LOGGER.debug("getCamData - done")
LOGGER.debug("Processed update data:")
LOGGER.debug(camData)
Expand Down

0 comments on commit a198d05

Please sign in to comment.