homebridge-kasa-python 2.7.0-beta.35 → 2.7.0-beta.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/python/kasaApi.py +65 -46
- package/package.json +1 -1
package/dist/python/kasaApi.py
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import asyncio
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
2
5
|
|
|
3
|
-
from kasa import
|
|
6
|
+
from kasa import (
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
Credentials,
|
|
9
|
+
Device,
|
|
10
|
+
DeviceType,
|
|
11
|
+
DeviceConfig,
|
|
12
|
+
Discover,
|
|
13
|
+
Module,
|
|
14
|
+
UnsupportedDeviceError,
|
|
15
|
+
)
|
|
4
16
|
from quart import Quart, jsonify, request
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
6
17
|
|
|
7
18
|
app = Quart(__name__)
|
|
8
19
|
|
|
@@ -26,16 +37,14 @@ def serialize_child(child: Device) -> Dict[str, Any]:
|
|
|
26
37
|
child_info = {
|
|
27
38
|
"alias": child.alias,
|
|
28
39
|
"id": child.device_id.split("_", 1)[1] if "_" in child.device_id else child.device_id,
|
|
29
|
-
"state": child.features["state"].value
|
|
40
|
+
"state": child.features["state"].value,
|
|
30
41
|
}
|
|
31
42
|
light_module = child.modules.get(Module.Light)
|
|
32
43
|
if light_module:
|
|
33
44
|
child_info.update(get_light_info(child))
|
|
34
45
|
fan_module = child.modules.get(Module.Fan)
|
|
35
46
|
if fan_module:
|
|
36
|
-
child_info.update({
|
|
37
|
-
"fan_speed_level": fan_module.fan_speed_level,
|
|
38
|
-
})
|
|
47
|
+
child_info.update({"fan_speed_level": fan_module.fan_speed_level})
|
|
39
48
|
return child_info
|
|
40
49
|
|
|
41
50
|
def get_light_info(device: Device) -> Dict[str, Any]:
|
|
@@ -73,39 +82,36 @@ def custom_serializer(device: Device) -> Dict[str, Any]:
|
|
|
73
82
|
if child_num > 0:
|
|
74
83
|
sys_info["children"] = [serialize_child(child) for child in device.children]
|
|
75
84
|
else:
|
|
76
|
-
sys_info.update({
|
|
77
|
-
"state": device.features["state"].value
|
|
78
|
-
})
|
|
85
|
+
sys_info.update({"state": device.features["state"].value})
|
|
79
86
|
if light_module:
|
|
80
87
|
sys_info.update(get_light_info(device))
|
|
81
88
|
if fan_module:
|
|
82
|
-
sys_info.update({
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
sys_info.update({"fan_speed_level": fan_module.fan_speed_level})
|
|
90
|
+
|
|
91
|
+
feature_info = {
|
|
92
|
+
"brightness": False,
|
|
93
|
+
"color_temp": False,
|
|
94
|
+
"hsv": False,
|
|
95
|
+
"fan": False,
|
|
96
|
+
}
|
|
85
97
|
|
|
86
98
|
if light_module:
|
|
87
|
-
feature_info
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
elif fan_module:
|
|
93
|
-
feature_info = {
|
|
94
|
-
"fan": True if fan_module else False
|
|
95
|
-
}
|
|
96
|
-
else:
|
|
97
|
-
feature_info = {}
|
|
99
|
+
feature_info.update({
|
|
100
|
+
"brightness": light_module.has_feature("brightness"),
|
|
101
|
+
"color_temp": light_module.has_feature("color_temp"),
|
|
102
|
+
"hsv": light_module.has_feature("hsv"),
|
|
103
|
+
})
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
}
|
|
105
|
+
if fan_module:
|
|
106
|
+
feature_info.update({"fan": True})
|
|
107
|
+
|
|
108
|
+
return {"sys_info": sys_info, "feature_info": feature_info}
|
|
103
109
|
|
|
104
110
|
async def discover_devices(
|
|
105
111
|
username: Optional[str] = None,
|
|
106
112
|
password: Optional[str] = None,
|
|
107
113
|
additional_broadcasts: Optional[List[str]] = None,
|
|
108
|
-
manual_devices: Optional[List[str]] = None
|
|
114
|
+
manual_devices: Optional[List[str]] = None,
|
|
109
115
|
) -> Dict[str, Any]:
|
|
110
116
|
devices = {}
|
|
111
117
|
devices_to_remove = []
|
|
@@ -120,11 +126,8 @@ async def discover_devices(
|
|
|
120
126
|
print(f"Discovered device: {device.alias}")
|
|
121
127
|
try:
|
|
122
128
|
await device.update()
|
|
123
|
-
except UnsupportedDeviceError:
|
|
124
|
-
print(f"
|
|
125
|
-
devices_to_remove.append(device.host)
|
|
126
|
-
except AuthenticationError:
|
|
127
|
-
print(f"Authentication error: {device.host}")
|
|
129
|
+
except (UnsupportedDeviceError, AuthenticationError) as e:
|
|
130
|
+
print(f"{e.__class__.__name__}: {device.host}", file=sys.stderr)
|
|
128
131
|
devices_to_remove.append(device.host)
|
|
129
132
|
except Exception as e:
|
|
130
133
|
print(f"Error during discovery: {e}", file=sys.stderr)
|
|
@@ -134,16 +137,17 @@ async def discover_devices(
|
|
|
134
137
|
print(f"Discovering on broadcast: {broadcast}")
|
|
135
138
|
try:
|
|
136
139
|
discovered = await Discover.discover(
|
|
137
|
-
target=broadcast,
|
|
138
|
-
credentials=credentials,
|
|
139
|
-
on_discovered=on_discovered
|
|
140
|
+
target=broadcast, credentials=credentials, on_discovered=on_discovered
|
|
140
141
|
)
|
|
142
|
+
for host in list(discovered.keys()):
|
|
143
|
+
if host in device_config_cache:
|
|
144
|
+
del discovered[host]
|
|
141
145
|
devices.update(discovered)
|
|
142
146
|
except Exception as e:
|
|
143
147
|
print(f"Error during broadcast discovery: {e}", file=sys.stderr)
|
|
144
148
|
|
|
145
149
|
async def discover_manual_device(host: str):
|
|
146
|
-
if host in devices:
|
|
150
|
+
if host in devices or host in device_config_cache:
|
|
147
151
|
return
|
|
148
152
|
print(f"Discovering manual device: {host}")
|
|
149
153
|
try:
|
|
@@ -157,6 +161,8 @@ async def discover_devices(
|
|
|
157
161
|
manual_discover_tasks = [discover_manual_device(host) for host in (manual_devices or [])]
|
|
158
162
|
await asyncio.gather(*discover_tasks, *manual_discover_tasks)
|
|
159
163
|
|
|
164
|
+
all_device_info = {}
|
|
165
|
+
update_tasks = []
|
|
160
166
|
host: str
|
|
161
167
|
device: Device
|
|
162
168
|
|
|
@@ -166,15 +172,22 @@ async def discover_devices(
|
|
|
166
172
|
print(f"Removing device: {device.alias}")
|
|
167
173
|
await device.disconnect()
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
for host, device_config_dict in device_config_cache.items():
|
|
176
|
+
device = devices.get(host)
|
|
177
|
+
if device:
|
|
178
|
+
print(f"Skipping device {device.alias} due to existing connection")
|
|
179
|
+
continue
|
|
180
|
+
device_config = DeviceConfig.from_dict(device_config_dict)
|
|
181
|
+
device = await Device.connect(config=device_config)
|
|
182
|
+
devices[host] = device
|
|
171
183
|
|
|
172
184
|
for host, device in devices.items():
|
|
173
185
|
try:
|
|
174
186
|
await device.update()
|
|
175
187
|
except Exception as e:
|
|
176
188
|
print(f"Error checking device: {e}", file=sys.stderr)
|
|
177
|
-
|
|
189
|
+
if device:
|
|
190
|
+
await device.disconnect()
|
|
178
191
|
continue
|
|
179
192
|
|
|
180
193
|
try:
|
|
@@ -236,8 +249,6 @@ async def close_all_connections():
|
|
|
236
249
|
disconnect_tasks = [device.disconnect() for device in device_cache.values()]
|
|
237
250
|
await asyncio.gather(*disconnect_tasks, return_exceptions=True)
|
|
238
251
|
device_cache.clear()
|
|
239
|
-
device_lock_cache.clear()
|
|
240
|
-
device_config_cache.clear()
|
|
241
252
|
|
|
242
253
|
async def create_device_info(host: str, device: Device):
|
|
243
254
|
print("Creating device info for host: ", host)
|
|
@@ -301,7 +312,7 @@ async def control_device(
|
|
|
301
312
|
feature: str,
|
|
302
313
|
action: str,
|
|
303
314
|
value: Any,
|
|
304
|
-
child_num: Optional[int] = None
|
|
315
|
+
child_num: Optional[int] = None,
|
|
305
316
|
) -> Dict[str, Any]:
|
|
306
317
|
print(f"Controlling device at host: {host}")
|
|
307
318
|
try:
|
|
@@ -327,7 +338,13 @@ async def control_device(
|
|
|
327
338
|
device_cache.pop(host, None)
|
|
328
339
|
return {"error": str(e)}
|
|
329
340
|
|
|
330
|
-
async def perform_device_action(
|
|
341
|
+
async def perform_device_action(
|
|
342
|
+
device: Device,
|
|
343
|
+
feature: str,
|
|
344
|
+
action: str,
|
|
345
|
+
value: Any,
|
|
346
|
+
child_num: Optional[int] = None,
|
|
347
|
+
) -> Dict[str, Any]:
|
|
331
348
|
target = device.children[child_num] if child_num is not None else device
|
|
332
349
|
light = target.modules.get(Module.Light)
|
|
333
350
|
fan = target.modules.get(Module.Fan)
|
|
@@ -432,4 +449,6 @@ async def health_check():
|
|
|
432
449
|
@app.after_serving
|
|
433
450
|
async def cleanup():
|
|
434
451
|
print("Cleaning up and disconnecting all devices.")
|
|
435
|
-
await close_all_connections()
|
|
452
|
+
await close_all_connections()
|
|
453
|
+
device_lock_cache.clear()
|
|
454
|
+
device_config_cache.clear()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Homebridge Kasa Python",
|
|
3
3
|
"name": "homebridge-kasa-python",
|
|
4
|
-
"version": "2.7.0-beta.
|
|
4
|
+
"version": "2.7.0-beta.36",
|
|
5
5
|
"description": "Plugin that uses Python-Kasa API to communicate with Kasa Devices.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|