FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add preliminary support for tapo plugs · python-kasa/python-kasa@b47e28d · GitHub

Repository navigation

Commit b47e28d

Browse files
committed
Add preliminary support for tapo plugs
1 parent 24da24e commit b47e28d

4 files changed

Lines changed: 188 additions & 0 deletions

File tree

‎kasa/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from kasa.smartlightstrip import SmartLightStrip
2424
from kasa.smartplug import SmartPlug
2525
from kasa.smartstrip import SmartStrip
26+
from kasa.tapoplug import TapoPlug
2627

2728
__version__ = version("python-kasa")
2829

‎kasa/cli.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ def wrapper(message=None, *args, **kwargs):
3636
SmartLightStrip,
3737
SmartPlug,
3838
SmartStrip,
39+
TapoPlug,
3940
)
4041

4142
TYPE_TO_CLASS = {
43+
"tapoplug": TapoPlug,
4244
"plug": SmartPlug,
4345
"bulb": SmartBulb,
4446
"dimmer": SmartDimmer,

‎kasa/discover.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,17 @@ async def discover_single(host: str, *, port: Optional[int] = None) -> SmartDevi
200200
:rtype: SmartDevice
201201
:return: Object for querying/controlling found device.
202202
"""
203+
204+
# TODO: until discover_single is modified, just hardcode reading the tapo addresses from envvars
205+
import os
206+
207+
tapo_addrs = os.environ.get("KASA_TAPO_ADDRS").split(";")
208+
if host in tapo_addrs:
209+
from kasa import TapoPlug
210+
211+
dev = TapoPlug(host)
212+
await dev.update()
213+
return dev
203214
protocol = TPLinkSmartHomeProtocol(host, port=port)
204215

205216
info = await protocol.query(Discover.DISCOVERY_QUERY)

‎kasa/tapoplug.py‎

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import logging
2+
import os
3+
from datetime import datetime, timedelta
4+
from typing import Any, Dict, Optional, Set
5+
6+
from plugp100.api.plug_device import PlugDevice
7+
from plugp100.api.tapo_client import TapoClient
8+
from plugp100.responses.device_state import DeviceInfo, PlugDeviceState
9+
from plugp100.responses.device_usage_info import DeviceUsageInfo
10+
from plugp100.responses.energy_info import EnergyInfo
11+
from plugp100.responses.power_info import PowerInfo
12+
13+
from . import EmeterStatus
14+
from .smartdevice import DeviceType, SmartDevice
15+
from .smartplug import SmartPlug
16+
17+
_LOGGER = logging.getLogger(__name__)
18+
19+
20+
# TODO: there should be a baseclass for plugs that does not initialize modules etc. that are related only to some implementations
21+
class TapoPlug(SmartPlug):
22+
def __init__(self, host: str, *, port: Optional[int] = None) -> None:
23+
# TODO: we are calling smartdevice here to avoid smartplug internal handling
24+
SmartDevice.__init__(self, host, port=port)
25+
# TODO: this is needed as we don't call smartplug ctor
26+
self._device_type = DeviceType.Plug
27+
env = os.environ
28+
self._tapo_client = TapoClient(
29+
env["KASA_TAPO_EMAIL"], env["KASA_TAPO_PASSWORD"]
30+
)
31+
self._tapo_device = PlugDevice(self._tapo_client, self.host)
32+
self._state = None
33+
34+
async def update(self, update_children: bool = True):
35+
if self._state is None:
36+
await self._tapo_device.login()
37+
38+
# TODO: check for success as
39+
self._state = (await self._tapo_device.get_state()).value
40+
self._info: DeviceInfo = self._state.info
41+
42+
self._usage: DeviceUsageInfo = (
43+
await self._tapo_device.get_device_usage()
44+
).value
45+
self._energy: EnergyInfo = (await self._tapo_device.get_energy_usage()).value
46+
self._emeter: PowerInfo = (await self._tapo_device.get_current_power()).value
47+
48+
self._last_update = self._data = {
49+
"state": self._state,
50+
"usage": self._usage,
51+
"emeter": self._emeter,
52+
"energy": self._energy,
53+
}
54+
55+
_LOGGER.debug("Got an update: %s", self._data)
56+
57+
@property
58+
def sys_info(self) -> Dict[str, Any]:
59+
return self._state
60+
61+
@property
62+
def model(self) -> str:
63+
return self._info.model
64+
65+
@property
66+
def alias(self) -> str:
67+
return self._info.nickname
68+
69+
@property
70+
def time(self) -> datetime:
71+
return None # TODO: make return value optional
72+
73+
@property
74+
def timezone(self) -> Dict:
75+
return {"timezone": self._info.timezone, "timediff": self._info.time_difference}
76+
77+
def has_emeter(self) -> bool:
78+
return True
79+
80+
@property
81+
def emeter_realtime(self) -> EmeterStatus:
82+
return EmeterStatus({"power_mw": self._energy.current_power})
83+
84+
@property
85+
def emeter_today(self) -> Optional[float]:
86+
return None
87+
88+
@property
89+
def emeter_this_month(self) -> Optional[float]:
90+
return None
91+
92+
@property
93+
def hw_info(self) -> Dict:
94+
# TODO: check that the keys match to kasa-infos
95+
return {
96+
"sw_ver": self._info.firmware_version,
97+
"hw_ver": self._info.hardware_version,
98+
"mac": self._info.mac,
99+
"type": self._info.type,
100+
"hwId": self._info.device_id,
101+
"dev_name": self._info.nickname,
102+
"oemId": self._info.oem_id,
103+
}
104+
105+
@property
106+
def location(self) -> Dict:
107+
loc = {
108+
"latitude": self._info.latitude / 10_000,
109+
"longitude": self._info.longitude / 10_000,
110+
}
111+
return loc
112+
113+
@property
114+
def rssi(self) -> Optional[int]:
115+
return self._info.rssi
116+
117+
@property
118+
def mac(self) -> str:
119+
return self._info.mac.replace("-", ":")
120+
121+
@property
122+
def device_id(self) -> str:
123+
return self._info.device_id
124+
125+
@property
126+
def internal_state(self) -> Any:
127+
return self._data
128+
129+
@property
130+
def is_on(self) -> bool:
131+
return self._state.device_on
132+
133+
async def turn_on(self, **kwargs):
134+
return await self._tapo_device.on()
135+
136+
async def turn_off(self, **kwargs):
137+
return await self._tapo_device.off()
138+
139+
async def _query_helper(
140+
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
141+
) -> Any:
142+
res = await self._tapo_device.raw_command(cmd, arg)
143+
if res.is_left():
144+
raise res.error
145+
return res.value
146+
147+
@property
148+
def led(self) -> bool:
149+
return None
150+
151+
async def set_led(self, state: bool):
152+
return await super().set_led(state)
153+
154+
@property
155+
def on_since(self) -> Optional[datetime]:
156+
on_time = self._info.on_time
157+
return datetime.now().replace(microsecond=0) - timedelta(seconds=on_time)
158+
159+
@property
160+
def state_information(self) -> Dict[str, Any]:
161+
return {
162+
"is_hw_v2": self._info.is_hardware_v2,
163+
"overheated": self._info.overheated,
164+
"signal_level": self._info.signal_level,
165+
"auto_off": self._info.auto_off,
166+
"auto_off_remaining": self._info.auto_off_time_remaining,
167+
"On since": self.on_since,
168+
"SSID": self._info.ssid,
169+
}
170+
171+
@property
172+
def features(self) -> Set[str]:
173+
# TODO:
174+
return set()

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL