Skip to content

Commit

Permalink
Update to 1.0.1 for pythinqconnect
Browse files Browse the repository at this point in the history
  • Loading branch information
thinq-connect committed Nov 21, 2024
1 parent 368b78a commit 674e003
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 49 deletions.
1 change: 1 addition & 0 deletions thinqconnect/devices/air_conditioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def __init__(self, profile: dict[str, Any]):
"filterInfo": {
"usedTime": Property.USED_TIME,
"filterLifetime": Property.FILTER_LIFETIME,
"filterRemainPercent": Property.FILTER_REMAIN_PERCENT,
},
},
)
Expand Down
4 changes: 4 additions & 0 deletions thinqconnect/devices/air_purifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(self, profile: dict[str, Any]):
"timer": Resource.TIMER,
"airFlow": Resource.AIR_FLOW,
"airQualitySensor": Resource.AIR_QUALITY_SENSOR,
"filterInfo": Resource.FILTER_INFO,
},
profile_map={
"airPurifierJobMode": {
Expand Down Expand Up @@ -48,6 +49,9 @@ def __init__(self, profile: dict[str, Any]):
"totalPollution": Property.TOTAL_POLLUTION,
"totalPollutionLevel": Property.TOTAL_POLLUTION_LEVEL,
},
"filterInfo": {
"filterRemainPercent": Property.FILTER_REMAIN_PERCENT,
},
},
)

Expand Down
1 change: 1 addition & 0 deletions thinqconnect/devices/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class Property(StrEnum):
EXPRESS_MODE = auto()
FAN_SPEED = auto()
FILTER_LIFETIME = auto()
FILTER_REMAIN_PERCENT = auto()
FLAVOR_INFO = auto()
FRESH_AIR_FILTER = auto()
GROWTH_MODE = auto()
Expand Down
2 changes: 0 additions & 2 deletions thinqconnect/devices/plant_cultivator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ def __init__(self, profile: dict[str, Any], location_name: Location):
"duration": Property.DURATION,
"startHour": Property.START_HOUR,
"startMinute": Property.START_MINUTE,
"endHour": Property.END_HOUR,
"endMinute": Property.END_MINUTE,
},
"temperature": {
"dayTargetTemperature": Property.DAY_TARGET_TEMPERATURE,
Expand Down
4 changes: 3 additions & 1 deletion thinqconnect/integration/homeassistant/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@

_LOGGER = logging.getLogger(__name__)


class ThinQPropertyEx(StrEnum):
"""The extended property definitions for common."""

Expand Down Expand Up @@ -766,7 +767,8 @@ async def _async_create_ha_bridges(
# Get a device profile from the server.
try:
profile = await thinq_api.async_get_device_profile(device_id)
except ThinQAPIException:
except Exception:
_LOGGER.exception("Cannot create ConnectDevice no profile info:%s", device_info)
return []

device_group_id = device_info.get("groupId")
Expand Down
141 changes: 99 additions & 42 deletions thinqconnect/integration/homeassistant/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def __init__(self, holders: Iterable[PropertyHolder | None] | None = None) -> No
self.target_temp: float | None = None
self.target_temp_high: float | None = None
self.target_temp_low: float | None = None
self.hvac_mode: str | None = None
self.hvac_mode: str = "off"
self.fan_mode: str | None = None
self.humidity: int | None = None
self.support_temperature_range: bool = False
Expand Down Expand Up @@ -515,14 +515,10 @@ def update(self) -> None:
minute = None
if not isinstance(second, int):
second = None
if hour is not None and minute is not None and second is not None:
self.value = time(hour, minute, second).strftime("%H:%M:%S")
elif hour is not None and minute is not None:
self.value = time(hour, minute).strftime(self.time_format)
elif minute is not None and second is not None:
self.value = f"{minute:0>2}:{second:0>2}"
else:
if all(v is None for v in [hour, minute, second]):
self.value = None
else:
self.value = time(hour or 0, minute or 0, second or 0)

def create_time_format_if_needed(self, time_format: str | None) -> str:
"""Return the default time format."""
Expand Down Expand Up @@ -559,7 +555,8 @@ async def async_set(self, value: Any) -> None:
await self.setter(self.minute_holder.api, None)
return

if (converted := self.str_to_time(value)) is not None:
converted = value if isinstance(value, time) else self.str_to_time(value)
if converted is not None:
# Set timer.
await self.setter(self.minute_holder.api, converted)
else:
Expand Down Expand Up @@ -603,6 +600,52 @@ class ClimatePropertyStateSpec:
humidity_key: str | None = None


class TemperatureHolderHelper:
"""A helper class that select a temperature property holder that is valid."""

def __init__(
self,
range_holder: dict[str, PropertyHolder | None],
base_holder: dict[str, PropertyHolder | None],
*,
use_range_feature: bool = True,
use_wildcard_holder: bool = False,
) -> None:
"""Set up."""
self.range_holder = range_holder
self.base_holder = base_holder
self.use_range_feature = use_range_feature
self.use_wildcard_holder = use_wildcard_holder
self.current_holder: PropertyHolder | None = None
self.value: float | None = None

def update(self, hvac_mode: str) -> float | None:
"""Update the current holder and then return the value."""
holder: PropertyHolder | None = None
value: Any = None

# In this case, use only one holder(key: "_") regardless of hvac mode.
if self.use_wildcard_holder:
hvac_mode = "_"

# First, try to get value from the range holder.
if (
self.use_range_feature
and (holder := self.range_holder.get(hvac_mode)) is not None
):
value = holder.get_value()

# It still no holder or value, try to get value from the base holder.
if (holder is None or value is None) and (
holder := self.base_holder.get(hvac_mode)
) is not None:
value = holder.get_value()

self.current_holder = holder
self.value = value
return value


class ClimatePropertyState(PropertyState):
"""A class that implements state that has climate properties."""

Expand Down Expand Up @@ -640,34 +683,51 @@ def __init__(
self.fan_mode_holder = fan_mode_holder
self.humidity_holder = humidity_holder
self.support_temperature_range = (
self.target_temp_low_range_holder is not None
self.current_temp_range_holder is not None
and self.target_temp_low_range_holder is not None
and self.target_temp_high_range_holder is not None
)

def get_target_temp_holder(self) -> PropertyHolder | None:
"""Select and return target temperature holder based on hvac mode."""
if self.support_temperature_range and self.hvac_mode == "cool":
return self.target_temp_high_range_holder
if self.support_temperature_range and self.hvac_mode == "heat":
return self.target_temp_low_range_holder
if self.hvac_mode in ("cool", "heat"):
return self.target_temp_holder
# Set up temperature helpers.
self.current_temp_helper = TemperatureHolderHelper(
{"_": current_temp_range_holder},
{"_": current_temp_holder},
use_range_feature=self.support_temperature_range,
use_wildcard_holder=True,
)
self.target_temp_helper = TemperatureHolderHelper(
{
"cool": self.target_temp_high_range_holder,
"heat": self.target_temp_low_range_holder,
},
{
"cool": self.target_temp_holder,
"heat": self.target_temp_holder,
},
use_range_feature=self.support_temperature_range,
)
self.target_temp_low_helper = TemperatureHolderHelper(
{"auto": self.target_temp_low_range_holder},
{"auto": self.target_temp_low_holder},
use_range_feature=self.support_temperature_range,
)
self.target_temp_high_helper = TemperatureHolderHelper(
{"auto": self.target_temp_high_range_holder},
{"auto": self.target_temp_high_holder},
use_range_feature=self.support_temperature_range,
)

return None
def get_target_temp_holder(self) -> PropertyHolder | None:
"""Return the current valid target temperature holder."""
return self.target_temp_helper.current_holder

def get_target_temp_low_holder(self) -> PropertyHolder | None:
"""Select and return target temperature low holder based on hvac mode."""
if self.support_temperature_range and self.hvac_mode == "auto":
return self.target_temp_low_range_holder

return None
"""Return the current valid target temperature low holder."""
return self.target_temp_low_helper.current_holder

def get_target_temp_high_holder(self) -> PropertyHolder | None:
"""Select and return target temperature low holder based on hvac mode."""
if self.support_temperature_range and self.hvac_mode == "auto":
return self.target_temp_high_range_holder

return None
"""Return the current valid target temperature high holder."""
return self.target_temp_high_helper.current_holder

@property
def min(self) -> float | None:
Expand Down Expand Up @@ -724,20 +784,17 @@ def fan_modes(self) -> list[str]:
def update(self) -> None:
"""Update the state."""
self.is_on = self.power_holder.get_value_as_bool()
self.hvac_mode = self.hvac_mode_holder.get_value()
self.current_temp = self.current_temp_holder.get_value()
self.hvac_mode = self.hvac_mode_holder.get_value() or "off"
self.current_temp = self.current_temp_helper.update(self.hvac_mode)

# Set all target temp as 'None' first.
self.target_temp = None
self.target_temp_high = None
self.target_temp_low = None

if (target_temp_holder := self.get_target_temp_holder()) is not None:
self.target_temp = target_temp_holder.get_value()
if (target_temp_low_holder := self.get_target_temp_low_holder()) is not None:
self.target_temp_low = target_temp_low_holder.get_value()
if (target_temp_high_holder := self.get_target_temp_high_holder()) is not None:
self.target_temp_high = target_temp_high_holder.get_value()
if self.is_on:
self.target_temp = self.target_temp_helper.update(self.hvac_mode)
self.target_temp_low = self.target_temp_low_helper.update(self.hvac_mode)
self.target_temp_high = self.target_temp_high_helper.update(self.hvac_mode)
else:
self.target_temp = None
self.target_temp_high = None
self.target_temp_low = None

if self.unit_holder is not None:
self.unit = self.unit_holder.get_value()
Expand Down
13 changes: 9 additions & 4 deletions thinqconnect/thinq_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,11 @@ def __init__(self, code: str, message: str, headers: dict):
self.code = code
self.message = message
self.headers = headers
error_name = error_code_mapping.get(code, "Unknown Error")
super().__init__(f"Error: {error_name} ({self.code}) - {self.message}")
self.error_name = error_code_mapping.get(code, "UNKNOWN_ERROR")
super().__init__(f"Error: {self.error_name} ({self.code}) - {self.message}")

def __str__(self) -> str:
error_name = error_code_mapping.get(self.code, "Unknown Error")
return f"ThinQAPIException: {error_name} ({self.code}) - {self.message}"
return f"ThinQAPIException: {self.error_name} ({self.code}) - {self.message}"


class ThinQApi:
Expand All @@ -114,6 +113,7 @@ def __init__(
access_token: str,
country_code: str,
client_id: str,
mock_response: bool = False,
):
"""Initialize settings."""
self._access_token = access_token
Expand All @@ -123,6 +123,7 @@ def __init__(
self._phase = "OP"
self._country_code = country_code
self._region_code = get_region_from_country(country_code)
self._mock_response = mock_response

def __await__(self):
yield from self.async_init().__await__()
Expand Down Expand Up @@ -297,6 +298,10 @@ async def async_request(self, method: str, endpoint: str, **kwargs: Any) -> dict
url,
kwargs,
)

if self._mock_response:
return {"message": "Mock Response", "body": kwargs.get("json")}

async with await self._async_fetch(method=method, url=url, **kwargs, headers=headers) as response:
payload = await response.json()
if response.ok:
Expand Down

0 comments on commit 674e003

Please sign in to comment.