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 CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "files": {
9
9
  "ignoreUnknown": false,
10
- "ignore": ["dist/**", ".vscode/**", ".github/**", "node_modules/**", "homebridge-ui/**"]
10
+ "ignore": ["dist/**", ".vscode/**", ".github/**", "node_modules/**"]
11
11
  },
12
12
  "formatter": {
13
13
  "enabled": true,
@@ -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
- const url = window.URL.createObjectURL(blob);
294
- a.href = url
295
- a.click();
296
- window.URL.revokeObjectURL(url);
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
  }
@@ -7,49 +7,29 @@
7
7
  * Based on https://github.com/homebridge/plugin-ui-utils
8
8
  *
9
9
  */
10
- import {
11
- HomebridgePluginUiServer,
12
- RequestError,
13
- } from "@homebridge/plugin-ui-utils";
14
- import Discover from "../dist/core/MideaDiscover.js";
15
- import CloudFactory from "../dist/core/MideaCloud.js";
16
- import {
17
- DeviceTypeToName,
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 "lodash";
20
+ import _ from 'lodash';
29
21
 
30
22
  const DEFAULT_ACCOUNT = [
31
- BigInt(
32
- "41136566961777418441619689108052131385308997994436615360276316597550126349990",
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
- "4270685954756226103292057380984602745528999549492916172461797725852782444008"
45
- ),
46
- BigInt(
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 = "\x1b[0m";
61
- _Bright = "\x1b[1m";
62
- _Dim = "\x1b[2m";
40
+ _Reset = '\x1b[0m';
41
+ _Bright = '\x1b[1m';
42
+ _Dim = '\x1b[2m';
63
43
 
64
- _FgBlack = "\x1b[30m";
65
- _FgRed = "\x1b[31m";
66
- _FgGreen = "\x1b[32m";
67
- _FgYellow = "\x1b[33m";
68
- _FgBlue = "\x1b[34m";
69
- _FgMagenta = "\x1b[35m";
70
- _FgCyan = "\x1b[36m";
71
- _FgWhite = "\x1b[37m";
72
- _FgGray = "\x1b[90m";
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("Custom UI created.");
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
- "/login",
124
- async ({ username, password, useDefaultProfile }) => {
125
- try {
126
- if (useDefaultProfile) {
127
- this.logger.debug("Using default profile.");
128
- username = Buffer.from(
129
- (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16),
130
- "hex",
131
- ).toString("ascii");
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("/mergeToDefault", async ({ config }) => {
116
+ this.onRequest('/mergeToDefault', async ({ config }) => {
156
117
  _.defaultsDeep(config, defaultConfig);
157
- config.devices.forEach((device) => {
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("/getDefaults", async () => {
127
+ this.onRequest('/getDefaults', async () => {
167
128
  return {
168
129
  defaultConfig,
169
130
  defaultDeviceConfig,
170
131
  };
171
132
  });
172
133
 
173
- this.onRequest("/discover", async ({ ip }) => {
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("/downloadLua", async ({ deviceType, deviceSn }) => {
153
+ this.onRequest('/downloadLua', async ({ deviceType, deviceSn }) => {
195
154
  try {
196
- if (!this.cloud || !this.cloud.isProxied()) {
197
- this.pushEvent("showToast", {
198
- success: true,
199
- msg: "Currently used cloud provider doesn't support Lua downloading, using the default profile now...",
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.cloud.getProtocolLua(deviceType, deviceSn);
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("hex") : undefined;
245
- device.key = key ? key.toString("hex") : undefined;
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, "hex"));
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
- let devices = [];
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("Start device discovery...");
319
- this.pushEvent("showToast", {
250
+ this.logger.info('Start device discovery...');
251
+ this.pushEvent('showToast', {
320
252
  success: true,
321
- msg: "Start device discovery",
253
+ msg: 'Start device discovery',
322
254
  });
323
255
  // If IP addresses provided then probe them directly
324
- ipAddrs?.forEach((ip) => {
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("device", async (device) => {
331
- device.displayName = DeviceTypeToName[device.type] ?? "Unknown";
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("retry", (nTry, nDevices) => {
338
- this.logger.info("Device discovery complete.");
339
- this.pushEvent("showToast", {
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("complete", () => {
346
- this.logger.info("Device discovery complete.");
347
- this.pushEvent("showToast", {
277
+ discover.on('complete', () => {
278
+ this.logger.info('Device discovery complete.');
279
+ this.pushEvent('showToast', {
348
280
  success: true,
349
- msg: "Discovery complete",
281
+ msg: 'Discovery complete',
350
282
  });
351
283
  resolve(devices);
352
284
  });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "displayName": "Homebridge Midea Platform",
3
3
  "name": "homebridge-midea-platform",
4
4
  "type": "module",
5
- "version": "1.2.6-beta.3",
5
+ "version": "1.2.6-beta.5",
6
6
  "description": "Homebridge plugin for Midea devices",
7
7
  "license": "Apache-2.0",
8
8
  "repository": {