Skip to content

Commit

Permalink
Add AC filter status support (FR #375) (#445)
Browse files Browse the repository at this point in the history
  • Loading branch information
ollo69 authored Dec 24, 2022
1 parent 44c665c commit fe9d690
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 42 deletions.
7 changes: 7 additions & 0 deletions custom_components/smartthinq_sensors/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
),
ThinQSensorEntityDescription(
key=FEAT_FILTER_MAIN_LIFE,
name="Filter Remaining Life",
icon="mdi:air-filter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
),
)
RANGE_SENSORS: Tuple[ThinQSensorEntityDescription, ...] = (
ThinQSensorEntityDescription(
Expand Down
43 changes: 40 additions & 3 deletions custom_components/smartthinq_sensors/wideq/ac.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .const import (
FEAT_ENERGY_CURRENT,
FEAT_FILTER_MAIN_LIFE,
FEAT_HOT_WATER_TEMP,
FEAT_HUMIDITY,
FEAT_LIGHTING_DISPLAY,
Expand Down Expand Up @@ -49,6 +50,8 @@
DUCT_ZONE_V1 = "DuctZone"
DUCT_ZONE_V1_TYPE = "DuctZoneType"
STATE_FILTER_V1 = "Filter"
STATE_FILTER_V1_MAX = "FilterMax"
STATE_FILTER_V1_USE = "FilterUse"
STATE_POWER_V1 = "InOutInstantPower"

# AC Section
Expand All @@ -68,6 +71,15 @@
STATE_MODE_JET = ["Jet", "airState.wMode.jet"]
STATE_LIGHTING_DISPLAY = ["DisplayControl", "airState.lightingState.displayControl"]

FILTER_TYPES = [
[
FEAT_FILTER_MAIN_LIFE,
[STATE_FILTER_V1_USE, "airState.filterMngStates.useTime"],
[STATE_FILTER_V1_MAX, "airState.filterMngStates.maxTime"],
None,
],
]

CMD_STATE_OPERATION = [CTRL_BASIC, "Set", STATE_OPERATION]
CMD_STATE_OP_MODE = [CTRL_BASIC, "Set", STATE_OPERATION_MODE]
CMD_STATE_TARGET_TEMP = [CTRL_BASIC, "Set", STATE_TARGET_TEMP]
Expand Down Expand Up @@ -128,6 +140,11 @@
ZONE_ST_CUR = "current"
ZONE_ST_NEW = "new"

FILTER_STATUS_MAP = {
STATE_FILTER_V1_USE: "UseTime",
STATE_FILTER_V1_MAX: "ChangePeriod",
}

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -266,7 +283,7 @@ def __init__(self, client, device, temp_unit=UNIT_TEMP_CELSIUS):
self._current_power = 0
self._current_power_supported = True

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

self._unit_conv = TempUnitConversion()
Expand Down Expand Up @@ -827,13 +844,13 @@ async def get_power(self):
async def get_filter_state(self):
"""Get information about the filter."""
if not self._filter_status_supported:
return None
return {}
try:
return await self._get_config(STATE_FILTER_V1)
except (ValueError, InvalidRequestError):
# Device does not support filter status
self._filter_status_supported = False
return None
return {}

async def set(
self, ctrl_key, command, *, key=None, value=None, data=None, ctrl_path=None
Expand All @@ -858,6 +875,10 @@ async def _get_device_info(self):
# this command is to get power usage on V1 device
if not self.is_air_to_water:
self._current_power = await self.get_power()
filter_status = await self.get_filter_state()
self._filter_status = {
k: filter_status[v] for k, v in FILTER_STATUS_MAP if v in filter_status
}

async def _pre_update_v2(self):
"""Call additional methods before data update for v2 API."""
Expand All @@ -875,6 +896,8 @@ async def poll(self) -> Optional["AirConditionerStatus"]:
return None
if self._should_poll and not self.is_air_to_water:
res[STATE_POWER_V1] = self._current_power
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 @@ -1090,6 +1113,19 @@ def lighting_display(self):
FEAT_LIGHTING_DISPLAY, str(value) == LIGHTING_DISPLAY_ON, False
)

@property
def filters_life(self):
"""Return percentage status for all filters."""
result = {}

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

return result

@property
def water_in_current_temp(self):
"""Return AWHP in water current temperature."""
Expand Down Expand Up @@ -1172,6 +1208,7 @@ def _update_features(self):
_ = [
self.current_temp,
self.energy_current,
self.filters_life,
self.humidity,
self.mode_airclean,
self.mode_jet,
Expand Down
40 changes: 1 addition & 39 deletions custom_components/smartthinq_sensors/wideq/airpurifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,44 +345,6 @@ def pm25(self):
return None
return self._update_feature(FEAT_PM25, value, False)

def _get_filter_life(
self, use_time_status, max_time_status, support_key, filter_types=None
):
"""Get filter status."""
if filter_types:
supported = False
for filter_type in filter_types:
if (
self._device.model_info.enum_value(support_key, filter_type)
is not None
):
supported = True
break
if not supported:
return None

key_max_status = self._get_state_key(max_time_status)
max_time = self.to_int_or_none(self.lookup_enum(key_max_status, True))
if max_time is None:
max_time = self.to_int_or_none(self.lookup_range(key_max_status))
if max_time is None:
return None
if max_time < 10: # because is an enum
return None

use_time = self.to_int_or_none(
self.lookup_range(self._get_state_key(use_time_status))
)
if use_time is None:
return None
if max_time < use_time:
return None

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

@property
def filters_life(self):
"""Return percentage status for all filters."""
Expand All @@ -393,7 +355,7 @@ def filters_life(self):

for filter_def in FILTER_TYPES:
status = self._get_filter_life(
filter_def[1], filter_def[2], support_key, filter_def[3]
filter_def[1], filter_def[2], filter_def[3], support_key
)
if status is not None:
self._update_feature(filter_def[0], status, False)
Expand Down
42 changes: 42 additions & 0 deletions custom_components/smartthinq_sensors/wideq/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,48 @@ def _str_to_num(str_val):
int_val = int(fl_val)
return int_val if int_val == fl_val else fl_val

def _get_filter_life(
self,
use_time_status: str | list,
max_time_status: str | list,
filter_types: list | None = None,
support_key: str | None = None,
):
"""Get filter status filtering by type if required."""
if filter_types and support_key:
supported = False
for filter_type in filter_types:
if (
self._device.model_info.enum_value(support_key, filter_type)
is not None
):
supported = True
break
if not supported:
return None

key_max_status = self._get_state_key(max_time_status)
max_time = self.to_int_or_none(self.lookup_enum(key_max_status, True))
if max_time is None:
max_time = self.to_int_or_none(self.lookup_range(key_max_status))
if max_time is None:
return None
if max_time < 10: # because is an enum
return None

use_time = self.to_int_or_none(
self.lookup_range(self._get_state_key(use_time_status))
)
if use_time is None:
return None
if max_time < use_time:
return None

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

@property
def has_data(self) -> bool:
"""Check if status cointain valid data."""
Expand Down
2 changes: 2 additions & 0 deletions custom_components/smartthinq_sensors/wideq/model_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,8 @@ def value(self, name):
# return EnumValue({"0": "False", "1": "True"})
if d["data_type"] in ("String", "string"):
return None
if d["data_type"] in ("Number", "number"):
return None
raise ValueError(
f"ModelInfoV2AC: unsupported value type {d['data_type']} - value: {d}",
)
Expand Down

0 comments on commit fe9d690

Please sign in to comment.