homebridge-kasa-python 3.1.0 → 3.2.0-beta.1

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
@@ -15,43 +15,119 @@
15
15
  <a href="https://www.npmjs.com/package/homebridge-kasa-python/v/latest"><img src="https://img.shields.io/npm/v/homebridge-kasa-python/latest?label=npm%40latest&color=blue" alt="latest npm version"></a>
16
16
  <a href="https://pypi.org/project/python-kasa/"><img src="https://img.shields.io/badge/Python%40latest-3.11%2C%203.12%2C%203.13-blue" alt="latest PyPI pyversions"></a>
17
17
  <a href="https://www.npmjs.com/package/homebridge-kasa-python/v/beta"><img src="https://img.shields.io/npm/v/homebridge-kasa-python/beta?label=npm%40beta&color=red" alt="beta npm version"></a>
18
- <a href="https://pypi.org/project/python-kasa/"><img src="https://img.shields.io/badge/Python%40latest-3.11%2C%203.12%2C%203.13-red" alt="beta PyPI pyversions"></a>
18
+ <a href="https://pypi.org/project/python-kasa/"><img src="https://img.shields.io/badge/Python%40beta-3.11%2C%203.12%2C%203.13-red" alt="beta PyPI pyversions"></a>
19
19
  <a href="https://www.npmjs.com/package/homebridge-kasa-python/v/latest"><img src="https://img.shields.io/npm/dt/homebridge-kasa-python?color=brightgreen" alt="npm downloads total"></a>
20
20
  <a href="https://www.paypal.me/ZeliardM/USD/"><img src="https://img.shields.io/badge/donate-paypal-orange" alt="donate paypal"></a>
21
21
  <a href="https://github.com/sponsors/ZeliardM"><img src="https://img.shields.io/badge/donate-github-orange" alt="donate github"></a>
22
22
  <a href="https://github.com/homebridge/homebridge/wiki/Verified-Plugins"><img src="https://img.shields.io/badge/homebridge-verified-blueviolet" alt="homebridge verified"></a>
23
23
  </p>
24
24
 
25
- <div align="center">
26
-
27
- > ## IMPORTANT!!!
28
- >Python Supported Versions: 3.11, 3.12, and 3.13.
29
-
30
- </div>
31
-
32
25
  This is a [Homebridge](https://github.com/homebridge/homebridge) plug-in based on the Python-Kasa API Library to interact with TP-Link Kasa/Tapo Devices.
33
26
 
34
27
  This plug-in will automatically discover your TP-Link Kasa/Tapo Devices on your network locally only and configure them to be used in HomeKit.
35
28
 
36
29
  Automatic Discovery may be possible only for some devices. If your device is not discovered automatically, try adding the IP Address into the Manual Devices List. Some newer devices require the Username and Password for your TP-Link Kasa/Tapo Cloud Account. Credentials can be enabled and provided in the plug-in settings.
37
30
 
38
- ### Current Supported and Tested Devices
39
- - I currently have used this plug-in with the HS300 (US) Power Strip and the KP115 (US) Plug. All other devices are yet to be tested and fully supported. If you have a device that is a plug or power strip and it does work as expected in HomeKit, please let me know and I can add it to the list of tested and supported devices. I don't have a lot of devices so cannot fully test everything.
40
-
41
- ### Features
42
-
43
- - Automatically discover TP-Link Kasa/Tapo Devices locally only on your network.
44
- - Currently only Plugs, Power Strips, Wall Switches, Bulbs, and Light Strips are supported by the plug-in and added to Homebridge.
45
- - This plug-in will by default filter out Native HomeKit/Matter Devices, this can be disabled to implement all supported devices on the network if desired.
46
- - Change Device States for Plugs, Change Device States for Power Strips, Change Device State and Supports Dimming for Wall Switches, Change Device State and Supports Hue, Saturation, and Value (HSV) and Color Temperature Adjustments, and Dimming for Bulbs and Light Strips that support those options. The KS240 Dual Fan and Light Dimmer Wall Switch is also supported if Native HomeKit/Matter Device Filtering is Disabled.
47
- - Supported Devices from the API are listed below, Devices with an asterisks ('*') next to the specific firmware will require the Username and Password for your TP-Link Kasa/Tapo Cloud Account to connect and function correctly. If your device is not listed below, it does not mean it won't work, but there may be issues. </p>*NOTE - Not All Devices Listed Below Are Supported By This Plug-In. These Devices Are Supported By The Python-Kasa API Library And Could Be Supported By The Plug-In In The Future.*</p>
31
+ ## Requirements
32
+ - Homebridge Supported Versions: 1.8.0 and 2.0.0-beta.0 or later.
33
+ - Node.js Supported Versions: 20, 22, and 24.
34
+ - Python Supported Versions: 3.11, 3.12, and 3.13.
35
+ - A supported Kasa/Tapo device.
36
+ - Enabling Third Party Compatibility in the Tapo/Kasa App can improve device compatibility.
37
+
38
+ ## Current Supported and Tested Devices
39
+ - I currently have used this plug-in with the HS300 (US) Power Strip and the KP115 (US) Plug. All other devices are yet to be tested and fully supported. If you have a device that is a plug, power strip, wall switch, bulb, or light strip and it does work as expected in HomeKit, please let me know and I can add it to the list of tested and supported devices. I don't have a lot of devices so cannot fully test everything.
40
+
41
+ ## Features
42
+ - Automatically discover TP-Link Kasa/Tapo Devices locally only on your network.
43
+ - Currently Plugs, Power Strips, Wall Switches, Bulbs, and Light Strips are supported by the plug-in and added to Homebridge.
44
+ - This plug-in will by default filter out Native HomeKit/Matter Devices, this can be disabled to implement all supported devices on the network if desired.
45
+ - Change Device States for Plugs, Change Device States for Power Strips, Change Device State and Supports Dimming for Wall Switches, Change Device State and Supports Hue, Saturation, and Value (HSV) and Color Temperature Adjustments, and Dimming for Bulbs and Light Strips that support those options. The KS240 Dual Fan and Light Dimmer Wall Switch is also supported if Native HomeKit/Matter Device Filtering is Disabled.
46
+ - OutletInUse on devices that support energy monitoring can follow real device power usage even when the extra HomeKit energy characteristics are not enabled.
47
+ - Energy monitoring characteristics (Volts, Amperes, Watts, and KiloWattHours) can be enabled separately for supported devices.
48
+ - Supported Devices from the API are listed below, Devices with an asterisks ('*') next to the specific firmware will require the Username and Password for your TP-Link Kasa/Tapo Cloud Account to connect and function correctly. If your device is not listed below, it does not mean it won't work, but there may be issues. </p>*NOTE - Not All Devices Listed Below Are Supported By This Plug-In. These Devices Are Supported By The Python-Kasa API Library And Could Be Supported By The Plug-In In The Future.*</p>
49
+
50
+ ## Installation
51
+ - Install from the Homebridge UI or with npm.
52
+ - Most users can install the plug-in, click Save, and restart Homebridge.
53
+ - On first startup, or any time the Python requirements change, the plug-in will verify Python, create/update the virtual environment, and install/update the required Python packages automatically.
54
+
55
+ ```bash
56
+ npm install -g homebridge-kasa-python
57
+ ```
58
+
59
+ ## Configuration Notes
60
+ - Enable Credentials only if your devices require authentication. Some newer Kasa/Tapo devices will not work without the TP-Link Kasa/Tapo Cloud Account Username and Password.
61
+ - Manual Devices now only require the device host or IP Address. If discovery is not working, try the Manual Devices List and Additional Broadcast Addresses before assuming a device is unsupported.
62
+ - Hide HomeKit or Matter Devices is enabled by default so supported native HomeKit/Matter devices are not duplicated in Homebridge.
63
+ - Wait Time Update controls how long similar commands are combined before being sent to a device.
64
+ - Advanced Python Logging only shows detailed Python-side logs when Homebridge Debug Mode is enabled.
65
+
66
+ ## Energy Notes
67
+ - Enable Energy Monitoring adds the extra HomeKit energy monitoring characteristics (Volts, Amperes, Watts, and KiloWattHours) for supported devices.
68
+ - OutletInUse for devices that support energy reporting is based on the device reported power usage, even when Enable Energy Monitoring is disabled.
69
+ - Outlet In Use Power Threshold defaults to 2 Watts and can be set as low as 0.001 in the configuration.
70
+ - OutletInUse is true when reported power is greater than the configured threshold and false when the reported power is less than or equal to the configured threshold.
71
+ - OutletInUse updates from energy reporting are debounced across two consecutive polling updates to help reduce noise from very small changes in reported power.
72
+ - Log Energy Monitoring Events enables logging of the extra energy characteristics only.
73
+
74
+ ## Example Configuration
75
+ ```json
76
+ {
77
+ "bridge": {
78
+ "name": "Homebridge",
79
+ "username": "11:22:33:AA:BB:CC",
80
+ "port": 12345,
81
+ "pin": "001-02-003"
82
+ },
83
+ "description": "This is an example configuration file.",
84
+ "platforms": [
85
+ {
86
+ "platform": "KasaPython",
87
+ "name": "KasaPython",
88
+ "enableCredentials": true,
89
+ "username": "Username",
90
+ "password": "Password",
91
+ "enableEnergyMonitoring": true,
92
+ "powerThreshold": 1.0,
93
+ "logEnergyMonitoring": false,
94
+ "hideHomeKitMatter": true,
95
+ "pollingInterval": 5,
96
+ "discoveryPollingInterval": 300,
97
+ "offlineInterval": 7,
98
+ "waitTimeUpdate": 100,
99
+ "pythonPath": "/usr/bin/python3",
100
+ "advancedPythonLogging": false,
101
+ "additionalBroadcasts": [
102
+ "192.168.1.255",
103
+ "192.168.2.255"
104
+ ],
105
+ "manualDevices": [
106
+ {
107
+ "host": "192.168.1.100"
108
+ },
109
+ {
110
+ "host": "192.168.2.100"
111
+ }
112
+ ],
113
+ "excludeMacAddresses": [
114
+ "AA:BB:CC:11:22:33",
115
+ "CC:BB:AA:33:22:11"
116
+ ],
117
+ "includeMacAddresses": [
118
+ "DD:EE:FF:44:55:66",
119
+ "FF:EE:DD:66:55:44"
120
+ ]
121
+ }
122
+ ],
123
+ "accessories": []
124
+ }
125
+ ```
48
126
 
49
127
  ## Kasa devices
50
-
51
128
  Some newer Kasa devices require authentication. These are marked with [*] in the list below.<br>Hub-Connected Devices may work across TAPO/KASA branded hubs even if they don't work across the native apps.
52
129
 
53
130
  ### Plugs
54
-
55
131
  - **EP10**
56
132
  - Hardware: 1.0 (US) / Firmware: 1.0.2
57
133
  - **EP25**
@@ -91,7 +167,6 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
91
167
  - Hardware: 1.0 (US) / Firmware: 1.0.0
92
168
 
93
169
  ### Power Strips
94
-
95
170
  - **EP40**
96
171
  - Hardware: 1.0 (US) / Firmware: 1.0.2
97
172
  - **EP40M**
@@ -116,7 +191,6 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
116
191
  - Hardware: 3.0 (US) / Firmware: 1.0.4
117
192
 
118
193
  ### Wall Switches
119
-
120
194
  - **ES20M**
121
195
  - Hardware: 1.0 (US) / Firmware: 1.0.11
122
196
  - Hardware: 1.0 (US) / Firmware: 1.0.8
@@ -164,7 +238,6 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
164
238
  - Hardware: 1.0 (US) / Firmware: 1.0.7[*]
165
239
 
166
240
  ### Bulbs
167
-
168
241
  - **KL110**
169
242
  - Hardware: 1.0 (US) / Firmware: 1.8.11
170
243
  - **KL110B**
@@ -195,7 +268,6 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
195
268
  - Hardware: 1.0 (US) / Firmware: 1.8.11
196
269
 
197
270
  ### Light Strips
198
-
199
271
  - **KL400L5**
200
272
  - Hardware: 1.0 (US) / Firmware: 1.0.5
201
273
  - Hardware: 1.0 (US) / Firmware: 1.0.8
@@ -211,26 +283,21 @@ Some newer Kasa devices require authentication. These are marked with [*] in the
211
283
  - Hardware: 2.0 (US) / Firmware: 1.0.9
212
284
 
213
285
  ### Hubs
214
-
215
286
  - **KH100**
216
287
  - Hardware: 1.0 (EU) / Firmware: 1.2.3[*]
217
288
  - Hardware: 1.0 (EU) / Firmware: 1.5.12[*]
218
289
  - Hardware: 1.0 (UK) / Firmware: 1.5.6[*]
219
290
 
220
291
  ### Hub-Connected Devices
221
-
222
292
  - **KE100**
223
293
  - Hardware: 1.0 (EU) / Firmware: 2.4.0[*]
224
294
  - Hardware: 1.0 (EU) / Firmware: 2.8.0[*]
225
295
  - Hardware: 1.0 (UK) / Firmware: 2.8.0[*]
226
296
 
227
-
228
297
  ## Tapo devices
229
-
230
298
  All Tapo devices require authentication.<br>Hub-Connected Devices may work across TAPO/KASA branded hubs even if they don't work across the native apps.
231
299
 
232
300
  ### Plugs
233
-
234
301
  - **P100**
235
302
  - Hardware: 1.0.0 (US) / Firmware: 1.1.3
236
303
  - Hardware: 1.0.0 (US) / Firmware: 1.3.7
@@ -259,7 +326,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
259
326
  - Hardware: 1.0 (US) / Firmware: 1.0.3
260
327
 
261
328
  ### Power Strips
262
-
263
329
  - **P210M**
264
330
  - Hardware: 1.0 (US) / Firmware: 1.0.3
265
331
  - **P300**
@@ -277,7 +343,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
277
343
  - Hardware: 1.0 (US) / Firmware: 1.0.2
278
344
 
279
345
  ### Wall Switches
280
-
281
346
  - **S210**
282
347
  - Hardware: 1.0 (EU) / Firmware: 1.9.0
283
348
  - **S220**
@@ -296,7 +361,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
296
361
  - Hardware: 1.0 (US) / Firmware: 1.2.2
297
362
 
298
363
  ### Bulbs
299
-
300
364
  - **L430C**
301
365
  - Hardware: 1.0 (EU) / Firmware: 1.0.4
302
366
  - **L430P**
@@ -320,7 +384,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
320
384
  - Hardware: 1.0 (EU) / Firmware: 1.1.2
321
385
 
322
386
  ### Light Strips
323
-
324
387
  - **L900-10**
325
388
  - Hardware: 1.0 (EU) / Firmware: 1.0.17
326
389
  - Hardware: 1.0 (US) / Firmware: 1.0.11
@@ -337,7 +400,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
337
400
  - Hardware: 1.0 (US) / Firmware: 1.1.2
338
401
 
339
402
  ### Cameras
340
-
341
403
  - **C100**
342
404
  - Hardware: 4.0 / Firmware: 1.3.14
343
405
  - **C110**
@@ -363,7 +425,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
363
425
  - Hardware: 3.0 / Firmware: 1.3.11
364
426
 
365
427
  ### Doorbells and chimes
366
-
367
428
  - **D100C**
368
429
  - Hardware: 1.0 (US) / Firmware: 1.1.3
369
430
  - **D130**
@@ -372,14 +433,12 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
372
433
  - Hardware: 1.20 (EU) / Firmware: 1.1.19
373
434
 
374
435
  ### Vacuums
375
-
376
436
  - **RV20 Max Plus**
377
437
  - Hardware: 1.0 (EU) / Firmware: 1.0.7
378
438
  - **RV30 Max**
379
439
  - Hardware: 1.0 (US) / Firmware: 1.2.0
380
440
 
381
441
  ### Hubs
382
-
383
442
  - **H100**
384
443
  - Hardware: 1.0 (AU) / Firmware: 1.5.23
385
444
  - Hardware: 1.0 (EU) / Firmware: 1.2.3
@@ -391,7 +450,6 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
391
450
  - Hardware: 1.0 (US) / Firmware: 1.3.6
392
451
 
393
452
  ### Hub-Connected Devices
394
-
395
453
  - **S200B**
396
454
  - Hardware: 1.0 (EU) / Firmware: 1.11.0
397
455
  - Hardware: 1.0 (US) / Firmware: 1.12.0
@@ -414,5 +472,5 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
414
472
  - Hardware: 1.0 (EU) / Firmware: 1.7.0
415
473
  - Hardware: 1.0 (US) / Firmware: 1.8.0
416
474
 
417
- ### Credits
418
- - Huge thanks to rytilahti and all the developers at python-kasa for the [Python-Kasa API](https://github.com/python-kasa/python-kasa), plasticrake for the [Unofficial API documentation](https://github.com/plasticrake/tplink-smarthome-api), and maxileith for [Excellent Python Implementation](https://github.com/maxileith/homebridge-appletv-enhanced).
475
+ ## Credits
476
+ - Huge thanks to rytilahti and all the developers at python-kasa for the [Python-Kasa API](https://github.com/python-kasa/python-kasa), plasticrake for the [Unofficial API documentation](https://github.com/plasticrake/tplink-smarthome-api), and maxileith for [Excellent Python Implementation](https://github.com/maxileith/homebridge-appletv-enhanced).
@@ -47,6 +47,19 @@
47
47
  "description": "Enable to add energy monitoring characteristics (Volts, Amperes, Watts, KiloWattHours) to supported devices.",
48
48
  "default": false
49
49
  },
50
+ "powerThreshold": {
51
+ "title": "Outlet In Use Power Threshold (Watts)",
52
+ "type": "number",
53
+ "description": "Power threshold used to mark OutletInUse as true on devices with energy reporting support. Values must be at least 0.001 and may use up to three decimal places.",
54
+ "default": 2,
55
+ "minimum": 0.001
56
+ },
57
+ "logEnergyMonitoring": {
58
+ "title": "Log Energy Monitoring Events",
59
+ "type": "boolean",
60
+ "description": "Enable logging of power/energy monitoring characteristic updates (Volts, Amperes, Watts, KiloWattHours).",
61
+ "default": false
62
+ },
50
63
  "hideHomeKitMatter": {
51
64
  "title": "Hide HomeKit or Matter Devices",
52
65
  "type": "boolean",
@@ -90,14 +103,6 @@
90
103
  "host": {
91
104
  "type": "string",
92
105
  "title": "Host"
93
- },
94
- "alias": {
95
- "type": "string",
96
- "title": "Alias",
97
- "readonly": true,
98
- "condition": {
99
- "functionBody": "return model.manualDevices && model.manualDevices[arrayIndices] && model.manualDevices[arrayIndices].host && model.manualDevices[arrayIndices].host !== '';"
100
- }
101
106
  }
102
107
  }
103
108
  },
@@ -139,12 +144,6 @@
139
144
  "type": "boolean",
140
145
  "description": "Enable detailed logging for Python scripts. Only shows logs when Debug Mode in Homebridge is enabled.",
141
146
  "default": false
142
- },
143
- "logEnergyMonitoring": {
144
- "title": "Log Energy Monitoring Events",
145
- "type": "boolean",
146
- "description": "Enable logging of power/energy monitoring characteristic updates (Volts, Amperes, Watts, KiloWattHours).",
147
- "default": false
148
147
  }
149
148
  }
150
149
  },
@@ -165,7 +164,17 @@
165
164
  "type": "help",
166
165
  "helpvalue": "Username and Password will be required for specific devices only."
167
166
  },
168
- "enableEnergyMonitoring",
167
+ {
168
+ "type": "fieldset",
169
+ "title": "Energy (Optional)",
170
+ "description": "Configure energy-based Outlet In Use handling and optional energy characteristics.",
171
+ "expandable": true,
172
+ "items": [
173
+ "enableEnergyMonitoring",
174
+ "powerThreshold",
175
+ "logEnergyMonitoring"
176
+ ]
177
+ },
169
178
  {
170
179
  "type": "fieldset",
171
180
  "title": "HomeKit (Optional)",
@@ -210,8 +219,7 @@
210
219
  "key": "manualDevices",
211
220
  "type": "array",
212
221
  "items": [
213
- "manualDevices[].host",
214
- "manualDevices[].alias"
222
+ "manualDevices[].host"
215
223
  ]
216
224
  },
217
225
  {
@@ -262,8 +270,7 @@
262
270
  "items": [
263
271
  "waitTimeUpdate",
264
272
  "pythonPath",
265
- "advancedPythonLogging",
266
- "logEnergyMonitoring"
273
+ "advancedPythonLogging"
267
274
  ]
268
275
  }
269
276
  ]
package/dist/config.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface KasaPythonConfigInput {
11
11
  username?: string;
12
12
  password?: string;
13
13
  enableEnergyMonitoring?: boolean;
14
+ powerThreshold?: number;
15
+ logEnergyMonitoring?: boolean;
14
16
  hideHomeKitMatter?: boolean;
15
17
  pollingInterval?: number;
16
18
  discoveryPollingInterval?: number;
@@ -22,14 +24,17 @@ export interface KasaPythonConfigInput {
22
24
  waitTimeUpdate?: number;
23
25
  pythonPath?: string;
24
26
  advancedPythonLogging?: boolean;
25
- logEnergyMonitoring?: boolean;
26
27
  }
27
28
  export type KasaPythonConfig = {
28
29
  name: string;
29
30
  enableCredentials: boolean;
30
31
  username: string;
31
32
  password: string;
32
- enableEnergyMonitoring: boolean;
33
+ energyOptions: {
34
+ enableEnergyMonitoring: boolean;
35
+ powerThreshold: number;
36
+ logEnergyMonitoring: boolean;
37
+ };
33
38
  homekitOptions: {
34
39
  hideHomeKitMatter: boolean;
35
40
  };
@@ -46,14 +51,7 @@ export type KasaPythonConfig = {
46
51
  waitTimeUpdate: number;
47
52
  pythonPath?: string;
48
53
  advancedPythonLogging: boolean;
49
- logEnergyMonitoring: boolean;
50
54
  };
51
55
  };
52
56
  export declare const defaultConfig: KasaPythonConfig;
53
- export declare function migrateManualDevices(manualDevices: (string | ConfigDevice)[] | undefined | null): {
54
- manualDevices: ConfigDevice[];
55
- changed: boolean;
56
- };
57
57
  export declare function parseConfig(config: Record<string, unknown>): KasaPythonConfig;
58
- export declare function loadPlatformConfigFromStorage(storagePath: string): Promise<KasaPythonConfig>;
59
- export declare function persistDiscoveredAliases(storagePath: string, aliasesByHost: Map<string, string>): Promise<KasaPythonConfig | undefined>;
package/dist/config.js CHANGED
@@ -1,6 +1,3 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { PLATFORM_NAME } from './settings.js';
4
1
  import { isObjectLike } from './utils.js';
5
2
  export class ConfigParseError extends Error {
6
3
  errors;
@@ -33,7 +30,11 @@ export const defaultConfig = {
33
30
  enableCredentials: false,
34
31
  username: '',
35
32
  password: '',
36
- enableEnergyMonitoring: false,
33
+ energyOptions: {
34
+ enableEnergyMonitoring: false,
35
+ powerThreshold: 2,
36
+ logEnergyMonitoring: false,
37
+ },
37
38
  homekitOptions: {
38
39
  hideHomeKitMatter: true,
39
40
  },
@@ -50,34 +51,25 @@ export const defaultConfig = {
50
51
  waitTimeUpdate: 100,
51
52
  pythonPath: '',
52
53
  advancedPythonLogging: false,
53
- logEnergyMonitoring: false,
54
54
  },
55
55
  };
56
- const MISSING_ALIAS_PLACEHOLDER = 'Will Be Filled By Plug-In Automatically';
57
56
  function isNonEmptyString(value) {
58
57
  return typeof value === 'string' && value.trim().length > 0;
59
58
  }
60
- export function migrateManualDevices(manualDevices) {
59
+ function normalizeManualDevices(manualDevices) {
61
60
  if (!manualDevices || manualDevices.length === 0) {
62
- return { manualDevices: [], changed: false };
61
+ return [];
63
62
  }
64
- let changed = false;
65
- const migratedDevices = manualDevices.map(device => {
63
+ return manualDevices.flatMap(device => {
66
64
  if (typeof device === 'string') {
67
- changed = true;
68
- return { host: device, alias: MISSING_ALIAS_PLACEHOLDER };
65
+ return isNonEmptyString(device) ? [{ host: device }] : [];
69
66
  }
70
67
  const migratedDevice = device;
71
- const alias = isNonEmptyString(migratedDevice.alias) ? migratedDevice.alias : MISSING_ALIAS_PLACEHOLDER;
72
- if (alias !== migratedDevice.alias || 'breakoutChildDevices' in migratedDevice) {
73
- changed = true;
68
+ if (!isNonEmptyString(migratedDevice.host)) {
69
+ return [];
74
70
  }
75
- return {
76
- host: migratedDevice.host,
77
- alias,
78
- };
71
+ return [{ host: migratedDevice.host }];
79
72
  });
80
- return { manualDevices: migratedDevices, changed };
81
73
  }
82
74
  function validateManualDevices(manualDevices, errors) {
83
75
  if (manualDevices === undefined) {
@@ -92,9 +84,6 @@ function validateManualDevices(manualDevices, errors) {
92
84
  if (!isNonEmptyString(entry)) {
93
85
  errors.push(`\`manualDevices[${index}]\` should not be an empty string.`);
94
86
  }
95
- else {
96
- errors.push(`\`manualDevices[${index}]\` should be an object with \`host\` and \`alias\`.`);
97
- }
98
87
  return;
99
88
  }
100
89
  if (!isObjectLike(entry)) {
@@ -105,12 +94,6 @@ function validateManualDevices(manualDevices, errors) {
105
94
  if (!isNonEmptyString(device.host)) {
106
95
  errors.push(`\`manualDevices[${index}].host\` should be a non-empty string.`);
107
96
  }
108
- if (!isNonEmptyString(device.alias)) {
109
- errors.push(`\`manualDevices[${index}].alias\` should be a non-empty string.`);
110
- }
111
- if ('breakoutChildDevices' in device) {
112
- errors.push(`\`manualDevices[${index}]\` uses unsupported legacy field \`breakoutChildDevices\`.`);
113
- }
114
97
  });
115
98
  }
116
99
  function validateConfig(config) {
@@ -120,6 +103,8 @@ function validateConfig(config) {
120
103
  validateType(config, 'username', 'string', errors);
121
104
  validateType(config, 'password', 'string', errors);
122
105
  validateType(config, 'enableEnergyMonitoring', 'boolean', errors);
106
+ validateType(config, 'powerThreshold', 'number', errors);
107
+ validateType(config, 'logEnergyMonitoring', 'boolean', errors);
123
108
  validateType(config, 'hideHomeKitMatter', 'boolean', errors);
124
109
  validateType(config, 'pollingInterval', 'number', errors);
125
110
  validateType(config, 'discoveryPollingInterval', 'number', errors);
@@ -137,31 +122,49 @@ function validateConfig(config) {
137
122
  validateType(config, 'waitTimeUpdate', 'number', errors);
138
123
  validateType(config, 'pythonPath', 'string', errors);
139
124
  validateType(config, 'advancedPythonLogging', 'boolean', errors);
140
- validateType(config, 'logEnergyMonitoring', 'boolean', errors);
125
+ validatePowerThreshold(config.powerThreshold, errors);
141
126
  return errors;
142
127
  }
128
+ function validatePowerThreshold(value, errors) {
129
+ if (value === undefined) {
130
+ return;
131
+ }
132
+ if (typeof value !== 'number' || Number.isNaN(value)) {
133
+ return;
134
+ }
135
+ if (value < 0.001) {
136
+ errors.push('`powerThreshold` should be at least 0.001.');
137
+ }
138
+ if (Math.abs(Math.round(value * 1000) - (value * 1000)) > Number.EPSILON * 1000) {
139
+ errors.push('`powerThreshold` should use no more than three decimal places.');
140
+ }
141
+ }
143
142
  function validateType(config, key, expectedType, errors) {
144
143
  if (config[key] !== undefined && typeof config[key] !== expectedType) {
145
144
  errors.push(`\`${key}\` should be a ${expectedType}.`);
146
145
  }
147
146
  }
148
147
  export function parseConfig(config) {
148
+ if (!isObjectLike(config)) {
149
+ throw new ConfigParseError('Error parsing config');
150
+ }
149
151
  const errors = validateConfig(config);
150
152
  if (errors.length > 0) {
151
153
  throw new ConfigParseError('Error parsing config', errors);
152
154
  }
153
- if (!isObjectLike(config)) {
154
- throw new ConfigParseError('Error parsing config');
155
- }
156
155
  const parsedConfig = { ...defaultConfig, ...config };
157
- const strictManualDevices = parsedConfig.manualDevices
156
+ const normalizedManualDevices = normalizeManualDevices(parsedConfig.manualDevices)
158
157
  ?? defaultConfig.discoveryOptions.manualDevices;
159
158
  return {
160
159
  name: parsedConfig.name ?? defaultConfig.name,
161
160
  enableCredentials: parsedConfig.enableCredentials ?? defaultConfig.enableCredentials,
162
161
  username: parsedConfig.username ?? defaultConfig.username,
163
162
  password: parsedConfig.password ?? defaultConfig.password,
164
- enableEnergyMonitoring: parsedConfig.enableEnergyMonitoring ?? defaultConfig.enableEnergyMonitoring,
163
+ energyOptions: {
164
+ enableEnergyMonitoring: parsedConfig.enableEnergyMonitoring ?? defaultConfig.energyOptions.enableEnergyMonitoring,
165
+ powerThreshold: parsedConfig.powerThreshold ?? defaultConfig.energyOptions.powerThreshold,
166
+ logEnergyMonitoring: parsedConfig.logEnergyMonitoring ?? defaultConfig.energyOptions.logEnergyMonitoring,
167
+ },
165
168
  homekitOptions: {
166
169
  hideHomeKitMatter: parsedConfig.hideHomeKitMatter ?? defaultConfig.homekitOptions.hideHomeKitMatter,
167
170
  },
@@ -170,7 +173,7 @@ export function parseConfig(config) {
170
173
  discoveryPollingInterval: (parsedConfig.discoveryPollingInterval ?? defaultConfig.discoveryOptions.discoveryPollingInterval) * 1000,
171
174
  offlineInterval: (parsedConfig.offlineInterval ?? defaultConfig.discoveryOptions.offlineInterval) * 24 * 60 * 60 * 1000,
172
175
  additionalBroadcasts: parsedConfig.additionalBroadcasts ?? defaultConfig.discoveryOptions.additionalBroadcasts,
173
- manualDevices: strictManualDevices,
176
+ manualDevices: normalizedManualDevices,
174
177
  excludeMacAddresses: parsedConfig.excludeMacAddresses ?? defaultConfig.discoveryOptions.excludeMacAddresses,
175
178
  includeMacAddresses: parsedConfig.includeMacAddresses ?? defaultConfig.discoveryOptions.includeMacAddresses,
176
179
  },
@@ -178,70 +181,7 @@ export function parseConfig(config) {
178
181
  waitTimeUpdate: parsedConfig.waitTimeUpdate ?? defaultConfig.advancedOptions.waitTimeUpdate,
179
182
  pythonPath: parsedConfig.pythonPath ?? defaultConfig.advancedOptions.pythonPath,
180
183
  advancedPythonLogging: parsedConfig.advancedPythonLogging ?? defaultConfig.advancedOptions.advancedPythonLogging,
181
- logEnergyMonitoring: parsedConfig.logEnergyMonitoring ?? defaultConfig.advancedOptions.logEnergyMonitoring,
182
184
  },
183
185
  };
184
186
  }
185
- function getConfigPath(storagePath) {
186
- return path.join(storagePath, 'config.json');
187
- }
188
- async function readHomebridgeConfig(storagePath) {
189
- const data = await fs.readFile(getConfigPath(storagePath), 'utf8');
190
- return JSON.parse(data);
191
- }
192
- async function writeHomebridgeConfig(storagePath, fileConfig) {
193
- await fs.writeFile(getConfigPath(storagePath), JSON.stringify(fileConfig, null, 2), 'utf8');
194
- }
195
- function getPlatformSection(fileConfig, platformName) {
196
- return fileConfig.platforms?.find(platformConfig => platformConfig.platform === platformName);
197
- }
198
- export async function loadPlatformConfigFromStorage(storagePath) {
199
- const fileConfig = await readHomebridgeConfig(storagePath);
200
- const platformSection = getPlatformSection(fileConfig, PLATFORM_NAME);
201
- if (!platformSection) {
202
- throw new ConfigParseError('KasaPython configuration missing in config file.');
203
- }
204
- const { manualDevices, changed } = migrateManualDevices(platformSection.manualDevices);
205
- if (platformSection.manualDevices !== undefined) {
206
- platformSection.manualDevices = manualDevices;
207
- }
208
- const parsedConfig = parseConfig(platformSection);
209
- if (changed) {
210
- await writeHomebridgeConfig(storagePath, fileConfig);
211
- }
212
- return parsedConfig;
213
- }
214
- export async function persistDiscoveredAliases(storagePath, aliasesByHost) {
215
- if (aliasesByHost.size === 0) {
216
- return undefined;
217
- }
218
- const fileConfig = await readHomebridgeConfig(storagePath);
219
- const platformSection = getPlatformSection(fileConfig, PLATFORM_NAME);
220
- if (!platformSection) {
221
- throw new ConfigParseError('KasaPython configuration missing in config file.');
222
- }
223
- const manualDevices = platformSection.manualDevices;
224
- if (!Array.isArray(manualDevices) || manualDevices.length === 0) {
225
- return undefined;
226
- }
227
- let changed = false;
228
- for (const entry of manualDevices) {
229
- if (!isObjectLike(entry)) {
230
- continue;
231
- }
232
- const device = entry;
233
- const host = typeof device.host === 'string' ? device.host : undefined;
234
- const nextAlias = host ? aliasesByHost.get(host) : undefined;
235
- if (!nextAlias || device.alias === nextAlias) {
236
- continue;
237
- }
238
- device.alias = nextAlias;
239
- changed = true;
240
- }
241
- if (!changed) {
242
- return undefined;
243
- }
244
- await writeHomebridgeConfig(storagePath, fileConfig);
245
- return parseConfig(platformSection);
246
- }
247
187
  //# sourceMappingURL=config.js.map