homebridge-midea-platform 1.2.1 → 1.2.2

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.
@@ -7,41 +7,57 @@
7
7
  * Based on https://github.com/homebridge/plugin-ui-utils
8
8
  *
9
9
  */
10
- const { HomebridgePluginUiServer, RequestError } = require('@homebridge/plugin-ui-utils');
11
- const Discover = require('../dist/core/MideaDiscover.js').default;
12
- const CloudFactory = require('../dist/core/MideaCloud.js').default;
13
- const { DeviceType, TCPMessageType, ProtocolVersion, Endianness } = require('../dist/core/MideaConstants.js');
14
- const { LocalSecurity, ProxiedSecurity } = require('../dist/core/MideaSecurity.js');
15
- const { PromiseSocket } = require('../dist/core/MideaUtils.js');
16
- const { defaultConfig, defaultDeviceConfig } = require('../dist/platformUtils.js');
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
+ DeviceType,
18
+ TCPMessageType,
19
+ ProtocolVersion,
20
+ Endianness,
21
+ } from "../dist/core/MideaConstants.js";
22
+ import { LocalSecurity, ProxiedSecurity } 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";
26
+ const require = createRequire(import.meta.url);
27
+
28
+ import _ from "lodash";
17
29
 
18
30
  const DEFAULT_ACCOUNT = [
19
- BigInt('39182118275972017797890111985649342047468653967530949796945843010512'),
20
- BigInt('29406100301096535908214728322278519471982973450672552249652548883645'),
21
- BigInt('39182118275972017797890111985649342050088014265865102175083010656997'),
31
+ BigInt(
32
+ "39182118275972017797890111985649342047468653967530949796945843010512",
33
+ ),
34
+ BigInt(
35
+ "29406100301096535908214728322278519471982973450672552249652548883645",
36
+ ),
37
+ BigInt(
38
+ "39182118275972017797890111985649342050088014265865102175083010656997",
39
+ ),
22
40
  ];
23
41
 
24
- var _ = require('lodash');
25
-
26
42
  /*********************************************************************
27
43
  * Logger
28
44
  * Lightweight log class to mimic the homebridge log capability
29
45
  */
30
46
  class Logger {
31
47
  _debug;
32
- _Reset = '\x1b[0m';
33
- _Bright = '\x1b[1m';
34
- _Dim = '\x1b[2m';
48
+ _Reset = "\x1b[0m";
49
+ _Bright = "\x1b[1m";
50
+ _Dim = "\x1b[2m";
35
51
 
36
- _FgBlack = '\x1b[30m';
37
- _FgRed = '\x1b[31m';
38
- _FgGreen = '\x1b[32m';
39
- _FgYellow = '\x1b[33m';
40
- _FgBlue = '\x1b[34m';
41
- _FgMagenta = '\x1b[35m';
42
- _FgCyan = '\x1b[36m';
43
- _FgWhite = '\x1b[37m';
44
- _FgGray = '\x1b[90m';
52
+ _FgBlack = "\x1b[30m";
53
+ _FgRed = "\x1b[31m";
54
+ _FgGreen = "\x1b[32m";
55
+ _FgYellow = "\x1b[33m";
56
+ _FgBlue = "\x1b[34m";
57
+ _FgMagenta = "\x1b[35m";
58
+ _FgCyan = "\x1b[36m";
59
+ _FgWhite = "\x1b[37m";
60
+ _FgGray = "\x1b[90m";
45
61
 
46
62
  constructor(uiDebug = false) {
47
63
  this._debug = uiDebug;
@@ -79,33 +95,53 @@ class UiServer extends HomebridgePluginUiServer {
79
95
  constructor() {
80
96
  super();
81
97
  // Obtain the plugin configuration from homebridge config JSON file.
82
- const config = require(this.homebridgeConfigPath).platforms.find((obj) => obj.platform === 'midea-platform');
98
+ const config = require(this.homebridgeConfigPath).platforms.find(
99
+ (obj) => obj.platform === "midea-platform",
100
+ );
83
101
  this.logger = new Logger(config?.uiDebug ?? false);
84
- this.logger.info('Custom UI created.');
102
+ this.logger.info("Custom UI created.");
85
103
  this.logger.debug(`ENV:\n${JSON.stringify(process.env, null, 2)}`);
86
104
  this.security = new LocalSecurity();
87
- this.promiseSocket = new PromiseSocket(this.logger, config?.uiDebug ?? false);
105
+ this.promiseSocket = new PromiseSocket(
106
+ this.logger,
107
+ config?.uiDebug ?? false,
108
+ );
88
109
 
89
- this.onRequest('/login', async ({ username, password, registeredApp, useDefaultProfile }) => {
90
- try {
91
- if (useDefaultProfile) {
92
- this.logger.debug('Using default profile.');
93
- registeredApp = 'Midea SmartHome (MSmartHome)';
94
- username = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16), 'hex').toString('ascii');
95
- password = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16), 'hex').toString('ascii');
96
- }
97
- this.cloud = CloudFactory.createCloud(username, password, registeredApp);
98
- if (username && password && registeredApp) {
99
- await this.cloud.login();
110
+ this.onRequest(
111
+ "/login",
112
+ async ({ username, password, registeredApp, useDefaultProfile }) => {
113
+ try {
114
+ if (useDefaultProfile) {
115
+ this.logger.debug("Using default profile.");
116
+ registeredApp = "Midea SmartHome (MSmartHome)";
117
+ username = Buffer.from(
118
+ (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16),
119
+ "hex",
120
+ ).toString("ascii");
121
+ password = Buffer.from(
122
+ (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16),
123
+ "hex",
124
+ ).toString("ascii");
125
+ }
126
+ this.cloud = CloudFactory.createCloud(
127
+ username,
128
+ password,
129
+ registeredApp,
130
+ );
131
+ if (username && password && registeredApp) {
132
+ await this.cloud.login();
133
+ }
134
+ } catch (e) {
135
+ const msg = e instanceof Error ? e.stack : e;
136
+ this.logger.warn(`Login failed:\n${msg}`);
137
+ throw new RequestError(
138
+ "Login failed! Check the logs for more information.",
139
+ );
100
140
  }
101
- } catch (e) {
102
- const msg = e instanceof Error ? e.stack : e;
103
- this.logger.warn(`Login failed:\n${msg}`);
104
- throw new RequestError('Login failed! Check the logs for more information.');
105
- }
106
- });
141
+ },
142
+ );
107
143
 
108
- this.onRequest('/mergeToDefault', async ({ config }) => {
144
+ this.onRequest("/mergeToDefault", async ({ config }) => {
109
145
  _.defaultsDeep(config, defaultConfig);
110
146
  config.devices.forEach((device) => {
111
147
  _.defaultsDeep(device, defaultDeviceConfig);
@@ -116,42 +152,57 @@ class UiServer extends HomebridgePluginUiServer {
116
152
  return config;
117
153
  });
118
154
 
119
- this.onRequest('/getDefaults', async () => {
155
+ this.onRequest("/getDefaults", async () => {
120
156
  return {
121
157
  defaultConfig,
122
158
  defaultDeviceConfig,
123
159
  };
124
160
  });
125
161
 
126
- this.onRequest('/discover', async ({ ip }) => {
162
+ this.onRequest("/discover", async ({ ip }) => {
127
163
  try {
128
164
  const devices = await this.blockingDiscover(ip);
129
165
  for (const device of devices) {
130
166
  if (device.version === ProtocolVersion.V3 && this.cloud.loggedIn) {
131
167
  await this.getNewCredentials(device);
132
168
  } else {
133
- device.token = '';
134
- device.key = '';
169
+ device.token = "";
170
+ device.key = "";
135
171
  }
136
172
  }
137
173
  this.logger.debug(`All devices:\n${JSON.stringify(devices, null, 2)}`);
138
- return devices.filter((a) => Object.keys(a).length > 0).sort((a, b) => a.ip.localeCompare(b.ip));
174
+ return devices
175
+ .filter((a) => Object.keys(a).length > 0)
176
+ .sort((a, b) => a.ip.localeCompare(b.ip));
139
177
  } catch (e) {
140
178
  const msg = e instanceof Error ? e.stack : e;
141
179
  throw new RequestError(`Device discovery failed:\n${msg}`);
142
180
  }
143
181
  });
144
182
 
145
- this.onRequest('/downloadLua', async ({ deviceType, deviceSn }) => {
183
+ this.onRequest("/downloadLua", async ({ deviceType, deviceSn }) => {
146
184
  try {
147
185
  if (!this.cloud || !(this.cloud instanceof ProxiedSecurity)) {
148
- this.pushEvent('showToast', { success: true, msg: 'Currently used cloud provider doesn\'t support Lua downloading, using the default profile now...' });
149
- const registeredApp = 'Midea SmartHome (MSmartHome)';
150
- const username = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16), 'hex').toString('ascii');
151
- const password = Buffer.from((DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16), 'hex').toString('ascii');
152
- this.cloud = CloudFactory.createCloud(username, password, registeredApp);
186
+ this.pushEvent("showToast", {
187
+ success: true,
188
+ msg: "Currently used cloud provider doesn't support Lua downloading, using the default profile now...",
189
+ });
190
+ const registeredApp = "Midea SmartHome (MSmartHome)";
191
+ const username = Buffer.from(
192
+ (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[1]).toString(16),
193
+ "hex",
194
+ ).toString("ascii");
195
+ const password = Buffer.from(
196
+ (DEFAULT_ACCOUNT[0] ^ DEFAULT_ACCOUNT[2]).toString(16),
197
+ "hex",
198
+ ).toString("ascii");
199
+ this.cloud = CloudFactory.createCloud(
200
+ username,
201
+ password,
202
+ registeredApp,
203
+ );
153
204
  await this.cloud.login();
154
- }
205
+ }
155
206
  const lua = await this.cloud.getProtocolLua(deviceType, deviceSn);
156
207
  return lua;
157
208
  } catch (e) {
@@ -180,20 +231,24 @@ class UiServer extends HomebridgePluginUiServer {
180
231
  const endianess = i === 0 ? Endianness.Little : Endianness.Big;
181
232
  try {
182
233
  const [token, key] = await this.cloud.getTokenKey(device.id, endianess);
183
- device.token = token ? token.toString('hex') : undefined;
184
- device.key = key ? key.toString('hex') : undefined;
234
+ device.token = token ? token.toString("hex") : undefined;
235
+ device.key = key ? key.toString("hex") : undefined;
185
236
  await this.authenticate(device);
186
237
  connected = true;
187
238
  } catch (e) {
188
239
  //const msg = e instanceof Error ? e.stack : e;
189
- this.logger.debug(`[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`);
240
+ this.logger.debug(
241
+ `[${device.name}] Getting token and key with ${endianess}-endian is not successful.\n${e}`,
242
+ );
190
243
  // if failed then reset token/key
191
244
  device.token = undefined;
192
245
  device.key = undefined;
193
246
  }
194
247
  i++;
195
248
  }
196
- this.logger.debug(`[${device.name}] Token: ${device.token}, Key: ${device.key}`);
249
+ this.logger.debug(
250
+ `[${device.name}] Token: ${device.token}, Key: ${device.key}`,
251
+ );
197
252
  return;
198
253
  }
199
254
 
@@ -209,7 +264,10 @@ class UiServer extends HomebridgePluginUiServer {
209
264
  // Wrap next block in try/finally so we can destroy the socket if error occurs
210
265
  // let thrown errors cascade up.
211
266
  try {
212
- const request = this.security.encode_8370(Buffer.from(device.token, 'hex'), TCPMessageType.HANDSHAKE_REQUEST);
267
+ const request = this.security.encode_8370(
268
+ Buffer.from(device.token, "hex"),
269
+ TCPMessageType.HANDSHAKE_REQUEST,
270
+ );
213
271
  await this.promiseSocket.write(request);
214
272
  const response = await this.promiseSocket.read();
215
273
  if (response) {
@@ -219,12 +277,16 @@ class UiServer extends HomebridgePluginUiServer {
219
277
  response.length
220
278
  })\n${JSON.stringify(response)}`,
221
279
  );
222
- throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`);
280
+ throw Error(
281
+ `[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}. (Data length mismatch)`,
282
+ );
223
283
  }
224
284
  const resp = response.subarray(8, 72);
225
- this.security.tcp_key_from_resp(resp, Buffer.from(device.key, 'hex'));
285
+ this.security.tcp_key_from_resp(resp, Buffer.from(device.key, "hex"));
226
286
  } else {
227
- throw Error(`[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`);
287
+ throw Error(
288
+ `[${device.name}] Authenticate error when receiving data from ${device.ip}:${device.port}.`,
289
+ );
228
290
  }
229
291
  } finally {
230
292
  this.promiseSocket.destroy();
@@ -238,11 +300,16 @@ class UiServer extends HomebridgePluginUiServer {
238
300
  */
239
301
  async blockingDiscover(ipAddrs = undefined) {
240
302
  let devices = [];
241
- this.logger.debug(`[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`);
303
+ this.logger.debug(
304
+ `[blockingDiscover] IP addresses: ${JSON.stringify(ipAddrs)}`,
305
+ );
242
306
  const discover = new Discover(this.logger);
243
307
  return new Promise((resolve, reject) => {
244
- this.logger.info('Start device discovery...');
245
- this.pushEvent('showToast', { success: true, msg: 'Start device discovery' });
308
+ this.logger.info("Start device discovery...");
309
+ this.pushEvent("showToast", {
310
+ success: true,
311
+ msg: "Start device discovery",
312
+ });
246
313
  // If IP addresses provided then probe them directly
247
314
  ipAddrs?.forEach((ip) => {
248
315
  discover.discoverDeviceByIP(ip);
@@ -250,49 +317,55 @@ class UiServer extends HomebridgePluginUiServer {
250
317
  // And then send broadcast to network(s)
251
318
  discover.startDiscover();
252
319
 
253
- discover.on('device', async (device) => {
320
+ discover.on("device", async (device) => {
254
321
  switch (device.type) {
255
- case DeviceType.AIR_CONDITIONER:
256
- device.displayName = 'Air Conditioner';
257
- break;
258
- case DeviceType.DEHUMIDIFIER:
259
- device.displayName = 'Dehumidifier';
260
- break;
261
- case DeviceType.HEAT_PUMP_WIFI_CONTROLLER:
262
- device.displayName = 'Heat Pump WiFi Controller';
263
- case DeviceType.FRONT_LOAD_WASHER:
264
- device.displayName = 'Front Load Washer';
265
- break;
266
- case DeviceType.DISHWASHER:
267
- device.displayName = 'Dishwasher';
268
- break;
269
- case DeviceType.ELECTRIC_WATER_HEATER:
270
- device.displayName = 'Electric Water Heater';
271
- break;
272
- case DeviceType.GAS_WATER_HEATER:
273
- device.displayName = 'Gas Water Heater';
274
- break;
275
- case DeviceType.FAN:
276
- device.displayName = 'Fan';
277
- break;
278
- case DeviceType.UNKNOWN:
279
- default:
280
- device.displayName = 'Unknown';
281
- break;
322
+ case DeviceType.AIR_CONDITIONER:
323
+ device.displayName = "Air Conditioner";
324
+ break;
325
+ case DeviceType.DEHUMIDIFIER:
326
+ device.displayName = "Dehumidifier";
327
+ break;
328
+ case DeviceType.HEAT_PUMP_WIFI_CONTROLLER:
329
+ device.displayName = "Heat Pump WiFi Controller";
330
+ case DeviceType.FRONT_LOAD_WASHER:
331
+ device.displayName = "Front Load Washer";
332
+ break;
333
+ case DeviceType.DISHWASHER:
334
+ device.displayName = "Dishwasher";
335
+ break;
336
+ case DeviceType.ELECTRIC_WATER_HEATER:
337
+ device.displayName = "Electric Water Heater";
338
+ break;
339
+ case DeviceType.GAS_WATER_HEATER:
340
+ device.displayName = "Gas Water Heater";
341
+ break;
342
+ case DeviceType.FAN:
343
+ device.displayName = "Fan";
344
+ break;
345
+ case DeviceType.UNKNOWN:
346
+ default:
347
+ device.displayName = "Unknown";
348
+ break;
282
349
  }
283
350
  devices.push(device);
284
351
  // too verbose to post every device as found...
285
352
  // this.pushEvent('showToast', { success: true, msg: `Discovered ${device.name} at ${device.ip}`, device: device });
286
353
  });
287
354
 
288
- discover.on('retry', (nTry, nDevices) => {
289
- this.logger.info('Device discovery complete.');
290
- this.pushEvent('showToast', { success: true, msg: `Continuing to search for devices (${nDevices} found)` });
355
+ discover.on("retry", (nTry, nDevices) => {
356
+ this.logger.info("Device discovery complete.");
357
+ this.pushEvent("showToast", {
358
+ success: true,
359
+ msg: `Continuing to search for devices (${nDevices} found)`,
360
+ });
291
361
  });
292
362
 
293
- discover.on('complete', () => {
294
- this.logger.info('Device discovery complete.');
295
- this.pushEvent('showToast', { success: true, msg: 'Discovery complete' });
363
+ discover.on("complete", () => {
364
+ this.logger.info("Device discovery complete.");
365
+ this.pushEvent("showToast", {
366
+ success: true,
367
+ msg: "Discovery complete",
368
+ });
296
369
  resolve(devices);
297
370
  });
298
371
  });
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.1",
5
+ "version": "1.2.2",
6
6
  "description": "Homebridge plugin for Midea devices",
7
7
  "license": "Apache-2.0",
8
8
  "repository": {