homebridge-kasa-python 1.0.1 → 2.0.0

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.
Files changed (45) hide show
  1. package/README.md +21 -6
  2. package/config.schema.json +2 -33
  3. package/dist/accessoryInformation.d.ts +1 -1
  4. package/dist/accessoryInformation.js +14 -18
  5. package/dist/accessoryInformation.js.map +1 -1
  6. package/dist/categoriesParse.d.ts +8 -0
  7. package/dist/categoriesParse.js +44 -0
  8. package/dist/categoriesParse.js.map +1 -0
  9. package/dist/config.d.ts +12 -155
  10. package/dist/config.js +52 -100
  11. package/dist/config.js.map +1 -1
  12. package/dist/devices/create.d.ts +3 -9
  13. package/dist/devices/create.js +6 -7
  14. package/dist/devices/create.js.map +1 -1
  15. package/dist/devices/deviceManager.d.ts +6 -8
  16. package/dist/devices/deviceManager.js +47 -126
  17. package/dist/devices/deviceManager.js.map +1 -1
  18. package/dist/devices/homekitPlug.d.ts +10 -16
  19. package/dist/devices/homekitPlug.js +93 -74
  20. package/dist/devices/homekitPlug.js.map +1 -1
  21. package/dist/devices/homekitPowerstrip.d.ts +11 -17
  22. package/dist/devices/homekitPowerstrip.js +106 -73
  23. package/dist/devices/homekitPowerstrip.js.map +1 -1
  24. package/dist/devices/index.d.ts +12 -19
  25. package/dist/devices/index.js +44 -71
  26. package/dist/devices/index.js.map +1 -1
  27. package/dist/devices/kasaDevices.d.ts +4 -5
  28. package/dist/platform.d.ts +22 -29
  29. package/dist/platform.js +127 -131
  30. package/dist/platform.js.map +1 -1
  31. package/dist/python/kasaApi.py +109 -0
  32. package/dist/python/pythonChecker.d.ts +2 -2
  33. package/dist/python/pythonChecker.js +24 -89
  34. package/dist/python/pythonChecker.js.map +1 -1
  35. package/dist/utils.d.ts +3 -18
  36. package/dist/utils.js +72 -116
  37. package/dist/utils.js.map +1 -1
  38. package/package.json +23 -15
  39. package/requirements.txt +4 -1
  40. package/dist/python/discover.py +0 -38
  41. package/dist/python/getSysInfo.py +0 -39
  42. package/dist/python/turnOff.py +0 -20
  43. package/dist/python/turnOffChild.py +0 -22
  44. package/dist/python/turnOn.py +0 -20
  45. package/dist/python/turnOnChild.py +0 -22
package/README.md CHANGED
@@ -16,20 +16,32 @@
16
16
  <a href="https://www.npmjs.com/package/homebridge-kasa-python"><img src="https://badgen.net/npm/dt/homebridge-kasa-python" alt="npm downloads total"></a>
17
17
  <a href="https://www.npmjs.com/package/homebridge-kasa-python"><img src="https://badgen.net/npm/dm/homebridge-kasa-python" alt="npm downloads monthly"></a>
18
18
  <a href="https://www.paypal.me/ZeliardM/USD/"><img src="https://badgen.net/badge/donate/paypal/E69138" alt="donate"></a>
19
+ <a href="https://github.com/sponsors/ZeliardM"><img src="https://badgen.net/badge/donate/github/E69138" alt="donate"></a>
19
20
  <a href="https://pypi.org/project/python-kasa/"><img src="https://img.shields.io/badge/Python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue" alt="PyPI pyversions"></a>
20
21
  </p>
21
22
 
23
+ <div align="center">
24
+
25
+ > ## IMPORTANT!!!
26
+ >This Plug-In Has Only Been Configured And Tested On Proxmox LXC. I Have Not Tested This On Any Other Platforms. If You Install It On Another Platform and It Works, Please Let Me Know So It Can Be Added To The List Of Supported Platforms.</p>
27
+ >Updates To Code Require Cached Accessories To Be Removed To Update Naming Conventions For Homebridge 2.0 Update.
28
+
29
+ </div>
30
+
22
31
  This is a [Homebridge](https://github.com/homebridge/homebridge) plug-in based on the Python-Kasa API Library to interact with TP-Link Kasa Devices.
23
32
 
24
- This plug-in automatically discovers your TP-Link Kasa Devices on your network and configures them to be used in HomeKit.
33
+ This plug-in will automatically discover your TP-Link Kasa Devices on your network locally only and configure them to be used in HomeKit.
34
+
35
+ Automatic Discovery is possible only for some devices, some newer devices require the Username and Password for your TP-Link Kasa Cloud Account. Credentials can be provided in the plug-in settings.
25
36
 
26
- Automatic Discovery is possible only for some devices, some newer devices require the Username and Password for your TP-Link Kasa Cloud Account.
37
+ ### Current Supported and Tested Devices
38
+ - 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.
27
39
 
28
40
  ### Features
29
41
 
30
- - Automatically discover TP-Link Kasa Devices on your network.
31
- - Change Device States for Plugs, Change Device State and Supports Dimming for Switches, Change Device State, and Supports Hue, Saturation, and Value (HSV), Color, and Temperature Adjustments for Bulbs that Support those options.
32
- - Supported Devices are listed below, Devices with an asterisks ('*') next to the specific firmware will require the Username and Password for your TP-Link Kasa Cloud Account to connect and function.
42
+ - Automatically discover TP-Link Kasa Devices locally only on your network.
43
+ - Change Device States for Plugs, Change Device States for Power Strips, Change Device State and Supports Dimming for Switches, Change Device State and Supports Hue, Saturation, and Value (HSV), Color, Color Temperature Adjustments, and Dimming for Bulbs and Light Strips that support those options.</p>*NOTE - Not All Functions Are Currently Supported, All Devices and Functions Could Be Supported By The Plug-In In The Furture.*</p>
44
+ - 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 Cloud Account to connect and function correctly.</p>*NOTE - Not All Devices Listed Below Are Supported By This Plug-In. These Devices Are Supported By The Python-Kasa API And Could Be Supported By The Plug-In In The Future.*</p>
33
45
  <div align="center">
34
46
  <table style="border-collapse: collapse; border: 1px solid black;">
35
47
  <thead>
@@ -395,4 +407,7 @@ Automatic Discovery is possible only for some devices, some newer devices requir
395
407
  </tr>
396
408
  </tbody>
397
409
  </table>
398
- </div>
410
+ </div>
411
+
412
+ ### Credits
413
+ - Huge thanks to GadgetReactor for the [Python-Kasa API](https://github.com/python-kasa/python-kasa), Donald Patrick Seal for the [Unofficial API documentation](https://github.com/plasticrake/tplink-smarthome-api), and Maximilian Leith for [Excellent Python Implementation](https://github.com/maxileith/homebridge-appletv-enhanced).
@@ -37,27 +37,12 @@
37
37
  "description": "Enable to create a single power strip accessory with multiple outlets, used for models HS107, KP200, HS300, KP303, KP400, and EP40. Default: false",
38
38
  "default": false
39
39
  },
40
- "discoveryPort": {
41
- "title": "Port",
42
- "type": "number",
43
- "required": false,
44
- "description": "Port to bind UDP socket for discovery. If port is not specified or is 0, the operating system will attempt to bind to a random port. Default: 0",
45
- "placeholder": "0"
46
- },
47
- "broadcastAddress": {
48
- "title": "Broadcast Address",
49
- "type": "string",
50
- "required": false,
51
- "description": "Broadcast Address. If discovery is not working tweak to match your subnet, eg: 192.168.1.255",
52
- "placeholder": "255.255.255.255",
53
- "format": "ipv4"
54
- },
55
40
  "pollingInterval": {
56
41
  "title": "Polling Interval (seconds)",
57
42
  "type": "integer",
58
43
  "required": false,
59
44
  "description": "How often to check device status in the background (seconds)",
60
- "placeholder": "10"
45
+ "placeholder": "5"
61
46
  },
62
47
  "includeMacAddresses": {
63
48
  "title": "Include MAC Addresses",
@@ -92,12 +77,6 @@
92
77
  "required": true,
93
78
  "placeholder": "192.168.1.1",
94
79
  "format": "ipv4"
95
- },
96
- "port": {
97
- "title": "Port",
98
- "type": "string",
99
- "required": false,
100
- "placeholder": "9999"
101
80
  }
102
81
  }
103
82
  }
@@ -116,13 +95,6 @@
116
95
  "description": "Here you can specify a path that points to a python executable. The plugin uses the systems default python as default. Setting a specific python executable here may be required if your systems default python version is too current for the plugin.",
117
96
  "placeholder": "/path/to/python3"
118
97
  },
119
- "timeout": {
120
- "title": "Timeout (seconds)",
121
- "type": "integer",
122
- "required": false,
123
- "description": "Communication Timeout (seconds)",
124
- "placeholder": "15"
125
- },
126
98
  "waitTimeUpdate": {
127
99
  "title": "Wait Time Update (milliseconds)",
128
100
  "type": "integer",
@@ -155,9 +127,7 @@
155
127
  "description": "Customize device discovery",
156
128
  "expandable": true,
157
129
  "items": [
158
- "broadcastAddress",
159
130
  "pollingInterval",
160
- "discoveryPort",
161
131
  {
162
132
  "key": "includeMacAddresses",
163
133
  "type": "array",
@@ -188,7 +158,7 @@
188
158
  "key": "devices",
189
159
  "type": "array",
190
160
  "buttonText": "Add Device",
191
- "items": ["devices[].host", "devices[].port"]
161
+ "items": ["devices[].host"]
192
162
  },
193
163
  {
194
164
  "type": "help",
@@ -206,7 +176,6 @@
206
176
  "items": [
207
177
  "pythonExecutable",
208
178
  "forceVenvRecreate",
209
- "timeout",
210
179
  "waitTimeUpdate"
211
180
  ]
212
181
  }
@@ -1,3 +1,3 @@
1
1
  import type { HAP, PlatformAccessory, Service } from 'homebridge';
2
2
  import type HomekitDevice from './devices/index.js';
3
- export default function accessoryInformation(hap: HAP): (accessory: PlatformAccessory, hkDevice: HomekitDevice) => Service | undefined;
3
+ export default function accessoryInformation(hap: HAP): (accessory: PlatformAccessory, homekitDevice: HomekitDevice) => Service | undefined;
@@ -1,23 +1,19 @@
1
1
  export default function accessoryInformation(hap) {
2
- const { Characteristic } = hap;
3
- return (accessory, hkDevice) => {
4
- const infoService = accessory.getService(hap.Service.AccessoryInformation);
5
- if (infoService === undefined) {
6
- return undefined;
7
- }
8
- if (!infoService.getCharacteristic(Characteristic.FirmwareRevision)) {
9
- infoService.addCharacteristic(Characteristic.FirmwareRevision);
10
- }
11
- if (!infoService.getCharacteristic(Characteristic.HardwareRevision)) {
12
- infoService.addCharacteristic(Characteristic.HardwareRevision);
13
- }
2
+ const { Characteristic, Service: { AccessoryInformation } } = hap;
3
+ return (accessory, homekitDevice) => {
4
+ const infoService = accessory.getService(AccessoryInformation) ?? accessory.addService(AccessoryInformation);
5
+ [Characteristic.Name, Characteristic.Manufacturer, Characteristic.Model, Characteristic.SerialNumber, Characteristic.FirmwareRevision]
6
+ .forEach(characteristic => {
7
+ if (!infoService.getCharacteristic(characteristic)) {
8
+ infoService.addCharacteristic(characteristic);
9
+ }
10
+ });
14
11
  infoService
15
- .setCharacteristic(Characteristic.Name, hkDevice.name)
16
- .setCharacteristic(Characteristic.Manufacturer, hkDevice.manufacturer)
17
- .setCharacteristic(Characteristic.Model, hkDevice.model)
18
- .setCharacteristic(Characteristic.SerialNumber, hkDevice.serialNumber)
19
- .setCharacteristic(Characteristic.FirmwareRevision, hkDevice.firmwareRevision)
20
- .setCharacteristic(Characteristic.HardwareRevision, hkDevice.hardwareRevision);
12
+ .setCharacteristic(Characteristic.Name, homekitDevice.name)
13
+ .setCharacteristic(Characteristic.Manufacturer, homekitDevice.manufacturer)
14
+ .setCharacteristic(Characteristic.Model, homekitDevice.model)
15
+ .setCharacteristic(Characteristic.SerialNumber, homekitDevice.serialNumber)
16
+ .setCharacteristic(Characteristic.FirmwareRevision, homekitDevice.firmwareRevision);
21
17
  return infoService;
22
18
  };
23
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"accessoryInformation.js","sourceRoot":"","sources":["../src/accessoryInformation.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAC1C,GAAQ;IAKR,MAAM,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC;IAE/B,OAAO,CAAC,SAA4B,EAAE,QAAuB,EAAE,EAAE;QAC/D,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC3E,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpE,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACpE,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACjE,CAAC;QACD,WAAW;aACR,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;aACrD,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC;aACrE,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;aACvD,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC;aACrE,iBAAiB,CAChB,cAAc,CAAC,gBAAgB,EAC/B,QAAQ,CAAC,gBAAgB,CAC1B;aACA,iBAAiB,CAChB,cAAc,CAAC,gBAAgB,EAC/B,QAAQ,CAAC,gBAAgB,CAC1B,CAAC;QAEJ,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"accessoryInformation.js","sourceRoot":"","sources":["../src/accessoryInformation.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAC1C,GAAQ;IAER,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,oBAAoB,EAAE,EAAE,GAAG,GAAG,CAAC;IAElE,OAAO,CAAC,SAA4B,EAAE,aAA4B,EAAE,EAAE;QACpE,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,oBAAoB,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;QAE7G,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,YAAY,EAAE,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,YAAY,EAAE,cAAc,CAAC,gBAAgB,CAAC;aACnI,OAAO,CAAC,cAAc,CAAC,EAAE;YACxB,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,CAAC;gBACnD,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC,CAAC;QAEL,WAAW;aACR,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;aAC1D,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,aAAa,CAAC,YAAY,CAAC;aAC1E,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;aAC5D,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,aAAa,CAAC,YAAY,CAAC;aAC1E,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAEtF,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import KasaPythonPlatform from './platform.js';
2
+ export declare class EnumParser {
3
+ private platform;
4
+ constructor(platform: KasaPythonPlatform);
5
+ private parseConstEnum;
6
+ private generateEnumObject;
7
+ parse(): Record<number, string> | null;
8
+ }
@@ -0,0 +1,44 @@
1
+ import ts from 'typescript';
2
+ export class EnumParser {
3
+ platform;
4
+ constructor(platform) {
5
+ this.platform = platform;
6
+ }
7
+ parseConstEnum(sourceFile) {
8
+ const enums = {};
9
+ const visit = (node) => {
10
+ if (ts.isEnumDeclaration(node) &&
11
+ node.name.text === 'Categories' &&
12
+ node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ConstKeyword)) {
13
+ const enumName = node.name.text;
14
+ enums[enumName] = node.members.map(member => ({
15
+ value: member.initializer && ts.isNumericLiteral(member.initializer) ? Number(member.initializer.text) : 0,
16
+ name: ts.isIdentifier(member.name) ? member.name.text : '',
17
+ }));
18
+ }
19
+ ts.forEachChild(node, visit);
20
+ };
21
+ visit(sourceFile);
22
+ return enums;
23
+ }
24
+ generateEnumObject(enums) {
25
+ return Object.values(enums).reduce((acc, members) => {
26
+ members.forEach(member => {
27
+ acc[member.value] = member.name;
28
+ });
29
+ return acc;
30
+ }, {});
31
+ }
32
+ parse() {
33
+ const fileToParse = `${this.platform.storagePath}/node_modules/homebridge/node_modules/hap-nodejs/dist/lib/Accessory.d.ts`;
34
+ const program = ts.createProgram([fileToParse], {});
35
+ const sourceFile = program.getSourceFile(fileToParse);
36
+ if (!sourceFile) {
37
+ this.platform.log.error('Source file not found.');
38
+ return null;
39
+ }
40
+ const enums = this.parseConstEnum(sourceFile);
41
+ return this.generateEnumObject(enums);
42
+ }
43
+ }
44
+ //# sourceMappingURL=categoriesParse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categoriesParse.js","sourceRoot":"","sources":["../src/categoriesParse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,OAAO,UAAU;IACD;IAApB,YAAoB,QAA4B;QAA5B,aAAQ,GAAR,QAAQ,CAAoB;IAAG,CAAC;IAE5C,cAAc,CAAC,UAAyB;QAC9C,MAAM,KAAK,GAAsD,EAAE,CAAC;QAEpE,MAAM,KAAK,GAAG,CAAC,IAAa,EAAE,EAAE;YAC9B,IACE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY;gBAC/B,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAC9E,CAAC;gBACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAChC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBAC5C,KAAK,EAAE,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC1G,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;iBAC3D,CAAC,CAAC,CAAC;YACN,CAAC;YACD,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC;QAEF,KAAK,CAAC,UAAU,CAAC,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,kBAAkB,CAAC,KAAwD;QACjF,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;YAClD,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;gBACvB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;YAClC,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAA4B,CAAC,CAAC;IACnC,CAAC;IAEM,KAAK;QACV,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,0EAA0E,CAAC;QAC3H,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QAEtD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;CACF"}
package/dist/config.d.ts CHANGED
@@ -1,186 +1,43 @@
1
- import { ErrorObject as AjvErrorObject } from 'ajv';
1
+ import { ErrorObject } from 'ajv';
2
2
  export declare class ConfigParseError extends Error {
3
- readonly errors?: AjvErrorObject<string, Record<string, unknown>, unknown>[] | null | undefined;
4
- readonly unknownError?: unknown | undefined;
5
- /**
6
- * Set by `Error.captureStackTrace`
7
- */
8
- readonly stack = "";
9
- constructor(message: string, errors?: AjvErrorObject<string, Record<string, unknown>, unknown>[] | null | undefined, unknownError?: unknown | undefined);
3
+ errors?: (ErrorObject<string, Record<string, unknown>, unknown>[] | null) | undefined;
4
+ unknownError?: unknown;
5
+ constructor(message: string, errors?: (ErrorObject<string, Record<string, unknown>, unknown>[] | null) | undefined, unknownError?: unknown);
6
+ private formatMessage;
10
7
  }
11
8
  export interface DeviceConfigInput {
12
9
  host: string;
13
- port?: number | undefined;
10
+ port?: number;
14
11
  }
15
12
  export interface KasaPythonConfigInput {
16
- /**
17
- * Username. If discovering devices is not working, try setting this to your Kasa Cloud Username.
18
- * This is required for the following devices:
19
- * EP25
20
- - Hardware: 2.6 (US) / Firmware: 1.0.1
21
- - Hardware: 2.6 (US) / Firmware: 1.0.2
22
- HS100
23
- - Hardware: 4.1 (UK) / Firmware: 1.1.0
24
- KP125M
25
- - Hardware: 1.0 (US) / Firmware: 1.1.3
26
- HS220
27
- - Hardware: 3.26 (US) / Firmware: 1.0.1
28
- KS205
29
- - Hardware: 1.0 (US) / Firmware: 1.0.2
30
- - Hardware: 1.0 (US) / Firmware: 1.1.0
31
- KS225
32
- - Hardware: 1.0 (US) / Firmware: 1.0.2
33
- - Hardware: 1.0 (US) / Firmware: 1.1.0
34
- KS240
35
- - Hardware: 1.0 (US) / Firmware: 1.0.4
36
- - Hardware: 1.0 (US) / Firmware: 1.0.5
37
- KH100
38
- - Hardware: 1.0 (UK) / Firmware: 1.5.6
39
- KE100
40
- - Hardware: 1.0 (EU) / Firmware: 2.4.0
41
- - Hardware: 1.0 (EU) / Firmware: 2.8.0
42
- - Hardware: 1.0 (UK) / Firmware: 2.8.0
43
- * @defaultValue ''
44
- */
13
+ name?: string;
45
14
  username?: string;
46
- /**
47
- * Password. If discovering devices is not working, try setting this to your Kasa Cloud Password.
48
- * This is required for the following devices:
49
- * EP25
50
- - Hardware: 2.6 (US) / Firmware: 1.0.1
51
- - Hardware: 2.6 (US) / Firmware: 1.0.2
52
- HS100
53
- - Hardware: 4.1 (UK) / Firmware: 1.1.0
54
- KP125M
55
- - Hardware: 1.0 (US) / Firmware: 1.1.3
56
- HS220
57
- - Hardware: 3.26 (US) / Firmware: 1.0.1
58
- KS205
59
- - Hardware: 1.0 (US) / Firmware: 1.0.2
60
- - Hardware: 1.0 (US) / Firmware: 1.1.0
61
- KS225
62
- - Hardware: 1.0 (US) / Firmware: 1.0.2
63
- - Hardware: 1.0 (US) / Firmware: 1.1.0
64
- KS240
65
- - Hardware: 1.0 (US) / Firmware: 1.0.4
66
- - Hardware: 1.0 (US) / Firmware: 1.0.5
67
- KH100
68
- - Hardware: 1.0 (UK) / Firmware: 1.5.6
69
- KE100
70
- - Hardware: 1.0 (EU) / Firmware: 2.4.0
71
- - Hardware: 1.0 (EU) / Firmware: 2.8.0
72
- - Hardware: 1.0 (UK) / Firmware: 2.8.0
73
- * @defaultValue ''
74
- */
75
15
  password?: string;
76
- /**
77
- * Create Multi-Outlet Devices as a Power Strip.
78
- * Enable to create a single power strip accessory with multiple outlets, used for models HS107, KP200, HS300, KP303, KP400, and EP40.
79
- * @defaultValue false
80
- */
81
16
  powerStrip?: boolean;
82
- /**
83
- * Port to bind UDP socket for discovery.
84
- * If port is not specified or is 0, the operating system will attempt to bind to a random port.
85
- * @defaultValue 0
86
- */
87
- discoveryPort?: number;
88
- /**
89
- * Broadcast Address. If discovery is not working, tweak to match your subnet, eg: 192.168.1.255
90
- * @defaultValue '255.255.255.255'
91
- */
92
- broadcastAddress?: string;
93
- /**
94
- * How often to check device status in the background (seconds)
95
- * @defaultValue 10
96
- */
97
17
  pollingInterval?: number;
98
- /**
99
- * Allow-list of MAC Addresses to include. If specified will ignore other devices.
100
- * MAC Addresses are normalized, special characters are removed and made uppercase for comparison.
101
- * Supports glob-style patterns
102
- */
103
18
  includeMacAddress?: Array<string>;
104
- /**
105
- * Deny-list of MAC Addresses to exclude.
106
- * MAC Addresses are normalized, special characters are removed and made uppercase for comparison.
107
- * Supports glob-style patterns
108
- */
109
19
  excludeMacAddresses?: Array<string>;
110
- /**
111
- * Manual list of devices Before resorting to manually specifying devices.
112
- * Try setting the broadcast address and check your router/switch/firewall configuration.
113
- * You must assign static IP addresses to your devices to use this configuration.
114
- */
115
20
  devices?: Array<DeviceConfigInput>;
116
- /**
117
- * Force Venv Recreation
118
- * Set this to force to recreate the virtual python environment with the next restart of the plugin.
119
- * @defaultValue false
120
- */
121
21
  forceVenvRecreate?: boolean;
122
- /**
123
- * Python Executable
124
- * Here you can specify a path that points to a python executable. The plugin uses the systems default python as default. Setting a
125
- * specific python executable here may be required if your systems default python version is too current for the plugin.
126
- */
127
22
  pythonExecutable?: string;
128
- /**
129
- * Communication Timeout (seconds)
130
- * @defaultValue 15
131
- */
132
- timeout?: number;
133
- /**
134
- * The time to wait to combine similar commands for a device before sending a command to a device (milliseconds)
135
- * @defaultValue 100
136
- */
137
23
  waitTimeUpdate?: number;
138
24
  }
139
- type KasaPythonConfigDefault = {
140
- username: string;
141
- password: string;
142
- powerStrip: boolean;
143
- discoveryPort: number;
144
- broadcastAddress: string;
145
- pollingInterval: number;
146
- includeMacAddress?: Array<string>;
147
- excludeMacAddresses?: Array<string>;
148
- devices?: Array<{
149
- host: string;
150
- port?: number | undefined;
151
- }>;
152
- forceVenvRecreate: boolean;
153
- pythonExecutable?: string;
154
- timeout: number;
155
- waitTimeUpdate: number;
156
- };
157
25
  export type KasaPythonConfig = {
26
+ name: string;
158
27
  username: string;
159
28
  password: string;
160
- forceVenvRecreate: boolean;
161
- pythonExecutable?: string;
162
- waitTimeUpdate: number;
163
29
  powerStrip: boolean;
164
- defaultSendOptions: {
165
- timeout: number;
166
- };
167
30
  discoveryOptions: {
168
- port: number | undefined;
169
- broadcastAddress: string;
170
31
  pollingInterval: number;
171
- deviceOptions: {
172
- defaultSendOptions: {
173
- timeout: number;
174
- };
175
- };
176
32
  includeMacAddress?: Array<string>;
177
33
  excludeMacAddresses?: Array<string>;
178
34
  devices?: Array<{
179
35
  host: string;
180
- port?: number | undefined;
181
36
  }>;
182
37
  };
38
+ forceVenvRecreate: boolean;
39
+ pythonExecutable?: string;
40
+ waitTimeUpdate: number;
183
41
  };
184
- export declare const defaultConfig: KasaPythonConfigDefault;
42
+ export declare const defaultConfig: KasaPythonConfig;
185
43
  export declare function parseConfig(config: Record<string, unknown>): KasaPythonConfig;
186
- export {};
package/dist/config.js CHANGED
@@ -1,98 +1,67 @@
1
1
  import Ajv from 'ajv';
2
2
  import addFormats from 'ajv-formats';
3
- import * as fs from 'fs';
4
- import defaults from 'lodash.defaults';
5
- import * as path from 'path';
6
- import { fileURLToPath } from 'url';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
7
6
  import { isObjectLike } from './utils.js';
7
+ let schemaCache;
8
8
  export class ConfigParseError extends Error {
9
9
  errors;
10
10
  unknownError;
11
- /**
12
- * Set by `Error.captureStackTrace`
13
- */
14
- stack = '';
15
11
  constructor(message, errors, unknownError) {
16
12
  super(message);
17
13
  this.errors = errors;
18
14
  this.unknownError = unknownError;
19
- const errorsAsString = errors !== null && errors !== undefined
20
- ? errors
21
- .map((e) => {
22
- let msg = `\`${e.instancePath.replace(/^\//, '')}\` ${e.message}`;
23
- if ('allowedValues' in e.params) {
24
- msg += `. Allowed values: ${JSON.stringify(e.params.allowedValues)}`;
25
- }
26
- return msg;
27
- })
28
- .join('\n')
29
- : '';
30
15
  this.name = 'ConfigParseError';
31
- if (errorsAsString === '') {
32
- this.message = message;
33
- }
34
- else {
35
- this.message = `${message}:\n${errorsAsString}`;
16
+ this.message = this.formatMessage(message, errors, unknownError);
17
+ Error.captureStackTrace(this, this.constructor);
18
+ }
19
+ formatMessage(message, errors, unknownError) {
20
+ let formattedMessage = message;
21
+ if (errors && errors.length > 0) {
22
+ const errorsAsString = errors.map((e) => {
23
+ const allowedValues = 'allowedValues' in e.params ? `. Allowed values: ${JSON.stringify(e.params.allowedValues)}` : '';
24
+ return `\`${e.instancePath.replace(/^\//, '')}\` ${e.message}${allowedValues}`;
25
+ }).join('\n');
26
+ formattedMessage += `:\n${errorsAsString}`;
36
27
  }
37
28
  if (unknownError instanceof Error) {
38
- this.message += `\nAdditional Error: ${unknownError.message}`;
29
+ formattedMessage += `\nAdditional Error: ${unknownError.message}`;
39
30
  }
40
31
  else if (unknownError) {
41
- this.message += `\nAdditional Error: [Error details not available: ${unknownError}]`;
32
+ formattedMessage += `\nAdditional Error: [Error details not available: ${unknownError}]`;
42
33
  }
43
- Error.captureStackTrace(this, this.constructor);
34
+ return formattedMessage;
44
35
  }
45
36
  }
46
37
  export const defaultConfig = {
38
+ name: 'kasa-python',
47
39
  username: '',
48
40
  password: '',
49
41
  powerStrip: false,
50
- discoveryPort: 0,
51
- broadcastAddress: '255.255.255.255',
52
- pollingInterval: 10,
53
- includeMacAddress: undefined,
54
- excludeMacAddresses: undefined,
55
- devices: undefined,
42
+ discoveryOptions: {
43
+ pollingInterval: 5,
44
+ includeMacAddress: undefined,
45
+ excludeMacAddresses: undefined,
46
+ devices: undefined,
47
+ },
56
48
  forceVenvRecreate: false,
57
49
  pythonExecutable: undefined,
58
- timeout: 15,
59
50
  waitTimeUpdate: 100,
60
51
  };
61
- function isArrayOfStrings(value) {
62
- return (Array.isArray(value) && value.every((item) => typeof item === 'string'));
63
- }
64
- function isDeviceConfigInput(value) {
65
- return (isObjectLike(value) &&
66
- 'host' in value &&
67
- typeof value.host === 'string' &&
68
- (!('port' in value) || typeof value.port === 'number'));
69
- }
70
- function isArrayOfDeviceConfigInput(value) {
71
- return (Array.isArray(value) && value.every((item) => isDeviceConfigInput(item)));
72
- }
73
- function isKasaPythonConfigInput(c) {
74
- return (isObjectLike(c) &&
75
- (!('username' in c) || typeof c.username === 'string') &&
76
- (!('password' in c) || typeof c.password === 'string') &&
77
- (!('powerStrip' in c) || typeof c.powerStrip === 'boolean') &&
78
- (!('discoveryPort' in c) || typeof c.discoveryPort === 'number') &&
79
- (!('broadcastAddress' in c) || typeof c.broadcastAddress === 'string') &&
80
- (!('pollingInterval' in c) || typeof c.pollingInterval === 'number') &&
81
- (!('includeMacAddress' in c) ||
82
- isArrayOfStrings(c.includeMacAddress) ||
83
- c.includeMacAddress === undefined) &&
84
- (!('excludeMacAddresses' in c) ||
85
- isArrayOfStrings(c.excludeMacAddresses) ||
86
- c.excludeMacAddresses === undefined) &&
87
- (!('devices' in c) ||
88
- isArrayOfDeviceConfigInput(c.devices) ||
89
- c.devices === undefined) &&
90
- (!('forceVenvRecreate' in c) || typeof c.forceVenvRecreate === 'boolean') &&
91
- (!('pythonExecutable' in c) ||
92
- typeof c.pythonExecutable === 'string' ||
93
- c.pythonExecutable === undefined) &&
94
- (!('timeout' in c) || typeof c.timeout === 'number') &&
95
- (!('waitTimeUpdate' in c) || typeof c.waitTimeUpdate === 'number'));
52
+ function loadSchema() {
53
+ if (!schemaCache) {
54
+ try {
55
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
56
+ const schemaPath = path.join(__dirname, '../config.schema.json');
57
+ const schemaData = fs.readFileSync(schemaPath, 'utf8');
58
+ schemaCache = JSON.parse(schemaData);
59
+ }
60
+ catch (error) {
61
+ throw new ConfigParseError('Error reading schema', undefined, error);
62
+ }
63
+ }
64
+ return schemaCache;
96
65
  }
97
66
  export function parseConfig(config) {
98
67
  const ajv = new Ajv({ allErrors: true, strict: 'log' });
@@ -108,46 +77,29 @@ export function parseConfig(config) {
108
77
  'schema',
109
78
  'layout',
110
79
  ]);
111
- let schema;
112
- try {
113
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
114
- const schemaPath = path.join(__dirname, '../config.schema.json');
115
- const schemaData = fs.readFileSync(schemaPath, 'utf8');
116
- schema = JSON.parse(schemaData);
117
- }
118
- catch (error) {
119
- throw new ConfigParseError('Error reading schema', undefined, error);
120
- }
80
+ const schema = loadSchema();
121
81
  const validate = ajv.compile(schema);
122
82
  const valid = validate(config);
123
83
  if (!valid) {
124
84
  throw new ConfigParseError('Error parsing config', validate.errors);
125
85
  }
126
- if (!isKasaPythonConfigInput(config)) {
86
+ if (!isObjectLike(config)) {
127
87
  throw new ConfigParseError('Error parsing config');
128
88
  }
129
- const c = defaults(config, defaultConfig);
130
- const defaultSendOptions = {
131
- timeout: c.timeout * 1000,
132
- };
89
+ const c = { ...defaultConfig, ...config };
133
90
  return {
134
- username: c.username,
135
- password: c.password,
136
- powerStrip: c.powerStrip,
137
- forceVenvRecreate: c.forceVenvRecreate,
138
- pythonExecutable: c.pythonExecutable,
139
- waitTimeUpdate: c.waitTimeUpdate,
140
- defaultSendOptions,
91
+ name: c.name ?? defaultConfig.name,
92
+ username: c.username ?? defaultConfig.username,
93
+ password: c.password ?? defaultConfig.password,
94
+ powerStrip: c.powerStrip ?? defaultConfig.powerStrip,
95
+ forceVenvRecreate: c.forceVenvRecreate ?? defaultConfig.forceVenvRecreate,
96
+ pythonExecutable: c.pythonExecutable ?? defaultConfig.pythonExecutable,
97
+ waitTimeUpdate: c.waitTimeUpdate ?? defaultConfig.waitTimeUpdate,
141
98
  discoveryOptions: {
142
- port: c.discoveryPort,
143
- broadcastAddress: c.broadcastAddress,
144
- pollingInterval: c.pollingInterval * 1000,
145
- deviceOptions: {
146
- defaultSendOptions,
147
- },
148
- includeMacAddress: c.includeMacAddress,
149
- excludeMacAddresses: c.excludeMacAddresses,
150
- devices: c.devices,
99
+ pollingInterval: (c.discoveryOptions.pollingInterval ?? defaultConfig.discoveryOptions.pollingInterval) * 1000,
100
+ includeMacAddress: c.discoveryOptions.includeMacAddress ?? defaultConfig.discoveryOptions.includeMacAddress,
101
+ excludeMacAddresses: c.discoveryOptions.excludeMacAddresses ?? defaultConfig.discoveryOptions.excludeMacAddresses,
102
+ devices: c.discoveryOptions.devices ?? defaultConfig.discoveryOptions.devices,
151
103
  },
152
104
  };
153
105
  }