Skip to content

Commit

Permalink
Fix AC filter remaining time sensor - step2 (FR #375) (#465)
Browse files Browse the repository at this point in the history
- Fix filter remaining time sensor for ACv2 devices calling specific API method
- Add used time and max time attributes to filter sensors
  • Loading branch information
ollo69 authored Jan 4, 2023
1 parent e195ab3 commit 4808d8d
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 39 deletions.
32 changes: 28 additions & 4 deletions custom_components/smartthinq_sensors/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from .device_helpers import (
DEVICE_ICONS,
WASH_DEVICE_TYPES,
LGEBaseDevice,
LGERangeDevice,
LGERefrigeratorDevice,
LGETempDevice,
Expand All @@ -48,6 +49,8 @@
FEAT_FILTER_BOTTOM_LIFE,
FEAT_FILTER_DUST_LIFE,
FEAT_FILTER_MAIN_LIFE,
FEAT_FILTER_MAIN_MAX,
FEAT_FILTER_MAIN_USE,
FEAT_FILTER_MID_LIFE,
FEAT_FILTER_TOP_LIFE,
FEAT_HALFLOAD,
Expand Down Expand Up @@ -112,6 +115,7 @@ class ThinQSensorEntityDescription(SensorEntityDescription):

unit_fn: Callable[[Any], str] | None = None
value_fn: Callable[[Any], float | str] | None = None
feature_attributes: dict[str, str] | None = None


WASH_DEV_SENSORS: Tuple[ThinQSensorEntityDescription, ...] = (
Expand Down Expand Up @@ -281,6 +285,10 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
icon="mdi:air-filter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
feature_attributes={
"use_time": FEAT_FILTER_MAIN_USE,
"max_time": FEAT_FILTER_MAIN_MAX,
},
),
)
RANGE_SENSORS: Tuple[ThinQSensorEntityDescription, ...] = (
Expand Down Expand Up @@ -395,6 +403,10 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
icon="mdi:air-filter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
feature_attributes={
"use_time": FEAT_FILTER_MAIN_USE,
"max_time": FEAT_FILTER_MAIN_MAX,
},
),
ThinQSensorEntityDescription(
key=FEAT_FILTER_BOTTOM_LIFE,
Expand Down Expand Up @@ -598,7 +610,7 @@ def __init__(
self,
api: LGEDevice,
description: ThinQSensorEntityDescription,
wrapped_device=None,
wrapped_device: LGEBaseDevice | None = None,
):
"""Initialize the sensor."""
super().__init__(api.coordinator)
Expand Down Expand Up @@ -650,6 +662,18 @@ def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity."""
return self._api.assumed_state

@property
def extra_state_attributes(self):
"""Return the optional state attributes."""
features = self.entity_description.feature_attributes
if not (features and self._api.state):
return None
data = {}
for key, feat in features.items():
if val := self._api.state.device_features.get(feat):
data[key] = val
return data

def _get_sensor_state(self):
"""Get current sensor state"""
if self._wrap_device and self.entity_description.value_fn is not None:
Expand Down Expand Up @@ -689,7 +713,7 @@ def __init__(
def extra_state_attributes(self):
"""Return the optional state attributes."""
if not self._is_default:
return None
return super().extra_state_attributes

data = {
ATTR_RUN_COMPLETED: self._wrap_device.run_completed,
Expand Down Expand Up @@ -720,7 +744,7 @@ def __init__(
def extra_state_attributes(self):
"""Return the optional state attributes."""
if not self._is_default:
return None
return super().extra_state_attributes

data = {
ATTR_FRIDGE_TEMP: self._wrap_device.temp_fridge,
Expand Down Expand Up @@ -751,7 +775,7 @@ def __init__(
def extra_state_attributes(self):
"""Return the optional state attributes."""
if not self._is_default:
return None
return super().extra_state_attributes

data = {
ATTR_OVEN_LOWER_TARGET_TEMP: self._wrap_device.oven_lower_target_temp,
Expand Down
6 changes: 5 additions & 1 deletion custom_components/smartthinq_sensors/wideq/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,16 @@
FEAT_OVEN_UPPER_CURRENT_TEMP = "oven_upper_current_temp"
FEAT_OVEN_UPPER_STATE = "oven_upper_state"

# air purifier and dehumidifier device features
# air purifier and ac device filters features
FEAT_FILTER_BOTTOM_LIFE = "filter_bottom_life"
FEAT_FILTER_DUST_LIFE = "filter_dust_life"
FEAT_FILTER_MAIN_LIFE = "filter_main_life"
FEAT_FILTER_MAIN_MAX = "filter_main_max"
FEAT_FILTER_MAIN_USE = "filter_main_use"
FEAT_FILTER_MID_LIFE = "filter_mid_life"
FEAT_FILTER_TOP_LIFE = "filter_top_life"

# air purifier and dehumidifier device features
FEAT_PM1 = "pm1"
FEAT_PM10 = "pm10"
FEAT_PM25 = "pm25"
Expand Down
23 changes: 15 additions & 8 deletions custom_components/smartthinq_sensors/wideq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ async def _get_config_v2(
if self._should_poll or self.client.emulation:
return None

return await self._client.session.device_v2_controls(
payload = await self._client.session.device_v2_controls(
self._device_info.device_id,
ctrl_key,
command,
Expand All @@ -576,13 +576,19 @@ async def _get_config_v2(
ctrl_path=ctrl_path,
)

result = payload.get("result")
if not result or "data" not in result:
return None
return result["data"]

async def _get_config(self, key):
"""
Look up a device's configuration for a given value.
The response is parsed as base64-encoded JSON.
"""
if not self._should_poll:
return
return None

data = await self._client.session.get_device_config(
self._device_info.device_id, key
)
Expand All @@ -593,7 +599,8 @@ async def _get_config(self, key):
async def _get_control(self, key):
"""Look up a device's control value."""
if not self._should_poll:
return
return None

data = await self._client.session.get_device_config(
self._device_info.device_id,
key,
Expand Down Expand Up @@ -650,13 +657,13 @@ async def _get_device_snapshot(self, query_device=False):
try:
await self._pre_update_v2()
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug("Error %s calling pre_update function", exc)
_LOGGER.debug("Error calling pre_update function: %s", exc)

return await self._mon.refresh(query_device)

async def _additional_poll(self, poll_interval: int):
"""Perform dedicated additional device poll with a slower rate."""
if poll_interval <= 0 or self.client.emulation:
if poll_interval <= 0:
return
call_time = datetime.utcnow()
if self._last_additional_poll is None:
Expand All @@ -670,12 +677,12 @@ async def _additional_poll(self, poll_interval: int):
try:
await self._get_device_info()
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug("Error %s calling additional poll V1 methods", exc)
_LOGGER.debug("Error calling additional poll V1 methods: %s", exc)
else:
try:
await self._get_device_info_v2()
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug("Error %s calling additional poll V2 methods", exc)
_LOGGER.debug("Error calling additional poll V2 methods: %s", exc)

async def _device_poll(
self,
Expand Down Expand Up @@ -859,7 +866,7 @@ def _get_filter_life(
return None

try:
return int((use_time / max_time) * 100)
return [int((use_time / max_time) * 100), use_time, max_time]
except ValueError:
return None

Expand Down
46 changes: 27 additions & 19 deletions custom_components/smartthinq_sensors/wideq/devices/ac.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from ..const import (
FEAT_ENERGY_CURRENT,
FEAT_FILTER_MAIN_LIFE,
FEAT_FILTER_MAIN_MAX,
FEAT_FILTER_MAIN_USE,
FEAT_HOT_WATER_TEMP,
FEAT_HUMIDITY,
FEAT_LIGHTING_DISPLAY,
Expand Down Expand Up @@ -78,7 +80,7 @@

FILTER_TYPES = [
[
FEAT_FILTER_MAIN_LIFE,
[FEAT_FILTER_MAIN_LIFE, FEAT_FILTER_MAIN_USE, FEAT_FILTER_MAIN_MAX],
[STATE_FILTER_V1_USE, "airState.filterMngStates.useTime"],
[STATE_FILTER_V1_MAX, "airState.filterMngStates.maxTime"],
None,
Expand Down Expand Up @@ -290,7 +292,7 @@ def __init__(
self._current_power = None
self._current_power_supported = True

self._filter_status = {}
self._filter_status = None
self._filter_status_supported = True

self._unit_conv = TempUnitConversion()
Expand Down Expand Up @@ -843,36 +845,35 @@ async def get_power(self):
try:
value = await self._get_config(STATE_POWER_V1)
return value[STATE_POWER_V1]
except (ValueError, InvalidRequestError):
except (ValueError, InvalidRequestError) as exc:
# Device does not support whole unit instant power usage
_LOGGER.debug("Error calling get_power methods: %s", exc)
self._current_power_supported = False
return None

async def get_filter_state(self):
"""Get information about the filter."""
if not self._filter_status_supported:
return {}
return None
try:
return await self._get_config(STATE_FILTER_V1)
except (ValueError, InvalidRequestError):
except (ValueError, InvalidRequestError) as exc:
# Device does not support filter status
_LOGGER.debug("Error calling get_filter_state methods: %s", exc)
self._filter_status_supported = False
return {}
return None

async def get_filter_state_v2(self):
"""Get information about the filter."""
if not self._filter_status_supported:
return {}
return None
try:
_LOGGER.info("Entering GetFilerStatus V2")
result = await self._get_config_v2(CTRL_FILTER_V2, "Get")
_LOGGER.info("GetFilerStatus V2: %s", result)
return result
except (ValueError, InvalidRequestError) as ex:
return await self._get_config_v2(CTRL_FILTER_V2, "Get")
except (ValueError, InvalidRequestError) as exc:
# Device does not support filter status
_LOGGER.info("GetFilerStatus V2 error: %s", ex)
_LOGGER.debug("Error calling get_filter_state_v2 methods: %s", exc)
self._filter_status_supported = False
return {}
return None

async def set(
self, ctrl_key, command, *, key=None, value=None, data=None, ctrl_path=None
Expand Down Expand Up @@ -914,7 +915,7 @@ async def _get_device_info_v2(self):
Override in specific device to call requested methods.
"""
# this commands is to get filter status on V2 device
await self.get_filter_state_v2()
self._filter_status = await self.get_filter_state_v2()

async def poll(self) -> AirConditionerStatus | None:
"""Poll the device's current state."""
Expand All @@ -925,11 +926,15 @@ async def poll(self) -> AirConditionerStatus | None:
)
if not res:
return None

# update power for ACv1
if self._should_poll and not self.is_air_to_water:
if self._current_power is not None:
res[STATE_POWER_V1] = self._current_power
if self._filter_status:
res.update(self._filter_status)

# update filter status
if self._filter_status:
res.update(self._filter_status)

self._status = AirConditionerStatus(self, res)
if self._temperature_step == TEMP_STEP_WHOLE:
Expand Down Expand Up @@ -1153,8 +1158,11 @@ def filters_life(self):
for filter_def in FILTER_TYPES:
status = self._get_filter_life(filter_def[1], filter_def[2])
if status is not None:
self._update_feature(filter_def[0], status, False)
result[filter_def[0]] = status
for index, feat in enumerate(filter_def[0]):
if index >= len(status):
break
self._update_feature(feat, status[index], False)
result[feat] = status[index]

return result

Expand Down
19 changes: 12 additions & 7 deletions custom_components/smartthinq_sensors/wideq/devices/airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
FEAT_FILTER_BOTTOM_LIFE,
FEAT_FILTER_DUST_LIFE,
FEAT_FILTER_MAIN_LIFE,
FEAT_FILTER_MAIN_MAX,
FEAT_FILTER_MAIN_USE,
FEAT_FILTER_MID_LIFE,
FEAT_FILTER_TOP_LIFE,
FEAT_HUMIDITY,
Expand Down Expand Up @@ -41,31 +43,31 @@

FILTER_TYPES = [
[
FEAT_FILTER_MAIN_LIFE,
[FEAT_FILTER_MAIN_LIFE, FEAT_FILTER_MAIN_USE, FEAT_FILTER_MAIN_MAX],
["FilterUse", "airState.filterMngStates.useTime"],
["FilterMax", "airState.filterMngStates.maxTime"],
None,
],
[
FEAT_FILTER_TOP_LIFE,
[FEAT_FILTER_TOP_LIFE],
["FilterUseTop", "airState.filterMngStates.useTimeTop"],
["FilterMaxTop", "airState.filterMngStates.maxTimeTop"],
["@SUPPORT_TOP_HUMIDIFILTER", "@SUPPORT_D_PLUS_TOP"],
],
[
FEAT_FILTER_MID_LIFE,
[FEAT_FILTER_MID_LIFE],
["FilterUseMiddle", "airState.filterMngStates.useTimeMiddle"],
["FilterMaxMiddle", "airState.filterMngStates.maxTimeMiddle"],
["@SUPPORT_MID_HUMIDIFILTER"],
],
[
FEAT_FILTER_BOTTOM_LIFE,
[FEAT_FILTER_BOTTOM_LIFE],
["FilterUseBottom", "airState.filterMngStates.useTimeBottom"],
["FilterMaxBottom", "airState.filterMngStates.maxTimeBottom"],
["@SUPPORT_BOTTOM_PREFILTER"],
],
[
FEAT_FILTER_DUST_LIFE,
[FEAT_FILTER_DUST_LIFE],
["FilterUseDeodor", "airState.filterMngStates.useTimeDeodor"],
["FilterMaxDeodor", "airState.filterMngStates.maxTimeDeodor"],
["@SUPPORT_BOTTOM_DUSTCOLLECTION"],
Expand Down Expand Up @@ -361,8 +363,11 @@ def filters_life(self):
filter_def[1], filter_def[2], filter_def[3], support_key
)
if status is not None:
self._update_feature(filter_def[0], status, False)
result[filter_def[0]] = status
for index, feat in enumerate(filter_def[0]):
if index >= len(status):
break
self._update_feature(feat, status[index], False)
result[feat] = status[index]

return result

Expand Down

0 comments on commit 4808d8d

Please sign in to comment.