homebridge-midea-platform 1.2.6-beta.3 → 1.2.6-beta.5
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 +87 -155
- 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;
|
|
@@ -99,6 +79,7 @@ class Logger {
|
|
|
99
79
|
*/
|
|
100
80
|
class UiServer extends HomebridgePluginUiServer {
|
|
101
81
|
cloud;
|
|
82
|
+
smartHomeCloud;
|
|
102
83
|
promiseSocket;
|
|
103
84
|
security;
|
|
104
85
|
logger;
|
|
@@ -107,113 +88,77 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
107
88
|
constructor() {
|
|
108
89
|
super();
|
|
109
90
|
// Obtain the plugin configuration from homebridge config JSON file.
|
|
110
|
-
const config = require(this.homebridgeConfigPath).platforms.find(
|
|
111
|
-
(obj) => obj.platform === "midea-platform",
|
|
112
|
-
);
|
|
91
|
+
const config = require(this.homebridgeConfigPath).platforms.find((obj) => obj.platform === 'midea-platform');
|
|
113
92
|
this.logger = new Logger(config?.uiDebug ?? false);
|
|
114
|
-
this.logger.info(
|
|
93
|
+
this.logger.info('Custom UI created.');
|
|
115
94
|
this.logger.debug(`ENV:\n${JSON.stringify(process.env, null, 2)}`);
|
|
116
95
|
this.security = new LocalSecurity();
|
|
117
|
-
this.promiseSocket = new PromiseSocket(
|
|
118
|
-
this.logger,
|
|
119
|
-
config?.uiDebug ?? false,
|
|
120
|
-
);
|
|
96
|
+
this.promiseSocket = new PromiseSocket(this.logger, config?.uiDebug ?? false);
|
|
121
97
|
|
|
122
|
-
this.onRequest(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
password = Buffer.from(
|
|
133
|
-
(DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16),
|
|
134
|
-
"hex",
|
|
135
|
-
).toString("ascii");
|
|
136
|
-
}
|
|
137
|
-
this.cloud = CloudFactory.createCloud(
|
|
138
|
-
username,
|
|
139
|
-
password,
|
|
140
|
-
"NetHome Plus",
|
|
141
|
-
);
|
|
142
|
-
if (username && password) {
|
|
143
|
-
await this.cloud.login();
|
|
144
|
-
}
|
|
145
|
-
} catch (e) {
|
|
146
|
-
const msg = e instanceof Error ? e.stack : e;
|
|
147
|
-
this.logger.warn(`Login failed:\n${msg}`);
|
|
148
|
-
throw new RequestError(
|
|
149
|
-
"Login failed! Check the logs for more information.",
|
|
150
|
-
);
|
|
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();
|
|
151
108
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
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
|
+
});
|
|
154
115
|
|
|
155
|
-
this.onRequest(
|
|
116
|
+
this.onRequest('/mergeToDefault', async ({ config }) => {
|
|
156
117
|
_.defaultsDeep(config, defaultConfig);
|
|
157
|
-
config.devices
|
|
118
|
+
for (const device of config.devices) {
|
|
158
119
|
_.defaultsDeep(device, defaultDeviceConfig);
|
|
159
|
-
}
|
|
120
|
+
}
|
|
160
121
|
this.config = config;
|
|
161
122
|
this.logger.setDebugEnabled(config.uiDebug ? config.uiDebug : false);
|
|
162
123
|
this.logger.debug(`Merged config:\n${JSON.stringify(config, null, 2)}`);
|
|
163
124
|
return config;
|
|
164
125
|
});
|
|
165
126
|
|
|
166
|
-
this.onRequest(
|
|
127
|
+
this.onRequest('/getDefaults', async () => {
|
|
167
128
|
return {
|
|
168
129
|
defaultConfig,
|
|
169
130
|
defaultDeviceConfig,
|
|
170
131
|
};
|
|
171
132
|
});
|
|
172
133
|
|
|
173
|
-
this.onRequest(
|
|
134
|
+
this.onRequest('/discover', async ({ ip }) => {
|
|
174
135
|
try {
|
|
175
136
|
const devices = await this.blockingDiscover(ip);
|
|
176
137
|
for (const device of devices) {
|
|
177
138
|
if (device.version === ProtocolVersion.V3 && this.cloud.loggedIn) {
|
|
178
139
|
await this.getNewCredentials(device);
|
|
179
140
|
} else {
|
|
180
|
-
device.token =
|
|
181
|
-
device.key =
|
|
141
|
+
device.token = '';
|
|
142
|
+
device.key = '';
|
|
182
143
|
}
|
|
183
144
|
}
|
|
184
145
|
this.logger.debug(`All devices:\n${JSON.stringify(devices, null, 2)}`);
|
|
185
|
-
return devices
|
|
186
|
-
.filter((a) => Object.keys(a).length > 0)
|
|
187
|
-
.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));
|
|
188
147
|
} catch (e) {
|
|
189
148
|
const msg = e instanceof Error ? e.stack : e;
|
|
190
149
|
throw new RequestError(`Device discovery failed:\n${msg}`);
|
|
191
150
|
}
|
|
192
151
|
});
|
|
193
152
|
|
|
194
|
-
this.onRequest(
|
|
153
|
+
this.onRequest('/downloadLua', async ({ deviceType, deviceSn }) => {
|
|
195
154
|
try {
|
|
196
|
-
if (!this.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const username = Buffer.from(
|
|
202
|
-
(DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[1]).toString(16),
|
|
203
|
-
"hex",
|
|
204
|
-
).toString("ascii");
|
|
205
|
-
const password = Buffer.from(
|
|
206
|
-
(DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[2]).toString(16),
|
|
207
|
-
"hex",
|
|
208
|
-
).toString("ascii");
|
|
209
|
-
this.cloud = CloudFactory.createCloud(
|
|
210
|
-
username,
|
|
211
|
-
password,
|
|
212
|
-
"Midea SmartHome (MSmartHome)",
|
|
213
|
-
);
|
|
214
|
-
await this.cloud.login();
|
|
155
|
+
if (!this.smartHomeCloud) {
|
|
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)');
|
|
159
|
+
await this.smartHomeCloud.login();
|
|
215
160
|
}
|
|
216
|
-
const lua = await this.
|
|
161
|
+
const lua = await this.smartHomeCloud.getProtocolLua(deviceType, deviceSn);
|
|
217
162
|
return lua;
|
|
218
163
|
} catch (e) {
|
|
219
164
|
const msg = e instanceof Error ? e.stack : e;
|
|
@@ -241,24 +186,20 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
241
186
|
const endianess = i === 0 ? Endianness.Little : Endianness.Big;
|
|
242
187
|
try {
|
|
243
188
|
const [token, key] = await this.cloud.getTokenKey(device.id, endianess);
|
|
244
|
-
device.token = token ? token.toString(
|
|
245
|
-
device.key = key ? key.toString(
|
|
189
|
+
device.token = token ? token.toString('hex') : undefined;
|
|
190
|
+
device.key = key ? key.toString('hex') : undefined;
|
|
246
191
|
await this.authenticate(device);
|
|
247
192
|
connected = true;
|
|
248
193
|
} catch (e) {
|
|
249
194
|
//const msg = e instanceof Error ? e.stack : e;
|
|
250
|
-
this.logger.debug(
|
|
251
|
-
`[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`,
|
|
252
|
-
);
|
|
195
|
+
this.logger.debug(`[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`);
|
|
253
196
|
// if failed then reset token/key
|
|
254
197
|
device.token = undefined;
|
|
255
198
|
device.key = undefined;
|
|
256
199
|
}
|
|
257
200
|
i++;
|
|
258
201
|
}
|
|
259
|
-
this.logger.debug(
|
|
260
|
-
`[${device.name}] Token: ${device.token}, Key: ${device.key}`,
|
|
261
|
-
);
|
|
202
|
+
this.logger.debug(`[${device.name}] Token: ${device.token}, Key: ${device.key}`);
|
|
262
203
|
return;
|
|
263
204
|
}
|
|
264
205
|
|
|
@@ -274,10 +215,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
274
215
|
// Wrap next block in try/finally so we can destroy the socket if error occurs
|
|
275
216
|
// let thrown errors cascade up.
|
|
276
217
|
try {
|
|
277
|
-
const request = this.security.encode_8370(
|
|
278
|
-
Buffer.from(device.token, "hex"),
|
|
279
|
-
TCPMessageType.HANDSHAKE_REQUEST,
|
|
280
|
-
);
|
|
218
|
+
const request = this.security.encode_8370(Buffer.from(device.token, 'hex'), TCPMessageType.HANDSHAKE_REQUEST);
|
|
281
219
|
await this.promiseSocket.write(request);
|
|
282
220
|
const response = await this.promiseSocket.read();
|
|
283
221
|
if (response) {
|
|
@@ -287,16 +225,12 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
287
225
|
response.length
|
|
288
226
|
})\n${JSON.stringify(response)}`,
|
|
289
227
|
);
|
|
290
|
-
throw Error(
|
|
291
|
-
`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`,
|
|
292
|
-
);
|
|
228
|
+
throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`);
|
|
293
229
|
}
|
|
294
230
|
const resp = response.subarray(8, 72);
|
|
295
|
-
this.security.tcp_key_from_resp(resp, Buffer.from(device.key,
|
|
231
|
+
this.security.tcp_key_from_resp(resp, Buffer.from(device.key, 'hex'));
|
|
296
232
|
} else {
|
|
297
|
-
throw Error(
|
|
298
|
-
`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`,
|
|
299
|
-
);
|
|
233
|
+
throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`);
|
|
300
234
|
}
|
|
301
235
|
} finally {
|
|
302
236
|
this.promiseSocket.destroy();
|
|
@@ -309,44 +243,42 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
309
243
|
* for each as discovered.
|
|
310
244
|
*/
|
|
311
245
|
async blockingDiscover(ipAddrs = undefined) {
|
|
312
|
-
|
|
313
|
-
this.logger.debug(
|
|
314
|
-
`[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`,
|
|
315
|
-
);
|
|
246
|
+
const devices = [];
|
|
247
|
+
this.logger.debug(`[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`);
|
|
316
248
|
const discover = new Discover(this.logger);
|
|
317
249
|
return new Promise((resolve, reject) => {
|
|
318
|
-
this.logger.info(
|
|
319
|
-
this.pushEvent(
|
|
250
|
+
this.logger.info('Start device discovery...');
|
|
251
|
+
this.pushEvent('showToast', {
|
|
320
252
|
success: true,
|
|
321
|
-
msg:
|
|
253
|
+
msg: 'Start device discovery',
|
|
322
254
|
});
|
|
323
255
|
// If IP addresses provided then probe them directly
|
|
324
|
-
ipAddrs
|
|
256
|
+
for (const address of ipAddrs) {
|
|
325
257
|
discover.discoverDeviceByIP(ip);
|
|
326
|
-
}
|
|
258
|
+
}
|
|
327
259
|
// And then send broadcast to network(s)
|
|
328
260
|
discover.startDiscover();
|
|
329
261
|
|
|
330
|
-
discover.on(
|
|
331
|
-
device.displayName = DeviceTypeToName[device.type] ??
|
|
262
|
+
discover.on('device', async (device) => {
|
|
263
|
+
device.displayName = DeviceTypeToName[device.type] ?? 'Unknown';
|
|
332
264
|
devices.push(device);
|
|
333
265
|
// too verbose to post every device as found...
|
|
334
266
|
// this.pushEvent('showToast', { success: true, msg: `Discovered ${device.name} at ${device.ip}`, device: device });
|
|
335
267
|
});
|
|
336
268
|
|
|
337
|
-
discover.on(
|
|
338
|
-
this.logger.info(
|
|
339
|
-
this.pushEvent(
|
|
269
|
+
discover.on('retry', (nTry, nDevices) => {
|
|
270
|
+
this.logger.info('Device discovery complete.');
|
|
271
|
+
this.pushEvent('showToast', {
|
|
340
272
|
success: true,
|
|
341
273
|
msg: `Continuing to search for devices (${nDevices} found)`,
|
|
342
274
|
});
|
|
343
275
|
});
|
|
344
276
|
|
|
345
|
-
discover.on(
|
|
346
|
-
this.logger.info(
|
|
347
|
-
this.pushEvent(
|
|
277
|
+
discover.on('complete', () => {
|
|
278
|
+
this.logger.info('Device discovery complete.');
|
|
279
|
+
this.pushEvent('showToast', {
|
|
348
280
|
success: true,
|
|
349
|
-
msg:
|
|
281
|
+
msg: 'Discovery complete',
|
|
350
282
|
});
|
|
351
283
|
resolve(devices);
|
|
352
284
|
});
|
package/package.json
CHANGED