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 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;
@@ -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("Custom UI created.");
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
- "/login",
125
- async ({ username, password, useDefaultProfile }) => {
126
- try {
127
- if (useDefaultProfile) {
128
- this.logger.debug("Using default profile.");
129
- username = Buffer.from(
130
- (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16),
131
- "hex",
132
- ).toString("ascii");
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("/mergeToDefault", async ({ config }) => {
116
+ this.onRequest('/mergeToDefault', async ({ config }) => {
157
117
  _.defaultsDeep(config, defaultConfig);
158
- config.devices.forEach((device) => {
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("/getDefaults", async () => {
127
+ this.onRequest('/getDefaults', async () => {
168
128
  return {
169
129
  defaultConfig,
170
130
  defaultDeviceConfig,
171
131
  };
172
132
  });
173
133
 
174
- this.onRequest("/discover", async ({ ip }) => {
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("/downloadLua", async ({ deviceType, deviceSn }) => {
153
+ this.onRequest('/downloadLua', async ({ deviceType, deviceSn }) => {
196
154
  try {
197
155
  if (!this.smartHomeCloud) {
198
- const username = Buffer.from(
199
- (DEFAULT_SMARTHOME_ACCOUNT[0] ^ DEFAULT_SMARTHOME_ACCOUNT[1]).toString(16),
200
- "hex",
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("hex") : undefined;
242
- device.key = key ? key.toString("hex") : undefined;
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, "hex"));
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
- let devices = [];
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("Start device discovery...");
316
- this.pushEvent("showToast", {
250
+ this.logger.info('Start device discovery...');
251
+ this.pushEvent('showToast', {
317
252
  success: true,
318
- msg: "Start device discovery",
253
+ msg: 'Start device discovery',
319
254
  });
320
255
  // If IP addresses provided then probe them directly
321
- ipAddrs?.forEach((ip) => {
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("device", async (device) => {
328
- device.displayName = DeviceTypeToName[device.type] ?? "Unknown";
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("retry", (nTry, nDevices) => {
335
- this.logger.info("Device discovery complete.");
336
- this.pushEvent("showToast", {
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("complete", () => {
343
- this.logger.info("Device discovery complete.");
344
- this.pushEvent("showToast", {
277
+ discover.on('complete', () => {
278
+ this.logger.info('Device discovery complete.');
279
+ this.pushEvent('showToast', {
345
280
  success: true,
346
- msg: "Discovery complete",
281
+ msg: 'Discovery complete',
347
282
  });
348
283
  resolve(devices);
349
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.4",
5
+ "version": "1.2.6-beta.6",
6
6
  "description": "Homebridge plugin for Midea devices",
7
7
  "license": "Apache-2.0",
8
8
  "repository": {