homebridge-midea-platform 1.2.0-beta.8 → 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.
- package/CHANGELOG.md +5 -2
- package/README.md +2 -2
- package/dist/accessory/AccessoryFactory.d.ts +14 -15
- package/dist/accessory/AccessoryFactory.js +34 -40
- package/dist/accessory/AccessoryFactory.js.map +1 -1
- package/dist/accessory/AirConditionerAccessory.d.ts +98 -99
- package/dist/accessory/AirConditionerAccessory.js +666 -662
- package/dist/accessory/AirConditionerAccessory.js.map +1 -1
- package/dist/accessory/BaseAccessory.d.ts +11 -12
- package/dist/accessory/BaseAccessory.js +21 -21
- package/dist/accessory/BaseAccessory.js.map +1 -1
- package/dist/accessory/DehumidifierAccessory.d.ts +45 -46
- package/dist/accessory/DehumidifierAccessory.js +343 -344
- package/dist/accessory/DehumidifierAccessory.js.map +1 -1
- package/dist/accessory/DishwasherAccessory.d.ts +30 -31
- package/dist/accessory/DishwasherAccessory.js +59 -63
- package/dist/accessory/DishwasherAccessory.js.map +1 -1
- package/dist/accessory/ElectricWaterHeaterAccessory.d.ts +44 -45
- package/dist/accessory/ElectricWaterHeaterAccessory.js +172 -176
- package/dist/accessory/ElectricWaterHeaterAccessory.js.map +1 -1
- package/dist/accessory/FanAccessory.d.ts +39 -40
- package/dist/accessory/FanAccessory.js +120 -123
- package/dist/accessory/FanAccessory.js.map +1 -1
- package/dist/accessory/FrontLoadWasherAccessory.d.ts +30 -31
- package/dist/accessory/FrontLoadWasherAccessory.js +63 -66
- package/dist/accessory/FrontLoadWasherAccessory.js.map +1 -1
- package/dist/accessory/GasWaterHeaterAccessory.d.ts +51 -52
- package/dist/accessory/GasWaterHeaterAccessory.js +217 -216
- package/dist/accessory/GasWaterHeaterAccessory.js.map +1 -1
- package/dist/accessory/{HeatPumpWifiControllerAccessory.d.ts → HeatPumpWiFiControllerAccessory.d.ts} +34 -35
- package/dist/accessory/{HeatPumpWifiControllerAccessory.js → HeatPumpWiFiControllerAccessory.js} +55 -36
- package/dist/accessory/HeatPumpWiFiControllerAccessory.js.map +1 -0
- package/dist/core/MideaCloud.d.ts +34 -36
- package/dist/core/MideaCloud.js +349 -350
- package/dist/core/MideaCloud.js.map +1 -1
- package/dist/core/MideaConstants.d.ts +51 -52
- package/dist/core/MideaConstants.js +56 -59
- package/dist/core/MideaConstants.js.map +1 -1
- package/dist/core/MideaDevice.d.ts +75 -78
- package/dist/core/MideaDevice.js +430 -420
- package/dist/core/MideaDevice.js.map +1 -1
- package/dist/core/MideaDiscover.d.ts +34 -36
- package/dist/core/MideaDiscover.js +208 -212
- package/dist/core/MideaDiscover.js.map +1 -1
- package/dist/core/MideaMessage.d.ts +75 -76
- package/dist/core/MideaMessage.js +185 -184
- package/dist/core/MideaMessage.js.map +1 -1
- package/dist/core/MideaPacketBuilder.d.ts +9 -11
- package/dist/core/MideaPacketBuilder.js +60 -60
- package/dist/core/MideaPacketBuilder.js.map +1 -1
- package/dist/core/MideaSecurity.d.ts +62 -64
- package/dist/core/MideaSecurity.js +241 -251
- package/dist/core/MideaSecurity.js.map +1 -1
- package/dist/core/MideaUtils.d.ts +31 -33
- package/dist/core/MideaUtils.js +178 -181
- package/dist/core/MideaUtils.js.map +1 -1
- package/dist/devices/DeviceFactory.d.ts +14 -15
- package/dist/devices/DeviceFactory.js +34 -40
- package/dist/devices/DeviceFactory.js.map +1 -1
- package/dist/devices/a1/MideaA1Device.d.ts +75 -77
- package/dist/devices/a1/MideaA1Device.js +142 -145
- package/dist/devices/a1/MideaA1Device.js.map +1 -1
- package/dist/devices/a1/MideaA1Message.d.ts +39 -41
- package/dist/devices/a1/MideaA1Message.js +219 -198
- package/dist/devices/a1/MideaA1Message.js.map +1 -1
- package/dist/devices/ac/MideaACDevice.d.ts +105 -107
- package/dist/devices/ac/MideaACDevice.js +417 -400
- package/dist/devices/ac/MideaACDevice.js.map +1 -1
- package/dist/devices/ac/MideaACMessage.d.ts +97 -96
- package/dist/devices/ac/MideaACMessage.js +724 -621
- package/dist/devices/ac/MideaACMessage.js.map +1 -1
- package/dist/devices/c3/MideaC3Device.d.ts +73 -75
- package/dist/devices/c3/MideaC3Device.js +249 -255
- package/dist/devices/c3/MideaC3Device.js.map +1 -1
- package/dist/devices/c3/MideaC3Message.d.ts +81 -80
- package/dist/devices/c3/MideaC3Message.js +190 -152
- package/dist/devices/c3/MideaC3Message.js.map +1 -1
- package/dist/devices/db/MideaDBDevice.d.ts +28 -30
- package/dist/devices/db/MideaDBDevice.js +94 -100
- package/dist/devices/db/MideaDBDevice.js.map +1 -1
- package/dist/devices/db/MideaDBMessage.d.ts +31 -33
- package/dist/devices/db/MideaDBMessage.js +101 -101
- package/dist/devices/db/MideaDBMessage.js.map +1 -1
- package/dist/devices/e1/MideaE1Device.d.ts +55 -57
- package/dist/devices/e1/MideaE1Device.js +122 -128
- package/dist/devices/e1/MideaE1Device.js.map +1 -1
- package/dist/devices/e1/MideaE1Message.d.ts +27 -29
- package/dist/devices/e1/MideaE1Message.js +128 -107
- package/dist/devices/e1/MideaE1Message.js.map +1 -1
- package/dist/devices/e2/MideaE2Device.d.ts +43 -45
- package/dist/devices/e2/MideaE2Device.js +124 -129
- package/dist/devices/e2/MideaE2Device.js.map +1 -1
- package/dist/devices/e2/MideaE2Message.d.ts +34 -34
- package/dist/devices/e2/MideaE2Message.js +143 -132
- package/dist/devices/e2/MideaE2Message.js.map +1 -1
- package/dist/devices/e3/MideaE3Device.d.ts +42 -44
- package/dist/devices/e3/MideaE3Device.js +132 -137
- package/dist/devices/e3/MideaE3Device.js.map +1 -1
- package/dist/devices/e3/MideaE3Message.d.ts +51 -52
- package/dist/devices/e3/MideaE3Message.js +144 -136
- package/dist/devices/e3/MideaE3Message.js.map +1 -1
- package/dist/devices/fa/MideaFADevice.d.ts +35 -37
- package/dist/devices/fa/MideaFADevice.js +102 -106
- package/dist/devices/fa/MideaFADevice.js.map +1 -1
- package/dist/devices/fa/MideaFAMessage.d.ts +38 -39
- package/dist/devices/fa/MideaFAMessage.js +108 -98
- package/dist/devices/fa/MideaFAMessage.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +61 -61
- package/dist/platform.js +232 -212
- package/dist/platform.js.map +1 -1
- package/dist/platformUtils.d.ts +116 -117
- package/dist/platformUtils.js +107 -110
- package/dist/platformUtils.js.map +1 -1
- package/dist/settings.d.ts +8 -9
- package/dist/settings.js +8 -11
- package/dist/settings.js.map +1 -1
- package/docs/download_lua.md +9 -0
- package/eslint.config.js +43 -0
- package/homebridge-ui/public/js/bootstrap.min.js +1179 -1
- package/homebridge-ui/server.js +157 -84
- package/package.json +21 -31
- package/.eslintignore +0 -3
- package/.husky/pre-commit +0 -5
- package/.prettierrc +0 -19
- package/dist/accessory/AccessoryFactory.d.ts.map +0 -1
- package/dist/accessory/AirConditionerAccessory.d.ts.map +0 -1
- package/dist/accessory/BaseAccessory.d.ts.map +0 -1
- package/dist/accessory/DehumidifierAccessory.d.ts.map +0 -1
- package/dist/accessory/DishwasherAccessory.d.ts.map +0 -1
- package/dist/accessory/ElectricWaterHeaterAccessory.d.ts.map +0 -1
- package/dist/accessory/FanAccessory.d.ts.map +0 -1
- package/dist/accessory/FrontLoadWasherAccessory.d.ts.map +0 -1
- package/dist/accessory/GasWaterHeaterAccessory.d.ts.map +0 -1
- package/dist/accessory/HeatPumpWifiControllerAccessory.d.ts.map +0 -1
- package/dist/accessory/HeatPumpWifiControllerAccessory.js.map +0 -1
- package/dist/core/MideaCloud.d.ts.map +0 -1
- package/dist/core/MideaConstants.d.ts.map +0 -1
- package/dist/core/MideaDevice.d.ts.map +0 -1
- package/dist/core/MideaDiscover.d.ts.map +0 -1
- package/dist/core/MideaMessage.d.ts.map +0 -1
- package/dist/core/MideaPacketBuilder.d.ts.map +0 -1
- package/dist/core/MideaSecurity.d.ts.map +0 -1
- package/dist/core/MideaUtils.d.ts.map +0 -1
- package/dist/devices/DeviceFactory.d.ts.map +0 -1
- package/dist/devices/a1/MideaA1Device.d.ts.map +0 -1
- package/dist/devices/a1/MideaA1Message.d.ts.map +0 -1
- package/dist/devices/ac/MideaACDevice.d.ts.map +0 -1
- package/dist/devices/ac/MideaACMessage.d.ts.map +0 -1
- package/dist/devices/c3/MideaC3Device.d.ts.map +0 -1
- package/dist/devices/c3/MideaC3Message.d.ts.map +0 -1
- package/dist/devices/db/MideaDBDevice.d.ts.map +0 -1
- package/dist/devices/db/MideaDBMessage.d.ts.map +0 -1
- package/dist/devices/e1/MideaE1Device.d.ts.map +0 -1
- package/dist/devices/e1/MideaE1Message.d.ts.map +0 -1
- package/dist/devices/e2/MideaE2Device.d.ts.map +0 -1
- package/dist/devices/e2/MideaE2Message.d.ts.map +0 -1
- package/dist/devices/e3/MideaE3Device.d.ts.map +0 -1
- package/dist/devices/e3/MideaE3Message.d.ts.map +0 -1
- package/dist/devices/fa/MideaFADevice.d.ts.map +0 -1
- package/dist/devices/fa/MideaFAMessage.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/platform.d.ts.map +0 -1
- package/dist/platformUtils.d.ts.map +0 -1
- package/dist/settings.d.ts.map +0 -1
package/dist/core/MideaDevice.js
CHANGED
|
@@ -1,423 +1,433 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.logger
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
[
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1
|
+
import { LocalSecurity } from './MideaSecurity.js';
|
|
2
|
+
import { TCPMessageType, ProtocolVersion, ParseMessageResult } from './MideaConstants.js';
|
|
3
|
+
import { MessageQuerySubtype, MessageQuestCustom, MessageSubtypeResponse, MessageType } from './MideaMessage.js';
|
|
4
|
+
import PacketBuilder from './MideaPacketBuilder.js';
|
|
5
|
+
import EventEmitter from 'events';
|
|
6
|
+
import { PromiseSocket } from './MideaUtils.js';
|
|
7
|
+
export default class MideaDevice extends EventEmitter {
|
|
8
|
+
logger;
|
|
9
|
+
SOCKET_TIMEOUT = 1000; // milliseconds
|
|
10
|
+
ip;
|
|
11
|
+
port;
|
|
12
|
+
id;
|
|
13
|
+
model;
|
|
14
|
+
sn;
|
|
15
|
+
name;
|
|
16
|
+
type;
|
|
17
|
+
version;
|
|
18
|
+
is_running = false;
|
|
19
|
+
available = false;
|
|
20
|
+
unsupported_protocol = [];
|
|
21
|
+
device_protocol_version = 0;
|
|
22
|
+
refresh_interval;
|
|
23
|
+
heartbeat_interval;
|
|
24
|
+
verbose;
|
|
25
|
+
logRecoverableErrors;
|
|
26
|
+
logRefreshStatusErrors;
|
|
27
|
+
_sub_type;
|
|
28
|
+
token = undefined;
|
|
29
|
+
key = undefined;
|
|
30
|
+
security;
|
|
31
|
+
buffer;
|
|
32
|
+
promiseSocket;
|
|
33
|
+
constructor(logger, device_info, config, configDev) {
|
|
34
|
+
super();
|
|
35
|
+
this.logger = logger;
|
|
36
|
+
this.ip = device_info.ip;
|
|
37
|
+
this.port = device_info.port;
|
|
38
|
+
this.id = device_info.id;
|
|
39
|
+
this.model = device_info.model ?? 'unknown';
|
|
40
|
+
this.sn = device_info.sn ?? 'unknown';
|
|
41
|
+
this.name = device_info.name;
|
|
42
|
+
this.type = device_info.type;
|
|
43
|
+
this.version = device_info.version;
|
|
44
|
+
this.verbose = configDev.advanced_options.verbose;
|
|
45
|
+
this.logRecoverableErrors = configDev.advanced_options.logRecoverableErrors;
|
|
46
|
+
this.logRefreshStatusErrors = configDev.advanced_options.logRefreshStatusErrors;
|
|
47
|
+
this.logger.debug(`[${this.name}] Device specific verbose debug logging is set to ${configDev.advanced_options.verbose}`);
|
|
48
|
+
this.logger.debug(`[${this.name}] Device specific log recoverable errors is set to ${configDev.advanced_options.logRecoverableErrors}`);
|
|
49
|
+
this.refresh_interval = config.refreshInterval * 1000; // convert to miliseconds
|
|
50
|
+
this.heartbeat_interval = config.heartbeatInterval * 1000;
|
|
51
|
+
this.security = new LocalSecurity();
|
|
52
|
+
this.buffer = Buffer.alloc(0);
|
|
53
|
+
this.promiseSocket = new PromiseSocket(this.logger, this.logRecoverableErrors);
|
|
54
|
+
}
|
|
55
|
+
get sub_type() {
|
|
56
|
+
return this._sub_type || 0;
|
|
57
|
+
}
|
|
58
|
+
setCredentials(token, key) {
|
|
59
|
+
this.token = token;
|
|
60
|
+
this.key = key;
|
|
61
|
+
}
|
|
62
|
+
fetch_v2_message(message) {
|
|
63
|
+
const result = [];
|
|
64
|
+
while (message.length > 0) {
|
|
65
|
+
const length = message.length;
|
|
66
|
+
if (length < 6) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const alleged_length = message[4] + (message[5] << 8);
|
|
70
|
+
if (length >= alleged_length) {
|
|
71
|
+
result.push(message.subarray(0, alleged_length));
|
|
72
|
+
message = message.subarray(alleged_length, length);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [result, message];
|
|
79
|
+
}
|
|
80
|
+
async connect(refresh_status = true) {
|
|
81
|
+
try {
|
|
82
|
+
this.logger.debug(`Connecting to device ${this.name} (${this.ip}:${this.port})...`);
|
|
83
|
+
await this.promiseSocket.connect(this.port, this.ip);
|
|
84
|
+
this.promiseSocket.setTimeout(this.SOCKET_TIMEOUT);
|
|
85
|
+
if (this.version === ProtocolVersion.V3) {
|
|
86
|
+
await this.authenticate();
|
|
87
|
+
}
|
|
88
|
+
let retries = 0;
|
|
89
|
+
if (refresh_status) {
|
|
90
|
+
let success = await this.refresh_status(true);
|
|
91
|
+
while (!success && retries++ < 3) {
|
|
92
|
+
success = await this.refresh_status(true, true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (retries > 3) {
|
|
96
|
+
this.logger.debug(`[${this.name}] Error when connecting to device ${this.name} (${this.ip}:${this.port}): Refresh status failed.`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// Start listening for network traffic
|
|
100
|
+
this.open();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const msg = err instanceof Error ? err.stack : err;
|
|
105
|
+
this.logger.debug(`[${this.name}] Error when connecting to device ${this.name} (${this.ip}:${this.port}):\n${msg}`);
|
|
106
|
+
// Even though error thrown, it is probably because device is offline. Start listening anyway.
|
|
107
|
+
this.open();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async authenticate() {
|
|
112
|
+
if (!(this.token && this.key)) {
|
|
113
|
+
throw new Error('Token or key is missing!');
|
|
114
|
+
}
|
|
115
|
+
const request = this.security.encode_8370(this.token, TCPMessageType.HANDSHAKE_REQUEST);
|
|
116
|
+
await this.promiseSocket.write(request);
|
|
117
|
+
const response = await this.promiseSocket.read();
|
|
118
|
+
if (response) {
|
|
119
|
+
if (response.length < 20) {
|
|
120
|
+
throw Error(`Authenticate error when receiving data from ${this.ip}:${this.port}. (Data length mismatch)`);
|
|
121
|
+
}
|
|
122
|
+
const resp = response.subarray(8, 72);
|
|
123
|
+
this.security.tcp_key_from_resp(resp, this.key);
|
|
124
|
+
this.logger.debug(`[${this.name}] Authentication success.`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
throw Error(`Authenticate error when receiving data from ${this.ip}:${this.port}.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async send_message(data) {
|
|
131
|
+
if (this.verbose) {
|
|
132
|
+
this.logger.debug(`[${this.name}] Send message:\n${data.toString('hex')}`);
|
|
133
|
+
}
|
|
134
|
+
if (this.version === ProtocolVersion.V3) {
|
|
135
|
+
await this.send_message_v3(data);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await this.send_message_v2(data);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async send_message_v2(data, retries = 3, force_reinit = false) {
|
|
142
|
+
if (retries === 0) {
|
|
143
|
+
throw new Error(`[${this.name} | send_message] Error when sending data to device.`);
|
|
144
|
+
}
|
|
145
|
+
if (force_reinit || !this.promiseSocket || this.promiseSocket.destroyed) {
|
|
146
|
+
this.promiseSocket = new PromiseSocket(this.logger, this.logRecoverableErrors);
|
|
147
|
+
let connected = await this.connect(false);
|
|
148
|
+
while (!connected) {
|
|
149
|
+
connected = await this.connect(false);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
await this.promiseSocket.write(data);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
this.logger.debug(`[${this.name}] Error when sending data to device, retrying...`);
|
|
157
|
+
await this.send_message_v2(data, retries - 1, true);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async send_message_v3(data, message_type = TCPMessageType.ENCRYPTED_REQUEST) {
|
|
161
|
+
const encrypted_data = this.security.encode_8370(data, message_type);
|
|
162
|
+
await this.send_message_v2(encrypted_data);
|
|
163
|
+
}
|
|
164
|
+
async build_send(command) {
|
|
165
|
+
const data = command.serialize();
|
|
166
|
+
const message = new PacketBuilder(this.id, data).finalize();
|
|
167
|
+
await this.send_message(message);
|
|
168
|
+
}
|
|
169
|
+
async refresh_status(wait_response = false, ignore_unsupported = false) {
|
|
170
|
+
this.logger.debug(`[${this.name}] Refreshing status...`);
|
|
171
|
+
const commands = this.build_query();
|
|
172
|
+
if (this._sub_type === undefined) {
|
|
173
|
+
commands.unshift(new MessageQuerySubtype(this.type));
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
let error_cnt = 0;
|
|
177
|
+
for (const cmd of commands) {
|
|
178
|
+
if (ignore_unsupported || !this.unsupported_protocol.includes(cmd.constructor.name)) {
|
|
179
|
+
await this.build_send(cmd);
|
|
180
|
+
if (wait_response) {
|
|
181
|
+
try {
|
|
182
|
+
while (true) {
|
|
183
|
+
const message = await this.promiseSocket.read();
|
|
184
|
+
if (message.length === 0) {
|
|
185
|
+
throw new Error(`[${this.name} | refresh_status] Error when receiving data from device.`);
|
|
186
|
+
}
|
|
187
|
+
const result = this.parse_message(message);
|
|
188
|
+
if (result === ParseMessageResult.SUCCESS) {
|
|
189
|
+
const cmd_idx = this.unsupported_protocol.indexOf(cmd.constructor.name);
|
|
190
|
+
if (cmd_idx !== -1) {
|
|
191
|
+
this.unsupported_protocol.splice(cmd_idx, 1);
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
else if (result === ParseMessageResult.PADDING) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
throw new Error(`[${this.name} | refresh_status] Error when parsing message.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
error_cnt++;
|
|
205
|
+
// TODO: handle connection error
|
|
206
|
+
// this.unsupported_protocol.push(cmd.constructor.name);
|
|
207
|
+
if (this.logRefreshStatusErrors) {
|
|
208
|
+
this.logger.warn(`[${this.name}] Does not supports the protocol ${cmd.constructor.name}, ignored, error: ${err}`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.logger.debug(`[${this.name}] Does not supports the protocol ${cmd.constructor.name}, ignored, error: ${err}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
error_cnt++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (error_cnt === commands.length) {
|
|
221
|
+
if (this.logRefreshStatusErrors) {
|
|
222
|
+
this.logger.error(`[${this.name}] Refresh failed.`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.logger.debug(`[${this.name}] Refresh failed.`);
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
const msg = err instanceof Error ? err.stack : err;
|
|
232
|
+
if (this.logRecoverableErrors) {
|
|
233
|
+
this.logger.warn(`[${this.name} | refresh_status] Recoverable error:\n${msg}`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
this.logger.debug(`[${this.name} | refresh_status] Recoverable error:\n${msg}`);
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
preprocess_message(message) {
|
|
243
|
+
if (message[9] === MessageType.QUERY_SUBTYPE) {
|
|
244
|
+
const msg = new MessageSubtypeResponse(message);
|
|
245
|
+
this._sub_type = msg.sub_type;
|
|
246
|
+
this.set_subtype();
|
|
247
|
+
this.device_protocol_version = msg.device_protocol_version;
|
|
248
|
+
this.logger.debug(`[${this.name}] Subtype: ${this._sub_type}, device protocol version: ${this.device_protocol_version}`);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
parse_message(message) {
|
|
254
|
+
let messages;
|
|
255
|
+
if (this.verbose) {
|
|
256
|
+
this.logger.debug(`[${this.name}] Raw data to parse:\n${message.toString('hex')}`);
|
|
257
|
+
}
|
|
258
|
+
if (this.version === ProtocolVersion.V3) {
|
|
259
|
+
[messages, this.buffer] = this.security.decode_8370(Buffer.concat([this.buffer, message]));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
[messages, this.buffer] = this.fetch_v2_message(Buffer.concat([this.buffer, message]));
|
|
263
|
+
}
|
|
264
|
+
if (message.length === 0) {
|
|
265
|
+
return ParseMessageResult.PADDING;
|
|
266
|
+
}
|
|
267
|
+
for (const msg of messages) {
|
|
268
|
+
if (msg.toString('utf8') === 'ERROR') {
|
|
269
|
+
return ParseMessageResult.ERROR;
|
|
270
|
+
}
|
|
271
|
+
const payload_length = msg[4] + (msg[5] << 8) - 56;
|
|
272
|
+
const payload_type = msg[2] + (msg[3] << 8);
|
|
273
|
+
if (this.verbose) {
|
|
274
|
+
this.logger.debug(`[${this.name}] Msg to process. Length: ${payload_length} (0x${payload_length.toString(16)}), Type: ${payload_type} (0x${payload_type.toString(16)})\n${msg.toString('hex')}`);
|
|
275
|
+
}
|
|
276
|
+
if ([0x1001, 0x0001].includes(payload_type)) {
|
|
277
|
+
// Heartbeat
|
|
278
|
+
if (this.verbose) {
|
|
279
|
+
this.logger.debug(`[${this.name}] Heartbeat:\n${msg.toString('hex')}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (msg.length > 56) {
|
|
283
|
+
const cryptographic = msg.subarray(40, -16);
|
|
284
|
+
if (payload_length % 16 === 0) {
|
|
285
|
+
const decrypted = this.security.aes_decrypt(cryptographic);
|
|
286
|
+
if (this.preprocess_message(decrypted)) {
|
|
287
|
+
if (this.verbose) {
|
|
288
|
+
this.logger.debug(`[${this.name}] Decrypted data to parse:\n${decrypted.toString('hex')}`);
|
|
289
|
+
}
|
|
290
|
+
this.process_message(decrypted);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
if (this.logRecoverableErrors) {
|
|
295
|
+
this.logger.warn(`[${this.name}] Invalid payload length: ` + `${payload_length} (0x${payload_length.toString(16)})`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this.logger.debug(`[${this.name}] Invalid payload length: ` + `${payload_length} (0x${payload_length.toString(16)})`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.logger.warn(`[${this.name}] Illegal message.`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return ParseMessageResult.SUCCESS;
|
|
307
|
+
}
|
|
308
|
+
async send_command(command_type, command_body) {
|
|
309
|
+
const cmd = new MessageQuestCustom(this.type, command_type, command_body);
|
|
310
|
+
try {
|
|
311
|
+
if (this.verbose) {
|
|
312
|
+
this.logger.debug(`[${this.name}] Send command: ${command_body.toString('hex')}`);
|
|
313
|
+
}
|
|
314
|
+
await this.build_send(cmd);
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
306
317
|
this.logger.debug(`[${this.name}] Interface send_command failure: ${e},
|
|
307
318
|
cmd_type: ${command_type},
|
|
308
|
-
cmd_body: ${command_body.toString('hex')}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
async send_heartbeat() {
|
|
312
|
-
const message = new
|
|
313
|
-
await this.send_message(message);
|
|
314
|
-
}
|
|
315
|
-
async update(values) {
|
|
316
|
-
if (this.verbose) {
|
|
317
|
-
this.logger.info(`[${this.name}] Status change: ${JSON.stringify(values)}`);
|
|
318
|
-
}
|
|
319
|
-
this.emit('update', values);
|
|
320
|
-
}
|
|
321
|
-
open() {
|
|
322
|
-
if (!this.is_running) {
|
|
323
|
-
this.is_running = true;
|
|
324
|
-
this.run();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
close() {
|
|
328
|
-
if (this.is_running) {
|
|
329
|
-
this.is_running = false;
|
|
330
|
-
this.close_socket();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
close_socket() {
|
|
334
|
-
this.unsupported_protocol = [];
|
|
335
|
-
this.buffer = Buffer.alloc(0);
|
|
336
|
-
if (this.promiseSocket) {
|
|
337
|
-
this.promiseSocket.destroy();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/*********************************************************************
|
|
341
|
-
* run
|
|
342
|
-
* Continuous loop that runs listening for network traffic from the device
|
|
343
|
-
* and proceses each message as received.
|
|
344
|
-
*/
|
|
345
|
-
async run() {
|
|
346
|
-
this.logger.info(`[${this.name}] Starting network listener.`);
|
|
347
|
-
while (this.is_running) {
|
|
348
|
-
while (this.promiseSocket.destroyed) {
|
|
349
|
-
if (this.logRecoverableErrors) {
|
|
350
|
-
this.logger.info(`[${this.name}] Create new socket, reconnect`);
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
this.logger.debug(`[${this.name}] Create new socket, reconnect`);
|
|
354
|
-
}
|
|
355
|
-
this.promiseSocket = new
|
|
356
|
-
await this.connect(true); // need to refresh_status on connect as we reset start time below.
|
|
357
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
358
|
-
await sleep(5000);
|
|
359
|
-
}
|
|
360
|
-
let timeout_counter = 0;
|
|
361
|
-
const start = Date.now(); // milliseconds
|
|
362
|
-
let previous_refresh = start;
|
|
363
|
-
let previous_heartbeat = start;
|
|
364
|
-
while (!this.promiseSocket.destroyed) {
|
|
365
|
-
try {
|
|
366
|
-
const now = Date.now();
|
|
367
|
-
if (0 < this.refresh_interval && this.refresh_interval <= now - previous_refresh) {
|
|
368
|
-
this.refresh_status();
|
|
369
|
-
previous_refresh = now;
|
|
370
|
-
}
|
|
371
|
-
else if (now - previous_heartbeat >= this.heartbeat_interval) {
|
|
372
|
-
this.send_heartbeat();
|
|
373
|
-
previous_heartbeat = now;
|
|
374
|
-
}
|
|
375
|
-
// We wait up to one second for a message, in effect we cause the while loop
|
|
376
|
-
// we are in to itterate once a second... allowing us to check for heartbeat
|
|
377
|
-
// and refresh intervals (above).
|
|
378
|
-
this.promiseSocket.setTimeout(this.SOCKET_TIMEOUT);
|
|
379
|
-
const msg = await this.promiseSocket.read();
|
|
380
|
-
if (msg.length > 0) {
|
|
381
|
-
const result = this.parse_message(msg);
|
|
382
|
-
if (result ===
|
|
383
|
-
this.logger.debug(`[${this.name} | run] Error return from ParseMessageResult.`);
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
else if (result ===
|
|
387
|
-
timeout_counter = 0;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
timeout_counter++;
|
|
392
|
-
if (timeout_counter > 120 / (this.SOCKET_TIMEOUT / 1000)) {
|
|
393
|
-
// we've looped for ~two minutes and not received a successful response
|
|
394
|
-
// to heartbeat or status refresh. Therefore something must be broken.
|
|
395
|
-
if (this.logRecoverableErrors) {
|
|
396
|
-
this.logger.warn(`[${this.name} | run] Heartbeat timeout, closing.`);
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
this.logger.debug(`[${this.name} | run] Heartbeat timeout, closing.`);
|
|
400
|
-
}
|
|
401
|
-
this.close_socket();
|
|
402
|
-
// We break out of inner loop, but within outer loop we will attempt to
|
|
403
|
-
// reopen the socket and continue.
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
catch (e) {
|
|
409
|
-
const msg = e instanceof Error ? e.stack : e;
|
|
410
|
-
if (this.logRecoverableErrors) {
|
|
411
|
-
this.logger.warn(`[${this.name} | run] Error reading from socket:\n${msg}`);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
this.logger.debug(`[${this.name} | run] Error reading from socket:\n${msg}`);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
this.logger.info(`[${this.name}] Stopping network listener.`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
exports.default = MideaDevice;
|
|
319
|
+
cmd_body: ${command_body.toString('hex')}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async send_heartbeat() {
|
|
323
|
+
const message = new PacketBuilder(this.id, Buffer.alloc(0)).finalize(0);
|
|
324
|
+
await this.send_message(message);
|
|
325
|
+
}
|
|
326
|
+
async update(values) {
|
|
327
|
+
if (this.verbose) {
|
|
328
|
+
this.logger.info(`[${this.name}] Status change: ${JSON.stringify(values)}`);
|
|
329
|
+
}
|
|
330
|
+
this.emit('update', values);
|
|
331
|
+
}
|
|
332
|
+
open() {
|
|
333
|
+
if (!this.is_running) {
|
|
334
|
+
this.is_running = true;
|
|
335
|
+
this.run();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
close() {
|
|
339
|
+
if (this.is_running) {
|
|
340
|
+
this.is_running = false;
|
|
341
|
+
this.close_socket();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
close_socket() {
|
|
345
|
+
this.unsupported_protocol = [];
|
|
346
|
+
this.buffer = Buffer.alloc(0);
|
|
347
|
+
if (this.promiseSocket) {
|
|
348
|
+
this.promiseSocket.destroy();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/*********************************************************************
|
|
352
|
+
* run
|
|
353
|
+
* Continuous loop that runs listening for network traffic from the device
|
|
354
|
+
* and proceses each message as received.
|
|
355
|
+
*/
|
|
356
|
+
async run() {
|
|
357
|
+
this.logger.info(`[${this.name}] Starting network listener.`);
|
|
358
|
+
while (this.is_running) {
|
|
359
|
+
while (this.promiseSocket.destroyed) {
|
|
360
|
+
if (this.logRecoverableErrors) {
|
|
361
|
+
this.logger.info(`[${this.name}] Create new socket, reconnect`);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
this.logger.debug(`[${this.name}] Create new socket, reconnect`);
|
|
365
|
+
}
|
|
366
|
+
this.promiseSocket = new PromiseSocket(this.logger, this.logRecoverableErrors);
|
|
367
|
+
await this.connect(true); // need to refresh_status on connect as we reset start time below.
|
|
368
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
369
|
+
await sleep(5000);
|
|
370
|
+
}
|
|
371
|
+
let timeout_counter = 0;
|
|
372
|
+
const start = Date.now(); // milliseconds
|
|
373
|
+
let previous_refresh = start;
|
|
374
|
+
let previous_heartbeat = start;
|
|
375
|
+
while (!this.promiseSocket.destroyed) {
|
|
376
|
+
try {
|
|
377
|
+
const now = Date.now();
|
|
378
|
+
if (0 < this.refresh_interval && this.refresh_interval <= now - previous_refresh) {
|
|
379
|
+
this.refresh_status();
|
|
380
|
+
previous_refresh = now;
|
|
381
|
+
}
|
|
382
|
+
else if (now - previous_heartbeat >= this.heartbeat_interval) {
|
|
383
|
+
this.send_heartbeat();
|
|
384
|
+
previous_heartbeat = now;
|
|
385
|
+
}
|
|
386
|
+
// We wait up to one second for a message, in effect we cause the while loop
|
|
387
|
+
// we are in to itterate once a second... allowing us to check for heartbeat
|
|
388
|
+
// and refresh intervals (above).
|
|
389
|
+
this.promiseSocket.setTimeout(this.SOCKET_TIMEOUT);
|
|
390
|
+
const msg = await this.promiseSocket.read();
|
|
391
|
+
if (msg.length > 0) {
|
|
392
|
+
const result = this.parse_message(msg);
|
|
393
|
+
if (result === ParseMessageResult.ERROR) {
|
|
394
|
+
this.logger.debug(`[${this.name} | run] Error return from ParseMessageResult.`);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
else if (result === ParseMessageResult.SUCCESS) {
|
|
398
|
+
timeout_counter = 0;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
timeout_counter++;
|
|
403
|
+
if (timeout_counter > 120 / (this.SOCKET_TIMEOUT / 1000)) {
|
|
404
|
+
// we've looped for ~two minutes and not received a successful response
|
|
405
|
+
// to heartbeat or status refresh. Therefore something must be broken.
|
|
406
|
+
if (this.logRecoverableErrors) {
|
|
407
|
+
this.logger.warn(`[${this.name} | run] Heartbeat timeout, closing.`);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
this.logger.debug(`[${this.name} | run] Heartbeat timeout, closing.`);
|
|
411
|
+
}
|
|
412
|
+
this.close_socket();
|
|
413
|
+
// We break out of inner loop, but within outer loop we will attempt to
|
|
414
|
+
// reopen the socket and continue.
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (e) {
|
|
420
|
+
const msg = e instanceof Error ? e.stack : e;
|
|
421
|
+
if (this.logRecoverableErrors) {
|
|
422
|
+
this.logger.warn(`[${this.name} | run] Error reading from socket:\n${msg}`);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
this.logger.debug(`[${this.name} | run] Error reading from socket:\n${msg}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
this.logger.info(`[${this.name}] Stopping network listener.`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
423
433
|
//# sourceMappingURL=MideaDevice.js.map
|