From 3b42c4654e99e9965e9a3ee6c6f8e865d06a99e4 Mon Sep 17 00:00:00 2001 From: Jimmy Everling Date: Tue, 24 Sep 2024 09:07:44 +0200 Subject: [PATCH 1/2] Fixed issue with cached data being fetched from the API --- .../panasonic_cc/pcomfortcloud/constants.py | 4 ++++ .../panasonic_cc/pcomfortcloud/panasonicdevice.py | 15 ++++++++++++++- custom_components/panasonic_cc/sensor.py | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/custom_components/panasonic_cc/pcomfortcloud/constants.py b/custom_components/panasonic_cc/pcomfortcloud/constants.py index d226281..db8c8ba 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/constants.py +++ b/custom_components/panasonic_cc/pcomfortcloud/constants.py @@ -90,6 +90,10 @@ class IAutoXMode(Enum): Off = 1 On = 2 +class StatusDataMode(Enum): + LIVE = 0 + CACHED = 1 + INVALID_TEMPERATURE = 126 DEFAULT_X_APP_VERSION = "1.21.0" diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py index 04c3378..8aac60e 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py @@ -16,7 +16,7 @@ def read_enum(json, key, type, default_value): try: return type(json[key]) except Exception as es: - _LOGGER.warn("Error reading property '%s' with value '%s'", key, json[key], exc_info= es) + _LOGGER.warning("Error reading property '%s' with value '%s'", key, json[key], exc_info= es) return default_value def read_value(json, key, default_value): @@ -34,6 +34,7 @@ def __init__(self, json = None) -> None: self.model = '' self._has_parameters = False self._raw = None + self._status_data_mode = constants.StatusDataMode.LIVE self.load(json) @@ -58,6 +59,12 @@ def is_valid(self): @property def raw(self): return self._raw + + @property + def status_data_mode(self): + return self._status_data_mode + + class PanasonicDevice: @@ -66,11 +73,16 @@ def __init__(self, info: PanasonicDeviceInfo, json = None) -> None: self._features: PanasonicDeviceFeatures = None self._parameters: PanasonicDeviceParameters = None self._last_update = datetime.now(timezone.utc) + self._timestamp: datetime = None self.load(json) @property def id(self)->str: return self.info.id + + @property + def timestamp(self)->datetime: + return self._timestamp @property def info(self) -> PanasonicDeviceInfo: @@ -150,6 +162,7 @@ def load(self, json) -> bool: has_changed = True if self._parameters.load(json_parameters) else has_changed if has_changed: self._last_update = datetime.now(timezone.utc) + self._timestamp = datetime.fromtimestamp(json['timestamp'] / 1000, timezone.utc) return has_changed diff --git a/custom_components/panasonic_cc/sensor.py b/custom_components/panasonic_cc/sensor.py index 3f38f10..8647400 100644 --- a/custom_components/panasonic_cc/sensor.py +++ b/custom_components/panasonic_cc/sensor.py @@ -68,6 +68,19 @@ class PanasonicEnergySensorEntityDescription(SensorEntityDescription): is_available=lambda device: True, entity_registry_enabled_default=False, ) +DATA_AGE_DESCRIPTION = PanasonicSensorEntityDescription( + key="data_age", + translation_key="data_age", + name="Data Age", + icon="mdi:clock-outline", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=None, + native_unit_of_measurement=None, + get_state=lambda device: device.timestamp, + is_available=lambda device: True, + entity_registry_enabled_default=True, +) DAILY_ENERGY_DESCRIPTION = PanasonicEnergySensorEntityDescription( key="daily_energy_sensor", translation_key="daily_energy_sensor", @@ -151,6 +164,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities.append(PanasonicSensorEntity(coordinator, INSIDE_TEMPERATURE_DESCRIPTION)) entities.append(PanasonicSensorEntity(coordinator, OUTSIDE_TEMPERATURE_DESCRIPTION)) entities.append(PanasonicSensorEntity(coordinator, LAST_UPDATE_TIME_DESCRIPTION)) + entities.append(PanasonicSensorEntity(coordinator, DATA_AGE_DESCRIPTION)) if coordinator.device.has_zones: for zone in coordinator.device.parameters.zones: entities.append(PanasonicSensorEntity( From 1d853fac6b21c34ae574f371e4e996841962ea81 Mon Sep 17 00:00:00 2001 From: Jimmy Everling Date: Tue, 24 Sep 2024 10:45:37 +0200 Subject: [PATCH 2/2] Added fallback and cached data request --- .../panasonic_cc/pcomfortcloud/apiclient.py | 21 +++++++++++++++---- .../pcomfortcloud/panasonicdevice.py | 5 +++++ custom_components/panasonic_cc/sensor.py | 15 +++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/custom_components/panasonic_cc/pcomfortcloud/apiclient.py b/custom_components/panasonic_cc/pcomfortcloud/apiclient.py index bdff5cf..4c40034 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/apiclient.py +++ b/custom_components/panasonic_cc/pcomfortcloud/apiclient.py @@ -3,7 +3,6 @@ ''' import logging -import re import aiohttp import time from datetime import datetime @@ -43,6 +42,7 @@ def __init__(self, self._groups = None self._devices: list[PanasonicDeviceInfo] = None self._unknown_devices: list[PanasonicDeviceInfo] = [] + self._cache_devices = {} self._device_indexer = {} self._raw = raw @@ -134,14 +134,27 @@ async def history(self, device_id, mode, date, time_zone=""): } + async def _get_device_status(self, device_info: PanasonicDeviceInfo): + if (device_info.status_data_mode == constants.StatusDataMode.LIVE + or (device_info.id in self._cache_devices and self._cache_devices[device_info.id] <= 0)): + try: + json_response = await self.execute_get(self._get_device_status_url(device_info.guid), "get_status", 200) + device_info.status_data_mode = constants.StatusDataMode.LIVE + return json_response + except Exception as e: + _LOGGER.warning("Failed to get live status for device {} switching to cached data.".format(device_info.guid)) + device_info.status_data_mode = constants.StatusDataMode.CACHED + self._cache_devices[device_info.id] = 10 + json_response = await self.execute_get(self._get_device_status_now_url(device_info.guid), "get_status", 200) + self._cache_devices[device_info.id] -= 1 + return json_response async def get_device(self, device_info: PanasonicDeviceInfo) -> PanasonicDevice: - json_response = await self.execute_get(self._get_device_status_now_url(device_info.guid), "get_device", 200) + json_response = await self._get_device_status(device_info) return PanasonicDevice(device_info, json_response) async def try_update_device(self, device: PanasonicDevice) -> bool: - device_guid = device.info.guid - json_response = await self.execute_get(self._get_device_status_now_url(device_guid), "try_update", 200) + json_response = await self._get_device_status(device.info) return device.load(json_response) async def get_aquarea_device(self, device_info: PanasonicDeviceInfo): diff --git a/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py b/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py index 8aac60e..ff82423 100644 --- a/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py +++ b/custom_components/panasonic_cc/pcomfortcloud/panasonicdevice.py @@ -63,6 +63,11 @@ def raw(self): @property def status_data_mode(self): return self._status_data_mode + @status_data_mode.setter + def status_data_mode(self, value: constants.StatusDataMode): + self._status_data_mode = value + + diff --git a/custom_components/panasonic_cc/sensor.py b/custom_components/panasonic_cc/sensor.py index 8647400..3aed589 100644 --- a/custom_components/panasonic_cc/sensor.py +++ b/custom_components/panasonic_cc/sensor.py @@ -11,6 +11,7 @@ ) from .pcomfortcloud.panasonicdevice import PanasonicDevice, PanasonicDeviceEnergy, PanasonicDeviceZone +from .pcomfortcloud import constants from .const import ( DOMAIN, @@ -81,6 +82,19 @@ class PanasonicEnergySensorEntityDescription(SensorEntityDescription): is_available=lambda device: True, entity_registry_enabled_default=True, ) +DATA_MODE_DESCRIPTION = PanasonicSensorEntityDescription( + key="status_data_mode", + translation_key="status_data_mode", + name="Data Mode", + options=[opt.name for opt in constants.StatusDataMode], + device_class=SensorDeviceClass.ENUM, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=None, + native_unit_of_measurement=None, + get_state=lambda device: device.info.status_data_mode.name, + is_available=lambda device: True, + entity_registry_enabled_default=True, +) DAILY_ENERGY_DESCRIPTION = PanasonicEnergySensorEntityDescription( key="daily_energy_sensor", translation_key="daily_energy_sensor", @@ -165,6 +179,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities.append(PanasonicSensorEntity(coordinator, OUTSIDE_TEMPERATURE_DESCRIPTION)) entities.append(PanasonicSensorEntity(coordinator, LAST_UPDATE_TIME_DESCRIPTION)) entities.append(PanasonicSensorEntity(coordinator, DATA_AGE_DESCRIPTION)) + entities.append(PanasonicSensorEntity(coordinator, DATA_MODE_DESCRIPTION)) if coordinator.device.has_zones: for zone in coordinator.device.parameters.zones: entities.append(PanasonicSensorEntity(