| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,6 +23,7 @@ | |||
| 23 | 23 | from kasa.smartlightstrip import SmartLightStrip | |
| 24 | 24 | from kasa.smartplug import SmartPlug | |
| 25 | 25 | from kasa.smartstrip import SmartStrip | |
| 26 | + from kasa.tapoplug import TapoPlug | ||
| 26 | 27 | ||
| 27 | 28 | __version__ = version("python-kasa") | |
| 28 | 29 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,9 +36,11 @@ def wrapper(message=None, *args, **kwargs): | |||
| 36 | 36 | SmartLightStrip, | |
| 37 | 37 | SmartPlug, | |
| 38 | 38 | SmartStrip, | |
| 39 | + TapoPlug, | ||
| 39 | 40 | ) | |
| 40 | 41 | ||
| 41 | 42 | TYPE_TO_CLASS = { | |
| 43 | + "tapoplug": TapoPlug, | ||
| 42 | 44 | "plug": SmartPlug, | |
| 43 | 45 | "bulb": SmartBulb, | |
| 44 | 46 | "dimmer": SmartDimmer, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -200,6 +200,17 @@ async def discover_single(host: str, *, port: Optional[int] = None) -> SmartDevi | |||
| 200 | 200 | :rtype: SmartDevice | |
| 201 | 201 | :return: Object for querying/controlling found device. | |
| 202 | 202 | """ | |
| 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 | ||
| 203 | 214 | protocol = TPLinkSmartHomeProtocol(host, port=port) | |
| 204 | 215 | ||
| 205 | 216 | info = await protocol.query(Discover.DISCOVERY_QUERY) | |
| Original file line number | Diff line number | Diff 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() | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments