homebridge-midea-platform 1.2.6-beta.4 → 1.2.6-beta.6
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/biome.json +1 -1
- package/homebridge-ui/public/index.html +18 -6
- package/homebridge-ui/server.js +83 -148
- package/package.json +1 -1
package/biome.json
CHANGED
|
@@ -287,13 +287,25 @@
|
|
|
287
287
|
download_btn.addEventListener('click', async () => {
|
|
288
288
|
try {
|
|
289
289
|
const file_data_str = await homebridge.request('/downloadLua', { deviceType: device.type, deviceSn: device.sn });
|
|
290
|
-
const a = document.createElement('a');
|
|
291
|
-
a.setAttribute('download', `${device.type.toString(16)}_${device.model}.lua`);
|
|
292
290
|
const blob = new Blob([file_data_str], { type: 'text/plain' });
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
|
|
292
|
+
const url = URL.createObjectURL(blob);
|
|
293
|
+
|
|
294
|
+
const a = document.createElement('a');
|
|
295
|
+
a.href = url;
|
|
296
|
+
a.download = `${device.type.toString(16)}_${device.model}.lua`;
|
|
297
|
+
|
|
298
|
+
document.body.appendChild(a);
|
|
299
|
+
|
|
300
|
+
a.dispatchEvent(
|
|
301
|
+
new MouseEvent('click', {
|
|
302
|
+
bubbles: true,
|
|
303
|
+
cancelable: true,
|
|
304
|
+
view: window
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
document.body.removeChild(a);
|
|
297
309
|
} catch (e) {
|
|
298
310
|
homebridge.toast.error(e.message);
|
|
299
311
|
}
|
package/homebridge-ui/server.js
CHANGED
|
@@ -7,49 +7,29 @@
|
|
|
7
7
|
* Based on https://github.com/homebridge/plugin-ui-utils
|
|
8
8
|
*
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from
|
|
14
|
-
import Discover from
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
TCPMessageType,
|
|
19
|
-
ProtocolVersion,
|
|
20
|
-
Endianness,
|
|
21
|
-
} from "../dist/core/MideaConstants.js";
|
|
22
|
-
import { LocalSecurity } from "../dist/core/MideaSecurity.js";
|
|
23
|
-
import { PromiseSocket } from "../dist/core/MideaUtils.js";
|
|
24
|
-
import { defaultConfig, defaultDeviceConfig } from "../dist/platformUtils.js";
|
|
25
|
-
import { createRequire } from "node:module";
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-utils';
|
|
12
|
+
import CloudFactory from '../dist/core/MideaCloud.js';
|
|
13
|
+
import { DeviceTypeToName, Endianness, ProtocolVersion, TCPMessageType } from '../dist/core/MideaConstants.js';
|
|
14
|
+
import Discover from '../dist/core/MideaDiscover.js';
|
|
15
|
+
import { LocalSecurity } from '../dist/core/MideaSecurity.js';
|
|
16
|
+
import { PromiseSocket } from '../dist/core/MideaUtils.js';
|
|
17
|
+
import { defaultConfig, defaultDeviceConfig } from '../dist/platformUtils.js';
|
|
26
18
|
const require = createRequire(import.meta.url);
|
|
27
19
|
|
|
28
|
-
import _ from
|
|
20
|
+
import _ from 'lodash';
|
|
29
21
|
|
|
30
22
|
const DEFAULT_ACCOUNT = [
|
|
31
|
-
BigInt(
|
|
32
|
-
|
|
33
|
-
),
|
|
34
|
-
BigInt(
|
|
35
|
-
"41136566961777418205521495345904086238221761646585049169700858993146668339659",
|
|
36
|
-
),
|
|
37
|
-
BigInt(
|
|
38
|
-
"41136566961777418441619689108052131385308997994436615362339979365072738212503",
|
|
39
|
-
),
|
|
23
|
+
BigInt('41136566961777418441619689108052131385308997994436615360276316597550126349990'),
|
|
24
|
+
BigInt('41136566961777418205521495345904086238221761646585049169700858993146668339659'),
|
|
25
|
+
BigInt('41136566961777418441619689108052131385308997994436615362339979365072738212503'),
|
|
40
26
|
];
|
|
41
27
|
|
|
42
28
|
const DEFAULT_SMARTHOME_ACCOUNT = [
|
|
43
|
-
BigInt(
|
|
44
|
-
|
|
45
|
-
),
|
|
46
|
-
|
|
47
|
-
"4270685954756226103289246300068351442953216461967596801287597164299361014405"
|
|
48
|
-
),
|
|
49
|
-
BigInt(
|
|
50
|
-
"4270685954756226103292057380984602757738380835485182656166614575373678037465"
|
|
51
|
-
),
|
|
52
|
-
]
|
|
29
|
+
BigInt('4270685954756226103292057380984602745528999549492916172461797725852782444008'),
|
|
30
|
+
BigInt('4270685954756226103289246300068351442953216461967596801287597164299361014405'),
|
|
31
|
+
BigInt('4270685954756226103292057380984602757738380835485182656166614575373678037465'),
|
|
32
|
+
];
|
|
53
33
|
|
|
54
34
|
/*********************************************************************
|
|
55
35
|
* Logger
|
|
@@ -57,19 +37,19 @@ const DEFAULT_SMARTHOME_ACCOUNT = [
|
|
|
57
37
|
*/
|
|
58
38
|
class Logger {
|
|
59
39
|
_debug;
|
|
60
|
-
_Reset =
|
|
61
|
-
_Bright =
|
|
62
|
-
_Dim =
|
|
40
|
+
_Reset = '\x1b[0m';
|
|
41
|
+
_Bright = '\x1b[1m';
|
|
42
|
+
_Dim = '\x1b[2m';
|
|
63
43
|
|
|
64
|
-
_FgBlack =
|
|
65
|
-
_FgRed =
|
|
66
|
-
_FgGreen =
|
|
67
|
-
_FgYellow =
|
|
68
|
-
_FgBlue =
|
|
69
|
-
_FgMagenta =
|
|
70
|
-
_FgCyan =
|
|
71
|
-
_FgWhite =
|
|
72
|
-
_FgGray =
|
|
44
|
+
_FgBlack = '\x1b[30m';
|
|
45
|
+
_FgRed = '\x1b[31m';
|
|
46
|
+
_FgGreen = '\x1b[32m';
|
|
47
|
+
_FgYellow = '\x1b[33m';
|
|
48
|
+
_FgBlue = '\x1b[34m';
|
|
49
|
+
_FgMagenta = '\x1b[35m';
|
|
50
|
+
_FgCyan = '\x1b[36m';
|
|
51
|
+
_FgWhite = '\x1b[37m';
|
|
52
|
+
_FgGray = '\x1b[90m';
|
|
73
53
|
|
|
74
54
|
constructor(uiDebug = false) {
|
|
75
55
|
this._debug = uiDebug;
|
|
@@ -108,106 +88,74 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
108
88
|
constructor() {
|
|
109
89
|
super();
|
|
110
90
|
// Obtain the plugin configuration from homebridge config JSON file.
|
|
111
|
-
const config = require(this.homebridgeConfigPath).platforms.find(
|
|
112
|
-
(obj) => obj.platform === "midea-platform",
|
|
113
|
-
);
|
|
91
|
+
const config = require(this.homebridgeConfigPath).platforms.find((obj) => obj.platform === 'midea-platform');
|
|
114
92
|
this.logger = new Logger(config?.uiDebug ?? false);
|
|
115
|
-
this.logger.info(
|
|
93
|
+
this.logger.info('Custom UI created.');
|
|
116
94
|
this.logger.debug(`ENV:\n${JSON.stringify(process.env, null, 2)}`);
|
|
117
95
|
this.security = new LocalSecurity();
|
|
118
|
-
this.promiseSocket = new PromiseSocket(
|
|
119
|
-
this.logger,
|
|
120
|
-
config?.uiDebug ?? false,
|
|
121
|
-
);
|
|
96
|
+
this.promiseSocket = new PromiseSocket(this.logger, config?.uiDebug ?? false);
|
|
122
97
|
|
|
123
|
-
this.onRequest(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
password = Buffer.from(
|
|
134
|
-
(DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16),
|
|
135
|
-
"hex",
|
|
136
|
-
).toString("ascii");
|
|
137
|
-
}
|
|
138
|
-
this.cloud = CloudFactory.createCloud(
|
|
139
|
-
username,
|
|
140
|
-
password,
|
|
141
|
-
"NetHome Plus",
|
|
142
|
-
);
|
|
143
|
-
if (username && password) {
|
|
144
|
-
await this.cloud.login();
|
|
145
|
-
}
|
|
146
|
-
} catch (e) {
|
|
147
|
-
const msg = e instanceof Error ? e.stack : e;
|
|
148
|
-
this.logger.warn(`Login failed:\n${msg}`);
|
|
149
|
-
throw new RequestError(
|
|
150
|
-
"Login failed! Check the logs for more information.",
|
|
151
|
-
);
|
|
98
|
+
this.onRequest('/login', async ({ username, password, useDefaultProfile }) => {
|
|
99
|
+
try {
|
|
100
|
+
if (useDefaultProfile) {
|
|
101
|
+
this.logger.debug('Using default profile.');
|
|
102
|
+
username = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16), 'hex').toString('ascii');
|
|
103
|
+
password = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16), 'hex').toString('ascii');
|
|
104
|
+
}
|
|
105
|
+
this.cloud = CloudFactory.createCloud(username, password, 'NetHome Plus');
|
|
106
|
+
if (username && password) {
|
|
107
|
+
await this.cloud.login();
|
|
152
108
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
109
|
+
} catch (e) {
|
|
110
|
+
const msg = e instanceof Error ? e.stack : e;
|
|
111
|
+
this.logger.warn(`Login failed:\n${msg}`);
|
|
112
|
+
throw new RequestError('Login failed! Check the logs for more information.');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
155
115
|
|
|
156
|
-
this.onRequest(
|
|
116
|
+
this.onRequest('/mergeToDefault', async ({ config }) => {
|
|
157
117
|
_.defaultsDeep(config, defaultConfig);
|
|
158
|
-
config.devices
|
|
118
|
+
for (const device of config.devices) {
|
|
159
119
|
_.defaultsDeep(device, defaultDeviceConfig);
|
|
160
|
-
}
|
|
120
|
+
}
|
|
161
121
|
this.config = config;
|
|
162
122
|
this.logger.setDebugEnabled(config.uiDebug ? config.uiDebug : false);
|
|
163
123
|
this.logger.debug(`Merged config:\n${JSON.stringify(config, null, 2)}`);
|
|
164
124
|
return config;
|
|
165
125
|
});
|
|
166
126
|
|
|
167
|
-
this.onRequest(
|
|
127
|
+
this.onRequest('/getDefaults', async () => {
|
|
168
128
|
return {
|
|
169
129
|
defaultConfig,
|
|
170
130
|
defaultDeviceConfig,
|
|
171
131
|
};
|
|
172
132
|
});
|
|
173
133
|
|
|
174
|
-
this.onRequest(
|
|
134
|
+
this.onRequest('/discover', async ({ ip }) => {
|
|
175
135
|
try {
|
|
176
136
|
const devices = await this.blockingDiscover(ip);
|
|
177
137
|
for (const device of devices) {
|
|
178
138
|
if (device.version === ProtocolVersion.V3 && this.cloud.loggedIn) {
|
|
179
139
|
await this.getNewCredentials(device);
|
|
180
140
|
} else {
|
|
181
|
-
device.token =
|
|
182
|
-
device.key =
|
|
141
|
+
device.token = '';
|
|
142
|
+
device.key = '';
|
|
183
143
|
}
|
|
184
144
|
}
|
|
185
145
|
this.logger.debug(`All devices:\n${JSON.stringify(devices, null, 2)}`);
|
|
186
|
-
return devices
|
|
187
|
-
.filter((a) => Object.keys(a).length > 0)
|
|
188
|
-
.sort((a, b) => a.ip.localeCompare(b.ip));
|
|
146
|
+
return devices.filter((a) => Object.keys(a).length > 0).sort((a, b) => a.ip.localeCompare(b.ip));
|
|
189
147
|
} catch (e) {
|
|
190
148
|
const msg = e instanceof Error ? e.stack : e;
|
|
191
149
|
throw new RequestError(`Device discovery failed:\n${msg}`);
|
|
192
150
|
}
|
|
193
151
|
});
|
|
194
152
|
|
|
195
|
-
this.onRequest(
|
|
153
|
+
this.onRequest('/downloadLua', async ({ deviceType, deviceSn }) => {
|
|
196
154
|
try {
|
|
197
155
|
if (!this.smartHomeCloud) {
|
|
198
|
-
const username = Buffer.from(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
).toString("ascii");
|
|
202
|
-
const password = Buffer.from(
|
|
203
|
-
(DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[2]).toString(16),
|
|
204
|
-
"hex",
|
|
205
|
-
).toString("ascii");
|
|
206
|
-
this.smartHomeCloud = CloudFactory.createCloud(
|
|
207
|
-
username,
|
|
208
|
-
password,
|
|
209
|
-
"Midea SmartHome (MSmartHome)",
|
|
210
|
-
);
|
|
156
|
+
const username = Buffer.from((DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[1]).toString(16), 'hex').toString('ascii');
|
|
157
|
+
const password = Buffer.from((DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[2]).toString(16), 'hex').toString('ascii');
|
|
158
|
+
this.smartHomeCloud = CloudFactory.createCloud(username, password, 'Midea SmartHome (MSmartHome)');
|
|
211
159
|
await this.smartHomeCloud.login();
|
|
212
160
|
}
|
|
213
161
|
const lua = await this.smartHomeCloud.getProtocolLua(deviceType, deviceSn);
|
|
@@ -238,24 +186,20 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
238
186
|
const endianess = i === 0 ? Endianness.Little : Endianness.Big;
|
|
239
187
|
try {
|
|
240
188
|
const [token, key] = await this.cloud.getTokenKey(device.id, endianess);
|
|
241
|
-
device.token = token ? token.toString(
|
|
242
|
-
device.key = key ? key.toString(
|
|
189
|
+
device.token = token ? token.toString('hex') : undefined;
|
|
190
|
+
device.key = key ? key.toString('hex') : undefined;
|
|
243
191
|
await this.authenticate(device);
|
|
244
192
|
connected = true;
|
|
245
193
|
} catch (e) {
|
|
246
194
|
//const msg = e instanceof Error ? e.stack : e;
|
|
247
|
-
this.logger.debug(
|
|
248
|
-
`[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`,
|
|
249
|
-
);
|
|
195
|
+
this.logger.debug(`[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`);
|
|
250
196
|
// if failed then reset token/key
|
|
251
197
|
device.token = undefined;
|
|
252
198
|
device.key = undefined;
|
|
253
199
|
}
|
|
254
200
|
i++;
|
|
255
201
|
}
|
|
256
|
-
this.logger.debug(
|
|
257
|
-
`[${device.name}] Token: ${device.token}, Key: ${device.key}`,
|
|
258
|
-
);
|
|
202
|
+
this.logger.debug(`[${device.name}] Token: ${device.token}, Key: ${device.key}`);
|
|
259
203
|
return;
|
|
260
204
|
}
|
|
261
205
|
|
|
@@ -271,10 +215,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
271
215
|
// Wrap next block in try/finally so we can destroy the socket if error occurs
|
|
272
216
|
// let thrown errors cascade up.
|
|
273
217
|
try {
|
|
274
|
-
const request = this.security.encode_8370(
|
|
275
|
-
Buffer.from(device.token, "hex"),
|
|
276
|
-
TCPMessageType.HANDSHAKE_REQUEST,
|
|
277
|
-
);
|
|
218
|
+
const request = this.security.encode_8370(Buffer.from(device.token, 'hex'), TCPMessageType.HANDSHAKE_REQUEST);
|
|
278
219
|
await this.promiseSocket.write(request);
|
|
279
220
|
const response = await this.promiseSocket.read();
|
|
280
221
|
if (response) {
|
|
@@ -284,16 +225,12 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
284
225
|
response.length
|
|
285
226
|
})\n${JSON.stringify(response)}`,
|
|
286
227
|
);
|
|
287
|
-
throw Error(
|
|
288
|
-
`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`,
|
|
289
|
-
);
|
|
228
|
+
throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`);
|
|
290
229
|
}
|
|
291
230
|
const resp = response.subarray(8, 72);
|
|
292
|
-
this.security.tcp_key_from_resp(resp, Buffer.from(device.key,
|
|
231
|
+
this.security.tcp_key_from_resp(resp, Buffer.from(device.key, 'hex'));
|
|
293
232
|
} else {
|
|
294
|
-
throw Error(
|
|
295
|
-
`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`,
|
|
296
|
-
);
|
|
233
|
+
throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`);
|
|
297
234
|
}
|
|
298
235
|
} finally {
|
|
299
236
|
this.promiseSocket.destroy();
|
|
@@ -306,44 +243,42 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
306
243
|
* for each as discovered.
|
|
307
244
|
*/
|
|
308
245
|
async blockingDiscover(ipAddrs = undefined) {
|
|
309
|
-
|
|
310
|
-
this.logger.debug(
|
|
311
|
-
`[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`,
|
|
312
|
-
);
|
|
246
|
+
const devices = [];
|
|
247
|
+
this.logger.debug(`[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`);
|
|
313
248
|
const discover = new Discover(this.logger);
|
|
314
249
|
return new Promise((resolve, reject) => {
|
|
315
|
-
this.logger.info(
|
|
316
|
-
this.pushEvent(
|
|
250
|
+
this.logger.info('Start device discovery...');
|
|
251
|
+
this.pushEvent('showToast', {
|
|
317
252
|
success: true,
|
|
318
|
-
msg:
|
|
253
|
+
msg: 'Start device discovery',
|
|
319
254
|
});
|
|
320
255
|
// If IP addresses provided then probe them directly
|
|
321
|
-
|
|
256
|
+
for (const ip of ipAddrs) {
|
|
322
257
|
discover.discoverDeviceByIP(ip);
|
|
323
|
-
}
|
|
258
|
+
}
|
|
324
259
|
// And then send broadcast to network(s)
|
|
325
260
|
discover.startDiscover();
|
|
326
261
|
|
|
327
|
-
discover.on(
|
|
328
|
-
device.displayName = DeviceTypeToName[device.type] ??
|
|
262
|
+
discover.on('device', async (device) => {
|
|
263
|
+
device.displayName = DeviceTypeToName[device.type] ?? 'Unknown';
|
|
329
264
|
devices.push(device);
|
|
330
265
|
// too verbose to post every device as found...
|
|
331
266
|
// this.pushEvent('showToast', { success: true, msg: `Discovered ${device.name} at ${device.ip}`, device: device });
|
|
332
267
|
});
|
|
333
268
|
|
|
334
|
-
discover.on(
|
|
335
|
-
this.logger.info(
|
|
336
|
-
this.pushEvent(
|
|
269
|
+
discover.on('retry', (nTry, nDevices) => {
|
|
270
|
+
this.logger.info('Device discovery complete.');
|
|
271
|
+
this.pushEvent('showToast', {
|
|
337
272
|
success: true,
|
|
338
273
|
msg: `Continuing to search for devices (${nDevices} found)`,
|
|
339
274
|
});
|
|
340
275
|
});
|
|
341
276
|
|
|
342
|
-
discover.on(
|
|
343
|
-
this.logger.info(
|
|
344
|
-
this.pushEvent(
|
|
277
|
+
discover.on('complete', () => {
|
|
278
|
+
this.logger.info('Device discovery complete.');
|
|
279
|
+
this.pushEvent('showToast', {
|
|
345
280
|
success: true,
|
|
346
|
-
msg:
|
|
281
|
+
msg: 'Discovery complete',
|
|
347
282
|
});
|
|
348
283
|
resolve(devices);
|
|
349
284
|
});
|
package/package.json
CHANGED