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.
- 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 +23 -15
- 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/dist/platform.d.ts
CHANGED
|
@@ -1,57 +1,50 @@
|
|
|
1
1
|
import { Categories } from 'homebridge';
|
|
2
2
|
import type { API, Characteristic, DynamicPlatformPlugin, Logging, PlatformAccessory, PlatformConfig, Service, WithUUID } from 'homebridge';
|
|
3
|
+
import DeviceManager from './devices/deviceManager.js';
|
|
3
4
|
import type { KasaPythonConfig } from './config.js';
|
|
4
|
-
import type { KasaDevice } from './
|
|
5
|
+
import type { KasaDevice } from './devices/kasaDevices.js';
|
|
5
6
|
export type KasaPythonAccessoryContext = {
|
|
6
7
|
deviceId?: string;
|
|
7
8
|
};
|
|
8
9
|
export default class KasaPythonPlatform implements DynamicPlatformPlugin {
|
|
9
10
|
readonly log: Logging;
|
|
10
11
|
readonly api: API;
|
|
11
|
-
readonly Service: typeof Service;
|
|
12
12
|
readonly Characteristic: typeof Characteristic;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
private readonly homekitDevicesById;
|
|
13
|
+
readonly configuredAccessories: Map<string, PlatformAccessory<KasaPythonAccessoryContext>>;
|
|
14
|
+
readonly Service: typeof Service;
|
|
16
15
|
readonly storagePath: string;
|
|
17
16
|
readonly venvPythonExecutable: string;
|
|
18
|
-
|
|
17
|
+
config: KasaPythonConfig;
|
|
18
|
+
deviceManager: DeviceManager | undefined;
|
|
19
|
+
port: number;
|
|
20
|
+
private readonly categories;
|
|
21
|
+
private readonly homekitDevicesById;
|
|
22
|
+
private kasaProcess;
|
|
23
|
+
private platformInitialization;
|
|
19
24
|
constructor(log: Logging, config: PlatformConfig, api: API);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
private createHomekitDevice;
|
|
26
|
+
private initializeCategories;
|
|
27
|
+
initializePlatform(): Promise<void>;
|
|
28
|
+
private logInitializationDetails;
|
|
29
|
+
private verifyEnvironment;
|
|
30
|
+
private didFinishLaunching;
|
|
23
31
|
private checkPython;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* @internal
|
|
28
|
-
*/
|
|
32
|
+
private startKasaApi;
|
|
33
|
+
private stopKasaApi;
|
|
29
34
|
lsc(serviceOrCharacteristic: Service | Characteristic | {
|
|
30
35
|
UUID: string;
|
|
31
36
|
}, characteristic?: Characteristic | {
|
|
32
37
|
UUID: string;
|
|
33
38
|
}): string;
|
|
34
|
-
private createHomekitDevice;
|
|
35
|
-
getCategoryName(category: Categories): string | undefined;
|
|
36
39
|
getServiceName(service: {
|
|
37
40
|
UUID: string;
|
|
38
41
|
}): string | undefined;
|
|
39
42
|
getCharacteristicName(characteristic: WithUUID<{
|
|
40
|
-
name?: string;
|
|
41
|
-
displayName?: string;
|
|
43
|
+
name?: string | null;
|
|
44
|
+
displayName?: string | null;
|
|
42
45
|
}>): string | undefined;
|
|
43
|
-
|
|
44
|
-
* Registers a Homebridge PlatformAccessory.
|
|
45
|
-
*
|
|
46
|
-
* Calls {@link external:homebridge.API#registerPlatformAccessories}
|
|
47
|
-
*/
|
|
46
|
+
getCategoryName(category: Categories): string;
|
|
48
47
|
registerPlatformAccessory(platformAccessory: PlatformAccessory<KasaPythonAccessoryContext>): void;
|
|
49
|
-
/**
|
|
50
|
-
* Function invoked when homebridge tries to restore cached accessory
|
|
51
|
-
*/
|
|
52
48
|
configureAccessory(accessory: PlatformAccessory<KasaPythonAccessoryContext>): void;
|
|
53
|
-
/**
|
|
54
|
-
* Adds a new device.
|
|
55
|
-
*/
|
|
56
49
|
foundDevice(device: KasaDevice): void;
|
|
57
50
|
}
|
package/dist/platform.js
CHANGED
|
@@ -1,182 +1,178 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import getPort from 'get-port';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
4
|
import { satisfies } from 'semver';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
import { parseConfig } from './config.js';
|
|
7
|
-
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
8
|
-
import { lookup, lookupCharacteristicNameByUUID, isObjectLike } from './utils.js';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
9
6
|
import create from './devices/create.js';
|
|
10
7
|
import DeviceManager from './devices/deviceManager.js';
|
|
11
8
|
import PythonChecker from './python/pythonChecker.js';
|
|
9
|
+
import { EnumParser } from './categoriesParse.js';
|
|
10
|
+
import { parseConfig } from './config.js';
|
|
11
|
+
import { runCommand } from './utils.js';
|
|
12
|
+
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
13
|
+
import { isObjectLike, lookup, lookupCharacteristicNameByUUID, prefixLogger } from './utils.js';
|
|
12
14
|
let packageConfig;
|
|
13
|
-
|
|
15
|
+
async function loadPackageConfig(logger) {
|
|
14
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
17
|
const packageConfigPath = path.join(__dirname, '..', 'package.json');
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const log = prefixLogger(logger, '[Package Config]');
|
|
19
|
+
try {
|
|
20
|
+
const packageConfigData = await fs.readFile(packageConfigPath, 'utf8');
|
|
21
|
+
packageConfig = JSON.parse(packageConfigData);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
log.error(`Error reading package.json: ${error}`);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
export default class KasaPythonPlatform {
|
|
24
29
|
log;
|
|
25
30
|
api;
|
|
26
|
-
Service;
|
|
27
31
|
Characteristic;
|
|
28
|
-
config;
|
|
29
32
|
configuredAccessories = new Map();
|
|
30
|
-
|
|
33
|
+
Service;
|
|
31
34
|
storagePath;
|
|
32
35
|
venvPythonExecutable;
|
|
36
|
+
config;
|
|
33
37
|
deviceManager;
|
|
38
|
+
port = 0;
|
|
39
|
+
categories;
|
|
40
|
+
homekitDevicesById = new Map();
|
|
41
|
+
kasaProcess = null;
|
|
42
|
+
platformInitialization;
|
|
34
43
|
constructor(log, config, api) {
|
|
35
44
|
this.log = log;
|
|
36
45
|
this.api = api;
|
|
37
|
-
this.
|
|
46
|
+
this.Service = this.api.hap.Service;
|
|
47
|
+
this.Characteristic = this.api.hap.Characteristic;
|
|
38
48
|
this.storagePath = this.api.user.storagePath();
|
|
39
49
|
this.venvPythonExecutable = path.join(this.storagePath, 'kasa-python', '.venv', 'bin', 'python3');
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
50
|
+
this.config = parseConfig(config);
|
|
51
|
+
this.categories = this.initializeCategories();
|
|
52
|
+
this.platformInitialization = this.initializePlatform().catch((error) => {
|
|
53
|
+
this.log.error('Platform initialization failed:', error);
|
|
54
|
+
});
|
|
55
|
+
this.api.on('didFinishLaunching', async () => {
|
|
56
|
+
await this.platformInitialization;
|
|
57
|
+
await this.didFinishLaunching();
|
|
58
|
+
});
|
|
59
|
+
this.api.on('shutdown', () => {
|
|
60
|
+
this.stopKasaApi();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
createHomekitDevice(kasaDevice) {
|
|
64
|
+
return create(this, kasaDevice);
|
|
65
|
+
}
|
|
66
|
+
initializeCategories() {
|
|
67
|
+
return new EnumParser(this).parse();
|
|
68
|
+
}
|
|
69
|
+
async initializePlatform() {
|
|
70
|
+
await loadPackageConfig(this.log);
|
|
71
|
+
this.logInitializationDetails();
|
|
72
|
+
await this.verifyEnvironment();
|
|
73
|
+
}
|
|
74
|
+
logInitializationDetails() {
|
|
75
|
+
this.log.info(`${packageConfig.name} v${packageConfig.version}, node ${process.version}, ` +
|
|
76
|
+
`homebridge v${this.api.serverVersion}, api v${this.api.version} Initializing...`);
|
|
77
|
+
}
|
|
78
|
+
async verifyEnvironment() {
|
|
42
79
|
if (!satisfies(process.version, packageConfig.engines.node)) {
|
|
43
|
-
this.log.error(
|
|
80
|
+
this.log.error(`Error: not using minimum node version ${packageConfig.engines.node}`);
|
|
44
81
|
}
|
|
45
|
-
|
|
46
|
-
|
|
82
|
+
if (this.api.versionGreaterOrEqual && !this.api.versionGreaterOrEqual('1.8.4')) {
|
|
83
|
+
throw new Error(`homebridge-kasa-python requires homebridge >= 1.8.4. Currently running: ${this.api.serverVersion}`);
|
|
47
84
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
}
|
|
86
|
+
async didFinishLaunching() {
|
|
87
|
+
this.log.debug('Did Finish Launching Event Received');
|
|
88
|
+
try {
|
|
89
|
+
await this.checkPython();
|
|
90
|
+
this.port = await getPort();
|
|
91
|
+
this.deviceManager = new DeviceManager(this);
|
|
92
|
+
await this.startKasaApi();
|
|
93
|
+
await this.deviceManager.discoverDevices();
|
|
52
94
|
}
|
|
53
|
-
|
|
54
|
-
this.log.
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.log.error('An error occurred during startup:', error);
|
|
55
97
|
}
|
|
56
|
-
this.Service = this.api.hap.Service;
|
|
57
|
-
this.Characteristic = this.api.hap.Characteristic;
|
|
58
|
-
this.log.debug('config.json: %j', config);
|
|
59
|
-
this.config = parseConfig(config);
|
|
60
|
-
this.log.debug('config: %j', this.config);
|
|
61
|
-
this.log.info('%s v%s, node %s, homebridge v%s, api v%s Finished Initializing.', packageConfig.name, packageConfig.version, process.version, this.api.serverVersion, this.api.version);
|
|
62
|
-
this.api.on('didFinishLaunching', async () => {
|
|
63
|
-
this.log.info('Did Finish Launching Event Received');
|
|
64
|
-
try {
|
|
65
|
-
await this.checkPython().catch(error => {
|
|
66
|
-
this.log.error('Error checking python environment: %s', error);
|
|
67
|
-
});
|
|
68
|
-
await this.deviceManager.discoverDevices().catch(error => {
|
|
69
|
-
this.log.error('Error discovering devices: %s', error);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
this.log.error('An error occurred during startup: %s', error);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
98
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Function invoked when checking python environment.
|
|
79
|
-
*/
|
|
80
99
|
async checkPython() {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.allInOne(this.config.forceVenvRecreate).catch((error) => {
|
|
84
|
-
this.log.error('Error checking python environment: %s', error);
|
|
85
|
-
});
|
|
86
|
-
this.log.info('Python Checker finished, environment is ready.');
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Return string representation of Service/Characteristic for logging
|
|
90
|
-
*
|
|
91
|
-
* @internal
|
|
92
|
-
*/
|
|
93
|
-
lsc(serviceOrCharacteristic, characteristic) {
|
|
94
|
-
let serviceName;
|
|
95
|
-
let characteristicName;
|
|
96
|
-
if (serviceOrCharacteristic instanceof this.api.hap.Service) {
|
|
97
|
-
serviceName = this.getServiceName(serviceOrCharacteristic);
|
|
98
|
-
}
|
|
99
|
-
else if (serviceOrCharacteristic instanceof this.api.hap.Characteristic ||
|
|
100
|
-
('UUID' in serviceOrCharacteristic &&
|
|
101
|
-
typeof serviceOrCharacteristic.UUID === 'string')) {
|
|
102
|
-
characteristicName = this.getCharacteristicName(serviceOrCharacteristic);
|
|
100
|
+
try {
|
|
101
|
+
await new PythonChecker(this).allInOne();
|
|
103
102
|
}
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.log.error('Error checking python environment:', error);
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
}
|
|
107
|
+
async startKasaApi() {
|
|
108
|
+
const scriptPath = `${this.storagePath}/node_modules/homebridge-kasa-python/dist/python/kasaApi.py`;
|
|
109
|
+
try {
|
|
110
|
+
const [, stderr, , process] = await runCommand(this.log, this.venvPythonExecutable, [scriptPath, this.port.toString()], undefined, true, true, true);
|
|
111
|
+
this.kasaProcess = process;
|
|
112
|
+
if (stderr) {
|
|
113
|
+
this.log.debug(`kasaApi.py process started: ${stderr}`);
|
|
114
|
+
}
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof Error) {
|
|
118
|
+
this.log.error(`Error starting kasaApi.py process: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.log.error('An unknown error occurred during startup');
|
|
122
|
+
}
|
|
112
123
|
}
|
|
113
|
-
return `[${chalk.green(characteristicName)}]`;
|
|
114
124
|
}
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
stopKasaApi() {
|
|
126
|
+
if (this.kasaProcess) {
|
|
127
|
+
this.kasaProcess.kill();
|
|
128
|
+
this.kasaProcess = null;
|
|
129
|
+
this.log.debug('kasaApi.py process terminated');
|
|
130
|
+
}
|
|
117
131
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
lsc(serviceOrCharacteristic, characteristic) {
|
|
133
|
+
const serviceName = serviceOrCharacteristic instanceof this.api.hap.Service
|
|
134
|
+
? this.getServiceName(serviceOrCharacteristic)
|
|
135
|
+
: undefined;
|
|
136
|
+
const characteristicName = characteristic instanceof this.api.hap.Characteristic
|
|
137
|
+
? this.getCharacteristicName(characteristic)
|
|
138
|
+
: serviceOrCharacteristic instanceof this.api.hap.Characteristic || 'UUID' in serviceOrCharacteristic
|
|
139
|
+
? this.getCharacteristicName(serviceOrCharacteristic)
|
|
140
|
+
: undefined;
|
|
141
|
+
return `[${serviceName ? serviceName : ''}` +
|
|
142
|
+
`${serviceName && characteristicName ? '.' : ''}` +
|
|
143
|
+
`${characteristicName ? characteristicName : ''}]`;
|
|
121
144
|
}
|
|
122
145
|
getServiceName(service) {
|
|
123
|
-
return lookup(this.api.hap.Service, (thisKeyValue, value) => isObjectLike(thisKeyValue) &&
|
|
124
|
-
'UUID' in thisKeyValue &&
|
|
125
|
-
thisKeyValue.UUID === value, service.UUID);
|
|
146
|
+
return lookup(this.api.hap.Service, (thisKeyValue, value) => isObjectLike(thisKeyValue) && 'UUID' in thisKeyValue && thisKeyValue.UUID === value, service.UUID);
|
|
126
147
|
}
|
|
127
148
|
getCharacteristicName(characteristic) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
if ('UUID' in characteristic) {
|
|
136
|
-
return lookupCharacteristicNameByUUID(this.api.hap.Characteristic, characteristic.UUID);
|
|
137
|
-
}
|
|
138
|
-
return undefined;
|
|
149
|
+
return characteristic.name ??
|
|
150
|
+
characteristic.displayName ??
|
|
151
|
+
lookupCharacteristicNameByUUID(this.api.hap.Characteristic, characteristic.UUID);
|
|
152
|
+
}
|
|
153
|
+
getCategoryName(category) {
|
|
154
|
+
return this.categories ? this.categories[category] : 'Unknown';
|
|
139
155
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Registers a Homebridge PlatformAccessory.
|
|
142
|
-
*
|
|
143
|
-
* Calls {@link external:homebridge.API#registerPlatformAccessories}
|
|
144
|
-
*/
|
|
145
156
|
registerPlatformAccessory(platformAccessory) {
|
|
146
|
-
this.log.debug(`registerPlatformAccessory(
|
|
147
|
-
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
|
|
148
|
-
|
|
149
|
-
]);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Function invoked when homebridge tries to restore cached accessory
|
|
153
|
-
*/
|
|
157
|
+
this.log.debug(`registerPlatformAccessory([${platformAccessory.displayName}])`);
|
|
158
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [platformAccessory]);
|
|
159
|
+
}
|
|
154
160
|
configureAccessory(accessory) {
|
|
155
|
-
this.log.info(`Configuring cached accessory:
|
|
156
|
-
this.log.debug('%O', accessory.context);
|
|
161
|
+
this.log.info(`Configuring cached accessory: [${accessory.displayName}] UUID: ${accessory.UUID} deviceId: ${accessory.context.deviceId}`);
|
|
157
162
|
this.configuredAccessories.set(accessory.UUID, accessory);
|
|
158
163
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Adds a new device.
|
|
161
|
-
*/
|
|
162
164
|
foundDevice(device) {
|
|
163
|
-
const deviceId = device
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const deviceType = device.sys_info.mic_type;
|
|
167
|
-
if (deviceId === null || deviceId.length === 0) {
|
|
168
|
-
this.log.error('Missing deviceId: %s', deviceHost);
|
|
165
|
+
const { sys_info: { deviceId }, alias: deviceAlias, host: deviceHost, sys_info: { mic_type: deviceType } } = device;
|
|
166
|
+
if (!deviceId) {
|
|
167
|
+
this.log.error('Missing deviceId:', deviceHost);
|
|
169
168
|
return;
|
|
170
169
|
}
|
|
171
|
-
if (this.homekitDevicesById.
|
|
172
|
-
this.log.info(`Device already added:
|
|
170
|
+
if (this.homekitDevicesById.has(deviceId)) {
|
|
171
|
+
this.log.info(`Device already added: [${deviceAlias}] ${deviceType} [${deviceId}]`);
|
|
173
172
|
return;
|
|
174
173
|
}
|
|
175
|
-
this.log.info(`Adding:
|
|
176
|
-
this.
|
|
177
|
-
const uuid = this.api.hap.uuid.generate(deviceId);
|
|
178
|
-
const accessory = this.configuredAccessories.get(uuid);
|
|
179
|
-
this.homekitDevicesById.set(deviceId, this.createHomekitDevice(accessory, device));
|
|
174
|
+
this.log.info(`Adding: [${deviceAlias}] ${deviceType} [${deviceId}]`);
|
|
175
|
+
this.homekitDevicesById.set(deviceId, this.createHomekitDevice(device));
|
|
180
176
|
}
|
|
181
177
|
}
|
|
182
178
|
//# sourceMappingURL=platform.js.map
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import asyncio, eventlet, eventlet.wsgi, json, sys
|
|
2
|
+
from flask import Flask, request, jsonify
|
|
3
|
+
from flask_socketio import SocketIO
|
|
4
|
+
from kasa import Discover, Device
|
|
5
|
+
|
|
6
|
+
app = Flask(__name__)
|
|
7
|
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
|
8
|
+
|
|
9
|
+
device_cache = {}
|
|
10
|
+
|
|
11
|
+
def custom_device_serializer(device):
|
|
12
|
+
def is_serializable(value):
|
|
13
|
+
try:
|
|
14
|
+
json.dumps(value)
|
|
15
|
+
return True
|
|
16
|
+
except TypeError:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
return {attr: getattr(device, attr) for attr in dir(device)
|
|
20
|
+
if not attr.startswith("_") and not callable(getattr(device, attr))
|
|
21
|
+
and not asyncio.iscoroutine(getattr(device, attr))
|
|
22
|
+
and is_serializable(getattr(device, attr))}
|
|
23
|
+
|
|
24
|
+
async def discover_devices():
|
|
25
|
+
devices = await Discover.discover()
|
|
26
|
+
all_device_info = {}
|
|
27
|
+
tasks = []
|
|
28
|
+
for ip, dev in devices.items():
|
|
29
|
+
tasks.append(update_device_info(ip, dev))
|
|
30
|
+
results = await asyncio.gather(*tasks)
|
|
31
|
+
for ip, info in results:
|
|
32
|
+
all_device_info[ip] = info
|
|
33
|
+
return all_device_info
|
|
34
|
+
|
|
35
|
+
async def update_device_info(ip, dev: Device):
|
|
36
|
+
await dev.update()
|
|
37
|
+
device_info = custom_device_serializer(dev)
|
|
38
|
+
device_config = dev.config.to_dict()
|
|
39
|
+
device_cache[ip] = {
|
|
40
|
+
"device_info": device_info,
|
|
41
|
+
"device_config": device_config
|
|
42
|
+
}
|
|
43
|
+
return ip, device_cache[ip]
|
|
44
|
+
|
|
45
|
+
async def get_device_info(device_config):
|
|
46
|
+
dev = await Device.connect(config=Device.Config.from_dict(device_config))
|
|
47
|
+
try:
|
|
48
|
+
device_info = custom_device_serializer(dev)
|
|
49
|
+
return {"device_info": device_info}
|
|
50
|
+
finally:
|
|
51
|
+
await dev.disconnect()
|
|
52
|
+
|
|
53
|
+
async def control_device(device_config, action, child_num=None):
|
|
54
|
+
kasa_device = await Device.connect(config=Device.Config.from_dict(device_config))
|
|
55
|
+
try:
|
|
56
|
+
if child_num is not None:
|
|
57
|
+
child = kasa_device.children[child_num]
|
|
58
|
+
await getattr(child, action)()
|
|
59
|
+
else:
|
|
60
|
+
await getattr(kasa_device, action)()
|
|
61
|
+
return {"status": "success", f"is_{action.split('_')[1]}": True}
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return {"status": "error", "message": str(e)}
|
|
64
|
+
finally:
|
|
65
|
+
await kasa_device.disconnect()
|
|
66
|
+
|
|
67
|
+
def run_async(func, *args):
|
|
68
|
+
loop = asyncio.get_event_loop()
|
|
69
|
+
return loop.run_until_complete(func(*args))
|
|
70
|
+
|
|
71
|
+
@app.route('/discover', methods=['GET'])
|
|
72
|
+
def discover():
|
|
73
|
+
try:
|
|
74
|
+
devices_info = run_async(discover_devices)
|
|
75
|
+
return jsonify(devices_info)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
app.logger.error(f"Error in /discover: {str(e)}")
|
|
78
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
79
|
+
|
|
80
|
+
@app.route('/getSysInfo', methods=['POST'])
|
|
81
|
+
def get_sys_info_route():
|
|
82
|
+
try:
|
|
83
|
+
data = request.json
|
|
84
|
+
app.logger.debug(f"Request data: {data}")
|
|
85
|
+
device_config = data['device_config']
|
|
86
|
+
device_info = run_async(get_device_info, device_config)
|
|
87
|
+
return jsonify(device_info)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
app.logger.error(f"Error in /getSysInfo: {str(e)}")
|
|
90
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
91
|
+
|
|
92
|
+
@app.route('/controlDevice', methods=['POST'])
|
|
93
|
+
def control_device_route():
|
|
94
|
+
try:
|
|
95
|
+
data = request.json
|
|
96
|
+
app.logger.debug(f"Request data: {data}")
|
|
97
|
+
device_config = data['device_config']
|
|
98
|
+
action = data['action']
|
|
99
|
+
child_num = data.get('child_num')
|
|
100
|
+
result = run_async(control_device, device_config, action, child_num)
|
|
101
|
+
return jsonify(result)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
app.logger.error(f"Error in /controlDevice: {str(e)}")
|
|
104
|
+
return jsonify({"status": "error", "message": str(e)}), 500
|
|
105
|
+
|
|
106
|
+
if __name__ == '__main__':
|
|
107
|
+
port = int(sys.argv[1])
|
|
108
|
+
print(f"Starting server on port {port}")
|
|
109
|
+
eventlet.wsgi.server(eventlet.listen(('127.0.0.1', port)), app)
|
|
@@ -9,8 +9,8 @@ declare class PythonChecker {
|
|
|
9
9
|
private readonly venvPythonExecutable;
|
|
10
10
|
private readonly venvConfigPath;
|
|
11
11
|
private readonly requirementsPath;
|
|
12
|
-
constructor(platform: KasaPythonPlatform
|
|
13
|
-
allInOne(
|
|
12
|
+
constructor(platform: KasaPythonPlatform);
|
|
13
|
+
allInOne(): Promise<void>;
|
|
14
14
|
private ensurePluginDir;
|
|
15
15
|
private ensurePythonVersion;
|
|
16
16
|
private ensureVenvCreated;
|