Skip to content

Commit aea0575

Browse files
gh #192 updated the tapo multi power switch changes
1 parent f51c7b3 commit aea0575

File tree

2 files changed

+59
-40
lines changed

2 files changed

+59
-40
lines changed

framework/core/powerModules/tapoControl.py

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import json
4949
import re
5050
import subprocess
51-
import time
5251

5352
from framework.core.logModule import logModule
5453
from framework.core.powerModules.abstractPowerModule import PowerModuleInterface
@@ -75,7 +74,7 @@ def __init__( self, log:logModule, ip:str, outlet:str = None, **kwargs ):
7574
self.ip = ip
7675
self._username = kwargs.get("username", None)
7776
self._password = kwargs.get("password", None)
78-
if outlet:
77+
if outlet is not None:
7978
self._outlet=str(outlet)
8079
self._device_type = None
8180
self._encryption_type = None
@@ -111,7 +110,7 @@ def _performCommand(self, command, json = False, append_args:list = []):
111110
command_list.append("--encrypt-type")
112111
command_list.append(self._encryption_type)
113112
else:
114-
if self._outlet:
113+
if self._outlet is not None:
115114
command_list.append("--type")
116115
command_list.append("strip")
117116
else:
@@ -133,16 +132,16 @@ def powerOff(self):
133132
bool: True if the operation is successful, False otherwise.
134133
"""
135134
self._get_state()
136-
if self.is_off:
135+
if not self._is_on:
137136
return True
138-
if self._outlet:
137+
if self._outlet is not None:
139138
self._performCommand("off", append_args=["--index", str(self._outlet)])
140139
else:
141140
self._performCommand("off")
142141
self._get_state()
143-
if self.is_off == False:
142+
if self._is_on:
144143
self._log.error(" Power Off Failed")
145-
return self.is_off
144+
return not self._is_on
146145

147146
def powerOn(self):
148147
"""
@@ -152,21 +151,33 @@ def powerOn(self):
152151
bool: True if the operation is successful, False otherwise.
153152
"""
154153
self._get_state()
155-
if self.is_on:
154+
if self._is_on:
156155
return True
157-
if self._outlet:
156+
if self._outlet is not None:
158157
self._performCommand("on", append_args=["--index", str(self._outlet)])
159-
self._performCommand("on")
158+
else:
159+
self._performCommand("on")
160160
self._get_state()
161-
if self.is_on == False:
161+
if self._is_on == False:
162162
self._log.error(" Power On Failed")
163-
return self.is_on
163+
return self._is_on
164164

165165
def _get_state(self):
166166
"""Get the state of the device.
167167
"""
168168
result = self._performCommand("state")
169-
if self._outlet:
169+
if self._outlet is not None:
170+
# == Children ==
171+
#
172+
# == Smart Plug 1 (P304M) ==
173+
# == Primary features ==
174+
# State(state): True
175+
if result.find('Children') > 1: # smart extension plug with multiple outlets
176+
all_states = re.findall(r"^\s*State\s*\(state\)\s*:\s*(True|False)\s*$",
177+
result, flags=re.IGNORECASE | re.MULTILINE)
178+
self._is_on = all_states[int(self._outlet)] == 'True'
179+
self._log.debug(f"Slot state: {'ON' if self._is_on else 'OFF'}")
180+
return
170181
# We have a strip look at the status of the strip, and check the index and the device state
171182
#Device state: ON
172183
#== Plugs ==
@@ -233,6 +244,12 @@ def _discover_device(self):
233244
self._device_type = info.get("mic_type", "UNKNOWN")
234245
else:
235246
self._device_type = "UNKNOWN"
247+
elif result.get("get_child_device_list", {}).get('child_device_list', []):
248+
child_devices = result.get("get_child_device_list", {}).get('child_device_list', [])
249+
if len(child_devices) >= int(self._outlet) + 1:
250+
self._device_type = child_devices[int(self._outlet)].get("type", "UNKNOWN")
251+
elif result.get('get_device_info'):
252+
self._device_type = result.get("get_device_info").get("type", "UNKNOWN")
236253
else:
237254
self._device_type = "UNKNOWN"
238255
self._encryption_type = self._get_encryption_type()
@@ -249,28 +266,30 @@ def _get_encryption_type(self):
249266
return None
250267

251268
def getPowerLevel(self):
252-
if self._outlet:
253-
# TODO: implement this for a powerstrip
254-
# result = self._performCommand("emeter",
255-
# json=True,
256-
# append_args=["--index", str(self._outlet)])
257-
raise RuntimeError("Power monitoring is not yet supported for Tapo strips")
269+
if self._outlet is not None:
270+
args = [
271+
"--module", 'energy', 'get_current_power',
272+
"--index", self._outlet
273+
]
258274
else:
259-
result = self._performCommand("emeter", json=True)
260-
261-
if not result:
262-
raise ValueError("Received empty response from Tapo device for power monitoring")
263-
264-
try:
265-
result = json.loads(result)
266-
except json.JSONDecodeError as e:
267-
raise ValueError(f"Failed to parse JSON from Tapo device response: {e}")
268-
269-
millewatt = result.get('power_mw')
270-
if millewatt:
271-
try:
272-
power = int(millewatt) / 1000
273-
return power
274-
except:
275-
raise ValueError(f"Invalid value for power_mw: {millewatt}")
276-
raise KeyError("The dictionary returned by the Tapo device does not contain a valid 'power_mw' value.")
275+
args = [
276+
"--module", 'energy', 'get_current_power'
277+
]
278+
result = self._performCommand("command",
279+
json=True,
280+
append_args=args
281+
)
282+
283+
if not result:
284+
raise ValueError("Received empty response from Tapo device for power monitoring")
285+
286+
try:
287+
result = json.loads(result)
288+
except json.JSONDecodeError as e:
289+
raise ValueError(f"Failed to parse JSON from Tapo device response: {e}")
290+
291+
watt = result.get("get_current_power", {}).get('current_power', None)
292+
if watt is not None:
293+
return watt
294+
295+
raise KeyError("The dictionary returned by the Tapo device does not contain a valid 'power_mw' value.")

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jmespath==1.0.1
3434
marshmallow==3.21.1
3535
multidict==6.0.5
3636
netifaces==0.11.0
37-
numpy==1.26.4
37+
numpy>=2.0.0,<2.3.0
3838
opencv-python==4.9.0.80
3939
outcome==1.3.0.post0
4040
packaging==24.0
@@ -49,7 +49,7 @@ pyserial==3.5
4949
PySocks==1.7.1
5050
pytesseract==0.3.10
5151
python-dateutil==2.9.0.post0
52-
python-kasa==0.6.2.1
52+
python-kasa==0.7.7
5353
PyYAML==6.0.1
5454
requests==2.31.0
5555
requests-toolbelt==1.0.0
@@ -61,7 +61,7 @@ sortedcontainers==2.4.0
6161
soupsieve==2.5
6262
trio==0.25.0
6363
trio-websocket==0.11.1
64-
typing_extensions==4.10.0
64+
typing_extensions==4.12.2
6565
urllib3==1.26.18
6666
wcwidth==0.2.14
6767
wrapt==1.16.0

0 commit comments

Comments
 (0)