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.
@@ -1,8 +1,19 @@
1
- import asyncio, os, sys
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ from typing import Any, Dict, List, Optional
2
5
 
3
- from kasa import AuthenticationError, Credentials, Device, DeviceType, DeviceConfig, Discover, Module, UnsupportedDeviceError
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
- "fan_speed_level": fan_module.fan_speed_level,
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
- "brightness": light_module.has_feature("brightness"),
89
- "color_temp": light_module.has_feature("color_temp"),
90
- "hsv": light_module.has_feature("hsv")
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
- return {
100
- "sys_info": sys_info,
101
- "feature_info": feature_info
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"Unsupported device: {device.host}")
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
- all_device_info = {}
170
- update_tasks = []
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
- await device.disconnect()
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(device: Device, feature: str, action: str, value: Any, child_num: Optional[int] = None) -> Dict[str, Any]:
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.35",
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",