homebridge-kasa-python 2.7.2-beta.2 → 2.7.2-beta.4

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/README.md CHANGED
@@ -153,6 +153,7 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
153
153
  - **KS225**
154
154
  - Hardware: 1.0 (US) / Firmware: 1.0.2[*]
155
155
  - Hardware: 1.0 (US) / Firmware: 1.1.0[*]
156
+ - Hardware: 1.0 (US) / Firmware: 1.1.1[*]
156
157
  - **KS230**
157
158
  - Hardware: 1.0 (US) / Firmware: 1.0.14
158
159
  - Hardware: 2.0 (US) / Firmware: 1.0.11
@@ -183,6 +184,8 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
183
184
  - **KL60**
184
185
  - Hardware: 1.0 (UN) / Firmware: 1.1.4
185
186
  - Hardware: 1.0 (US) / Firmware: 1.1.13
187
+ - **LB100**
188
+ - Hardware: 1.0 (US) / Firmware: 1.8.11
186
189
  - **LB110**
187
190
  - Hardware: 1.0 (US) / Firmware: 1.8.11
188
191
 
@@ -226,6 +229,7 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
226
229
  - Hardware: 1.0.0 (US) / Firmware: 1.3.7
227
230
  - Hardware: 1.0.0 (US) / Firmware: 1.4.0
228
231
  - **P110**
232
+ - Hardware: 1.0 (AU) / Firmware: 1.3.1
229
233
  - Hardware: 1.0 (EU) / Firmware: 1.0.7
230
234
  - Hardware: 1.0 (EU) / Firmware: 1.2.3
231
235
  - Hardware: 1.0 (UK) / Firmware: 1.3.0
@@ -278,10 +282,13 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
278
282
  - **L510E**
279
283
  - Hardware: 3.0 (US) / Firmware: 1.0.5
280
284
  - Hardware: 3.0 (US) / Firmware: 1.1.2
285
+ - **L530B**
286
+ - Hardware: 3.0 (EU) / Firmware: 1.1.9
281
287
  - **L530E**
282
288
  - Hardware: 3.0 (EU) / Firmware: 1.0.6
283
289
  - Hardware: 3.0 (EU) / Firmware: 1.1.0
284
290
  - Hardware: 3.0 (EU) / Firmware: 1.1.6
291
+ - Hardware: 2.0 (TW) / Firmware: 1.1.1
285
292
  - Hardware: 2.0 (US) / Firmware: 1.1.0
286
293
  - **L630**
287
294
  - Hardware: 1.0 (EU) / Firmware: 1.1.2
@@ -300,12 +307,15 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
300
307
  - Hardware: 1.0 (US) / Firmware: 1.1.0
301
308
  - Hardware: 1.0 (US) / Firmware: 1.1.3
302
309
  - **L930-5**
310
+ - Hardware: 1.0 (EU) / Firmware: 1.2.5
303
311
  - Hardware: 1.0 (US) / Firmware: 1.1.2
304
312
 
305
313
  ### Cameras
306
314
 
307
315
  - **C100**
308
316
  - Hardware: 4.0 / Firmware: 1.3.14
317
+ - **C110**
318
+ - Hardware: 2.0 (EU) / Firmware: 1.4.3
309
319
  - **C210**
310
320
  - Hardware: 2.0 / Firmware: 1.3.11
311
321
  - Hardware: 2.0 (EU) / Firmware: 1.4.2
@@ -344,6 +354,7 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
344
354
  ### Hubs
345
355
 
346
356
  - **H100**
357
+ - Hardware: 1.0 (AU) / Firmware: 1.5.23
347
358
  - Hardware: 1.0 (EU) / Firmware: 1.2.3
348
359
  - Hardware: 1.0 (EU) / Firmware: 1.5.10
349
360
  - Hardware: 1.0 (EU) / Firmware: 1.5.5
@@ -362,6 +373,7 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
362
373
  - Hardware: 1.0 (EU) / Firmware: 1.12.0
363
374
  - **T100**
364
375
  - Hardware: 1.0 (EU) / Firmware: 1.12.0
376
+ - Hardware: 1.0 (US) / Firmware: 1.12.0
365
377
  - **T110**
366
378
  - Hardware: 1.0 (EU) / Firmware: 1.8.0
367
379
  - Hardware: 1.0 (EU) / Firmware: 1.9.0
@@ -59,6 +59,7 @@ export const LightBulbs = [
59
59
  'KL135',
60
60
  'KL50',
61
61
  'KL60',
62
+ 'LB100',
62
63
  'LB110',
63
64
  'L510',
64
65
  'L530',
@@ -73,6 +74,7 @@ export const LightBulbs = [
73
74
  ];
74
75
  export const Unsupported = [
75
76
  'C100',
77
+ 'C110',
76
78
  'C210',
77
79
  'C225',
78
80
  'C325WB',
@@ -1 +1 @@
1
- {"version":3,"file":"kasaDevices.js","sourceRoot":"","sources":["../../src/devices/kasaDevices.ts"],"names":[],"mappings":"AA4FA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,SAAS;IACT,SAAS;IACT,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC"}
1
+ {"version":3,"file":"kasaDevices.js","sourceRoot":"","sources":["../../src/devices/kasaDevices.ts"],"names":[],"mappings":"AA4FA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,SAAS;IACT,SAAS;IACT,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC"}
@@ -114,7 +114,7 @@ async def discover_devices(
114
114
  password: Optional[str] = None,
115
115
  additional_broadcasts: Optional[List[str]] = None,
116
116
  manual_devices: Optional[List[str]] = None,
117
- exlude_mac_addresses: Optional[List[str]] = None,
117
+ exclude_mac_addresses: Optional[List[str]] = None,
118
118
  ) -> Dict[str, Any]:
119
119
  devices = {}
120
120
  devices_to_remove = []
@@ -132,12 +132,15 @@ async def discover_devices(
132
132
  except (UnsupportedDeviceError, AuthenticationError) as e:
133
133
  print(f"{e.__class__.__name__}: {device.host}", file=sys.stderr)
134
134
  devices_to_remove.append(device.host)
135
+ await safe_disconnect(device)
135
136
  except Exception as e:
136
137
  print(f"Error during discovery: {e}", file=sys.stderr)
137
138
  devices_to_remove.append(device.host)
139
+ await safe_disconnect(device)
138
140
 
139
141
  async def discover_on_broadcast(broadcast: str):
140
142
  print(f"Discovering on broadcast: {broadcast}")
143
+ discovered = {}
141
144
  try:
142
145
  discovered = await Discover.discover(
143
146
  target=broadcast, credentials=credentials, on_discovered=on_discovered
@@ -148,6 +151,11 @@ async def discover_devices(
148
151
  devices.update(discovered)
149
152
  except Exception as e:
150
153
  print(f"Error during broadcast discovery: {e}", file=sys.stderr)
154
+ finally:
155
+ for host in list(discovered.keys()):
156
+ if host in device_config_cache:
157
+ del devices[host]
158
+ devices.update(discovered)
151
159
 
152
160
  async def discover_manual_device(host: str):
153
161
  if host in devices or host in device_config_cache:
@@ -159,6 +167,8 @@ async def discover_devices(
159
167
  devices[host] = device
160
168
  except Exception as e:
161
169
  print(f"Error during manual device discovery: {e}", file=sys.stderr)
170
+ devices_to_remove.append(host)
171
+ await safe_disconnect(device)
162
172
 
163
173
  discover_tasks = [discover_on_broadcast(bc) for bc in broadcasts]
164
174
  manual_discover_tasks = [discover_manual_device(host) for host in (manual_devices or [])]
@@ -171,14 +181,14 @@ async def discover_devices(
171
181
 
172
182
  for host in devices:
173
183
  device = devices[host]
174
- if device.mac in (exlude_mac_addresses or []):
184
+ if device.mac in (exclude_mac_addresses or []):
175
185
  devices_to_remove.append(host)
176
186
 
177
187
  for host in devices_to_remove:
178
188
  device = devices.pop(host, None)
179
189
  if device:
180
190
  print(f"Removing device: {device.alias}")
181
- await device.disconnect()
191
+ await safe_disconnect(device)
182
192
 
183
193
  for host, device_config_dict in device_config_cache.items():
184
194
  device = devices.get(host)
@@ -194,8 +204,7 @@ async def discover_devices(
194
204
  await device.update()
195
205
  except Exception as e:
196
206
  print(f"Error checking device: {e}", file=sys.stderr)
197
- if device:
198
- await device.disconnect()
207
+ await safe_disconnect(device)
199
208
  continue
200
209
 
201
210
  try:
@@ -207,11 +216,11 @@ async def discover_devices(
207
216
  print(f"Skipping device {device.alias} due to Native HomeKit support")
208
217
  if matter_component:
209
218
  print(f"Skipping device {device.alias} due to Matter support")
210
- await device.disconnect()
219
+ await safe_disconnect(device)
211
220
  continue
212
221
  except Exception as e:
213
222
  print(f"Error checking HomeKit and Matter modules: {e}", file=sys.stderr)
214
- await device.disconnect()
223
+ await safe_disconnect(device)
215
224
  continue
216
225
 
217
226
  device_type = device.device_type.value
@@ -223,7 +232,7 @@ async def discover_devices(
223
232
  update_tasks.append(create_device_info(host, device))
224
233
  else:
225
234
  print(f"Skipping unsupported device: {host}")
226
- await device.disconnect()
235
+ await safe_disconnect(device)
227
236
 
228
237
  results = await asyncio.gather(*update_tasks, return_exceptions=True)
229
238
 
@@ -231,26 +240,12 @@ async def discover_devices(
231
240
  host, info = result
232
241
  if isinstance(info, Exception):
233
242
  print(f"Error creating device info for host {host}: {info}", file=sys.stderr)
234
- try:
235
- device_config_dict = device_config_cache.get(host)
236
- if device_config_dict:
237
- device_config = DeviceConfig.from_dict(device_config_dict)
238
- device = await get_or_connect_device(host, device_config)
239
- if device:
240
- await device.disconnect()
241
- device_cache.pop(host, None)
242
- except Exception as e:
243
- print(f"Error disconnecting device: {e}", file=sys.stderr)
243
+ await handle_device_error(host)
244
244
  continue
245
245
  print(f"Device info created for device: {host}")
246
246
  all_device_info[host] = info
247
247
 
248
- for host, device in devices.items():
249
- try:
250
- await device.disconnect()
251
- print(f"Disconnected device: {host}")
252
- except Exception as e:
253
- print(f"Error disconnecting device {host}: {e}", file=sys.stderr)
248
+ await disconnect_all_devices(devices)
254
249
 
255
250
  return all_device_info
256
251
 
@@ -287,21 +282,7 @@ async def get_sys_info(host: str) -> Dict[str, Any]:
287
282
  device_info = custom_serializer(device)
288
283
  return {"sys_info": device_info["sys_info"]}
289
284
  except Exception as e:
290
- try:
291
- if device:
292
- await device.disconnect()
293
- device_cache.pop(host, None)
294
- device_lock = device_lock_cache.get(host, asyncio.Lock())
295
- async with device_lock:
296
- device = await get_or_connect_device(host, device_config)
297
- device_info = custom_serializer(device)
298
- return {"sys_info": device_info["sys_info"]}
299
- except Exception as e:
300
- print(f"GetSysInfo failed: {e}", file=sys.stderr)
301
- if device:
302
- await device.disconnect()
303
- device_cache.pop(host, None)
304
- return {"error": str(e)}
285
+ return await handle_device_error(host, e)
305
286
 
306
287
  async def get_or_connect_device(host: str, device_config: DeviceConfig) -> Device:
307
288
  try:
@@ -313,9 +294,7 @@ async def get_or_connect_device(host: str, device_config: DeviceConfig) -> Devic
313
294
  return device
314
295
  except Exception as e:
315
296
  print(f"Failed to connect to device: {e}", file=sys.stderr)
316
- if device:
317
- await device.disconnect()
318
- device_cache.pop(host, None)
297
+ await safe_disconnect(device)
319
298
  raise
320
299
 
321
300
  async def control_device(
@@ -334,20 +313,7 @@ async def control_device(
334
313
  device = await get_or_connect_device(host, device_config)
335
314
  return await perform_device_action(device, feature, action, value, child_num)
336
315
  except Exception as e:
337
- try:
338
- if device:
339
- await device.disconnect()
340
- device_cache.pop(host, None)
341
- device_lock = device_lock_cache.get(host, asyncio.Lock())
342
- async with device_lock:
343
- device = await get_or_connect_device(host, device_config)
344
- return await perform_device_action(device, feature, action, value, child_num)
345
- except Exception as e:
346
- print(f"ControlDevice failed: {e}", file=sys.stderr)
347
- if device:
348
- await device.disconnect()
349
- device_cache.pop(host, None)
350
- return {"error": str(e)}
316
+ return await handle_device_error(host, e)
351
317
 
352
318
  async def perform_device_action(
353
319
  device: Device,
@@ -421,8 +387,8 @@ async def discover_route():
421
387
  data: Dict[str, Any] = await request.get_json()
422
388
  additional_broadcasts = data.get('additionalBroadcasts', [])
423
389
  manual_devices = data.get('manualDevices', [])
424
- exlude_mac_addresses = data.get('excludeMacAddresses', [])
425
- devices_info = await discover_devices(username, password, additional_broadcasts, manual_devices, exlude_mac_addresses)
390
+ exclude_mac_addresses = data.get('excludeMacAddresses', [])
391
+ devices_info = await discover_devices(username, password, additional_broadcasts, manual_devices, exclude_mac_addresses)
426
392
  return jsonify(devices_info)
427
393
  except Exception as e:
428
394
  print(f"Discover route error: {e}", file=sys.stderr)
@@ -463,4 +429,29 @@ async def cleanup():
463
429
  print("Cleaning up and disconnecting all devices.")
464
430
  await close_all_connections()
465
431
  device_lock_cache.clear()
466
- device_config_cache.clear()
432
+ device_config_cache.clear()
433
+
434
+ async def safe_disconnect(device: Optional[Device]):
435
+ if device:
436
+ try:
437
+ await device.disconnect()
438
+ except Exception as e:
439
+ print(f"Error disconnecting device: {e}", file=sys.stderr)
440
+
441
+ async def handle_device_error(host: str, error: Optional[Exception] = None) -> Dict[str, Any]:
442
+ print(f"Handling device error for host {host}: {error}", file=sys.stderr)
443
+ try:
444
+ device_config_dict = device_config_cache.get(host)
445
+ if device_config_dict:
446
+ device_config = DeviceConfig.from_dict(device_config_dict)
447
+ device = await get_or_connect_device(host, device_config)
448
+ await safe_disconnect(device)
449
+ device_cache.pop(host, None)
450
+ except Exception as e:
451
+ print(f"Error during error handling: {e}", file=sys.stderr)
452
+ return {"error": str(error)}
453
+
454
+ async def disconnect_all_devices(devices: Dict[str, Device]):
455
+ for host, device in devices.items():
456
+ await safe_disconnect(device)
457
+ print(f"Disconnected device: {host}")
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.2-beta.2",
4
+ "version": "2.7.2-beta.4",
5
5
  "description": "Plugin that uses Python-Kasa API to communicate with Kasa Devices.",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -13,8 +13,8 @@
13
13
  "url": "https://github.com/ZeliardM/homebridge-kasa-python/issues"
14
14
  },
15
15
  "engines": {
16
- "homebridge": "^1.8.0 || ^2.0.0-beta.0",
17
- "node": "^18.20.6 || ^20.18.3 || ^22.14.0 || ^23.8.0",
16
+ "homebridge": "^1.9.0 || ^2.0.0-beta.0",
17
+ "node": "^18.20.7 || ^20.19.0 || ^22.14.0",
18
18
  "python": "^3.11.0"
19
19
  },
20
20
  "main": "dist/index.js",
@@ -48,19 +48,19 @@
48
48
  "requirements.txt"
49
49
  ],
50
50
  "devDependencies": {
51
- "@eslint/eslintrc": "^3.2.0",
52
- "@eslint/js": "^9.20.0",
53
- "@types/node": "^22.13.4",
54
- "@typescript-eslint/parser": "^8.24.1",
55
- "@stylistic/eslint-plugin": "^4.0.1",
56
- "eslint": "^9.20.1",
57
- "globals": "^15.15.0",
51
+ "@eslint/eslintrc": "^3.3.0",
52
+ "@eslint/js": "^9.22.0",
53
+ "@types/node": "^22.13.10",
54
+ "@typescript-eslint/parser": "^8.26.1",
55
+ "@stylistic/eslint-plugin": "^4.2.0",
56
+ "eslint": "^9.22.0",
57
+ "globals": "^16.0.0",
58
58
  "homebridge": "^2.0.0-beta.27",
59
59
  "node-persist": "^4.0.4",
60
60
  "nodemon": "^3.1.9",
61
61
  "rimraf": "^6.0.1",
62
62
  "ts-node": "^10.9.2",
63
- "typescript-eslint": "^8.24.1"
63
+ "typescript-eslint": "^8.26.1"
64
64
  },
65
65
  "homepage": "https://github.com/ZeliardM/homebridge-kasa-python#readme",
66
66
  "funding": [
@@ -74,9 +74,9 @@
74
74
  }
75
75
  ],
76
76
  "dependencies": {
77
- "axios": "^1.7.9",
77
+ "axios": "^1.8.3",
78
78
  "ts-essentials": "^10.0.4",
79
- "typescript": "^5.7.3"
79
+ "typescript": "^5.8.2"
80
80
  },
81
81
  "overrides": {
82
82
  "node-persist": "^4.0.4"