homebridge-kasa-python 1.0.2 → 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.
- package/README.md +21 -6
- package/config.schema.json +2 -33
- package/dist/accessoryInformation.d.ts +1 -1
- package/dist/accessoryInformation.js +14 -18
- package/dist/accessoryInformation.js.map +1 -1
- package/dist/categoriesParse.d.ts +8 -0
- package/dist/categoriesParse.js +44 -0
- package/dist/categoriesParse.js.map +1 -0
- package/dist/config.d.ts +12 -155
- package/dist/config.js +52 -100
- package/dist/config.js.map +1 -1
- package/dist/devices/create.d.ts +3 -9
- package/dist/devices/create.js +6 -7
- package/dist/devices/create.js.map +1 -1
- package/dist/devices/deviceManager.d.ts +6 -8
- package/dist/devices/deviceManager.js +47 -126
- package/dist/devices/deviceManager.js.map +1 -1
- package/dist/devices/homekitPlug.d.ts +10 -16
- package/dist/devices/homekitPlug.js +93 -74
- package/dist/devices/homekitPlug.js.map +1 -1
- package/dist/devices/homekitPowerstrip.d.ts +11 -17
- package/dist/devices/homekitPowerstrip.js +106 -73
- package/dist/devices/homekitPowerstrip.js.map +1 -1
- package/dist/devices/index.d.ts +12 -19
- package/dist/devices/index.js +44 -71
- package/dist/devices/index.js.map +1 -1
- package/dist/devices/kasaDevices.d.ts +4 -5
- package/dist/platform.d.ts +22 -29
- package/dist/platform.js +127 -131
- package/dist/platform.js.map +1 -1
- package/dist/python/kasaApi.py +109 -0
- package/dist/python/pythonChecker.d.ts +2 -2
- package/dist/python/pythonChecker.js +24 -89
- package/dist/python/pythonChecker.js.map +1 -1
- package/dist/utils.d.ts +3 -18
- package/dist/utils.js +72 -116
- package/dist/utils.js.map +1 -1
- package/package.json +20 -19
- package/requirements.txt +4 -1
- package/dist/python/discover.py +0 -38
- package/dist/python/getSysInfo.py +0 -39
- package/dist/python/turnOff.py +0 -20
- package/dist/python/turnOffChild.py +0 -22
- package/dist/python/turnOn.py +0 -20
- 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
|
|
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
|
-
|
|
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
|
|
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).
|
package/config.schema.json
CHANGED
|
@@ -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": "
|
|
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"
|
|
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,
|
|
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,
|
|
4
|
-
const infoService = accessory.getService(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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,
|
|
16
|
-
.setCharacteristic(Characteristic.Manufacturer,
|
|
17
|
-
.setCharacteristic(Characteristic.Model,
|
|
18
|
-
.setCharacteristic(Characteristic.SerialNumber,
|
|
19
|
-
.setCharacteristic(Characteristic.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":"
|
|
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,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
|
|
1
|
+
import { ErrorObject } from 'ajv';
|
|
2
2
|
export declare class ConfigParseError extends Error {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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:
|
|
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
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
29
|
+
formattedMessage += `\nAdditional Error: ${unknownError.message}`;
|
|
39
30
|
}
|
|
40
31
|
else if (unknownError) {
|
|
41
|
-
|
|
32
|
+
formattedMessage += `\nAdditional Error: [Error details not available: ${unknownError}]`;
|
|
42
33
|
}
|
|
43
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
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
|
-
|
|
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 (!
|
|
86
|
+
if (!isObjectLike(config)) {
|
|
127
87
|
throw new ConfigParseError('Error parsing config');
|
|
128
88
|
}
|
|
129
|
-
const c =
|
|
130
|
-
const defaultSendOptions = {
|
|
131
|
-
timeout: c.timeout * 1000,
|
|
132
|
-
};
|
|
89
|
+
const c = { ...defaultConfig, ...config };
|
|
133
90
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
}
|