-
-
Notifications
You must be signed in to change notification settings - Fork 288
Add support for Tapo H110 IR Air Conditioners #1716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3f82910
c7a3405
3b15fdd
4afa9eb
65fe12b
6a1c037
802b8a1
be03dff
4582ec1
5baddb0
43b779d
835c411
a2f0df6
d6eeba9
f6837d7
86a4ed3
646ca65
8d19e6a
be146b2
f4d81f2
99e0288
78016e4
9005319
21cda17
6bdad67
ade20d7
f5d0da5
79bcf3d
022775a
5f9fc5e
7e5fae8
3a8eddd
230b822
d15cbf9
c297f54
9e1da3d
a876e4f
f331495
31e337f
0f5a468
c1f92ea
3a72145
547ead9
d51788b
7db0f2c
cf5abbd
6c27973
5ea34bb
faad4c7
f90f4a0
e8681b5
3b1b699
67cc57f
f55868c
ed8e57f
b8d0387
34c89f3
7c6bc64
a88995e
a170141
60c7a00
987097c
18736c2
84e2aad
894db97
0e9f22e
341103c
2640c28
1a5970d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ __pycache__/ | |
|
|
||
| # Coverage | ||
| .coverage | ||
| coverage.xml | ||
|
|
||
| # Tox | ||
| .tox | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,6 +89,15 @@ async def _initialize_children(self) -> None: | |
| async def _try_create_child( | ||
| self, info: dict, child_components: dict | ||
| ) -> SmartDevice | None: | ||
| if info.get("category") == "ir.remote" and info.get("model") == "AC": | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this is a child device, please move the initialization to happen inside |
||
| from .smartirac import SmartIrAC | ||
|
|
||
| return await SmartIrAC.create( | ||
| parent=self, | ||
| child_info=info, | ||
| child_components_raw=child_components, | ||
| ) | ||
|
|
||
| from .smartchilddevice import SmartChildDevice | ||
|
|
||
| return await SmartChildDevice.create( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| """Smart IR AC child device implementation.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| from ..device_type import DeviceType | ||
| from .smartchilddevice import SmartChildDevice | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class SmartIrAC(SmartChildDevice): | ||
| """Presentation of a child IR Air Conditioner device.""" | ||
|
|
||
| def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
| self._ac_state: dict[str, int] = {} | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| @property | ||
| def device_type(self) -> DeviceType: | ||
| """Return the device type.""" | ||
| try: | ||
| return DeviceType.Climate | ||
| except AttributeError: | ||
| return DeviceType.Thermostat | ||
|
Comment on lines
+22
to
+27
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When can this happen? |
||
|
|
||
| def _update_internal_state(self, info: dict[str, Any]) -> None: | ||
| """Update the internal info state.""" | ||
| super()._update_internal_state(info) | ||
|
|
||
| ac_status = info.get("ac_status") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to have a less fragile parsing of the status. I cannot off the bat recommend how it should look like (could you create a PR to add a fixture?), but we want to make it more robust to avoid breaking if the device responses change suddenly. If this is a regular smart device, maybe it is only necessary to implement a module to handle this case? Let me know what do you think & if you need any help with deciphering how to bolt modules in! |
||
| if ac_status: | ||
| parts = ac_status.split("_") | ||
| self._ac_state = {} | ||
| for part in parts: | ||
| if len(part) >= 2: | ||
| key = part[0] | ||
| try: | ||
| value = int(part[1:]) | ||
| self._ac_state[key] = value | ||
| except ValueError: | ||
| pass | ||
|
|
||
| @property | ||
| def is_on(self) -> bool: | ||
| """Return true if the device is on.""" | ||
| return bool(self._ac_state.get("P", 0)) | ||
|
|
||
| @property | ||
| def target_temperature(self) -> int | None: | ||
| """Return the target temperature.""" | ||
| return self._ac_state.get("T") | ||
|
|
||
| @property | ||
| def hvac_mode(self) -> int | None: | ||
| """Return the hvac mode.""" | ||
| return self._ac_state.get("M") | ||
|
|
||
| @property | ||
| def fan_mode(self) -> int | None: | ||
| """Return the fan mode.""" | ||
| return self._ac_state.get("S") | ||
|
|
||
| @property | ||
| def swing_mode(self) -> int | None: | ||
| """Return the swing mode.""" | ||
| return self._ac_state.get("D") | ||
|
|
||
| async def _send_ir_cmd(self, **kwargs) -> dict: | ||
| """Send IR command to the AC.""" | ||
| payload = { | ||
| "power": bool(self._ac_state.get("P", 0)), | ||
| "on": bool(self._ac_state.get("P", 0)), | ||
| "mode": self._ac_state.get("M", 1), | ||
| "temp": self._ac_state.get("T", 24), | ||
| "wind_speed": self._ac_state.get("S", 1), | ||
| "wind_direct": self._ac_state.get("D", 0), | ||
| } | ||
|
|
||
| for key, value in kwargs.items(): | ||
| payload[key] = value | ||
| if key == "power": | ||
| payload["on"] = value | ||
|
|
||
| request = { | ||
| "multipleRequest": { | ||
| "requests": [{"method": "sendIrCmdByStatus", "params": payload}] | ||
| } | ||
| } | ||
|
|
||
| if "power" in payload: | ||
| self._ac_state["P"] = 1 if payload["power"] else 0 | ||
| if "mode" in payload: | ||
| self._ac_state["M"] = payload["mode"] | ||
| if "temp" in payload: | ||
| self._ac_state["T"] = payload["temp"] | ||
| if "wind_speed" in payload: | ||
| self._ac_state["S"] = payload["wind_speed"] | ||
| if "wind_direct" in payload: | ||
| self._ac_state["D"] = payload["wind_direct"] | ||
|
|
||
| return await self.protocol.query(request) | ||
|
|
||
| async def set_target_temperature(self, temp: int) -> dict: | ||
| """Set the target temperature.""" | ||
| return await self._send_ir_cmd(temp=temp) | ||
|
|
||
| async def set_hvac_mode(self, mode: int) -> dict: | ||
| """Set the hvac mode.""" | ||
| return await self._send_ir_cmd(mode=mode) | ||
|
|
||
| async def set_fan_mode(self, speed: int) -> dict: | ||
| """Set the fan mode.""" | ||
| return await self._send_ir_cmd(wind_speed=speed) | ||
|
|
||
| async def set_swing_mode(self, swing: int) -> dict: | ||
| """Set the swing mode.""" | ||
| return await self._send_ir_cmd(wind_direct=swing) | ||
|
|
||
| async def turn_on(self, **kwargs: Any) -> dict: | ||
| """Turn on the AC.""" | ||
| return await self._send_ir_cmd(power=True) | ||
|
|
||
| async def turn_off(self, **kwargs: Any) -> dict: | ||
| """Turn off the AC.""" | ||
| return await self._send_ir_cmd(power=False) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is a child device, there should be no need to export this.