homebridge-midea-platform 1.2.0-beta.2 → 1.2.0-beta.4
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/.husky/pre-commit +0 -0
- package/AC_ori.lua +5140 -0
- package/config.schema.json +14 -2
- package/dist/accessory/AccessoryFactory.d.ts +13 -12
- package/dist/accessory/AccessoryFactory.d.ts.map +1 -1
- package/dist/accessory/AccessoryFactory.js +37 -31
- package/dist/accessory/AccessoryFactory.js.map +1 -1
- package/dist/accessory/AirConditionerAccessory.d.ts +98 -92
- package/dist/accessory/AirConditionerAccessory.d.ts.map +1 -1
- package/dist/accessory/AirConditionerAccessory.js +662 -607
- package/dist/accessory/AirConditionerAccessory.js.map +1 -1
- package/dist/accessory/BaseAccessory.d.ts +11 -11
- package/dist/accessory/BaseAccessory.js +21 -21
- package/dist/accessory/DehumidifierAccessory.d.ts +45 -45
- package/dist/accessory/DehumidifierAccessory.js +344 -344
- package/dist/accessory/DishwasherAccessory.d.ts +30 -30
- package/dist/accessory/DishwasherAccessory.js +63 -63
- package/dist/accessory/ElectricWaterHeaterAccessory.d.ts +44 -44
- package/dist/accessory/ElectricWaterHeaterAccessory.js +176 -176
- package/dist/accessory/FanAccessory.d.ts +39 -39
- package/dist/accessory/FanAccessory.js +123 -123
- package/dist/accessory/FrontLoadWasherAccessory.d.ts +30 -30
- package/dist/accessory/FrontLoadWasherAccessory.js +66 -66
- package/dist/accessory/GasWaterHeaterAccessory.d.ts +51 -51
- package/dist/accessory/GasWaterHeaterAccessory.js +216 -216
- package/dist/core/MideaCloud.d.ts +35 -35
- package/dist/core/MideaCloud.js +350 -350
- package/dist/core/MideaConstants.d.ts +50 -50
- package/dist/core/MideaConstants.js +58 -58
- package/dist/core/MideaDevice.d.ts +76 -76
- package/dist/core/MideaDevice.js +409 -409
- package/dist/core/MideaDiscover.d.ts +35 -35
- package/dist/core/MideaDiscover.js +212 -212
- package/dist/core/MideaMessage.d.ts +75 -75
- package/dist/core/MideaMessage.js +184 -184
- package/dist/core/MideaPacketBuilder.d.ts +10 -10
- package/dist/core/MideaPacketBuilder.js +60 -60
- package/dist/core/MideaSecurity.d.ts +63 -63
- package/dist/core/MideaSecurity.js +251 -251
- package/dist/core/MideaUtils.d.ts +32 -32
- package/dist/core/MideaUtils.js +181 -181
- package/dist/devices/DeviceFactory.d.ts +13 -13
- package/dist/devices/DeviceFactory.d.ts.map +1 -1
- package/dist/devices/DeviceFactory.js +37 -36
- package/dist/devices/DeviceFactory.js.map +1 -1
- package/dist/devices/a1/MideaA1Device.d.ts +76 -76
- package/dist/devices/a1/MideaA1Device.js +145 -145
- package/dist/devices/a1/MideaA1Message.d.ts +40 -40
- package/dist/devices/a1/MideaA1Message.js +198 -198
- package/dist/devices/ac/MideaACDevice.d.ts +106 -104
- package/dist/devices/ac/MideaACDevice.d.ts.map +1 -1
- package/dist/devices/ac/MideaACDevice.js +400 -384
- package/dist/devices/ac/MideaACDevice.js.map +1 -1
- package/dist/devices/ac/MideaACMessage.d.ts +95 -94
- package/dist/devices/ac/MideaACMessage.d.ts.map +1 -1
- package/dist/devices/ac/MideaACMessage.js +619 -611
- package/dist/devices/ac/MideaACMessage.js.map +1 -1
- package/dist/devices/db/MideaDBDevice.d.ts +29 -29
- package/dist/devices/db/MideaDBDevice.js +100 -100
- package/dist/devices/db/MideaDBMessage.d.ts +32 -32
- package/dist/devices/db/MideaDBMessage.js +101 -101
- package/dist/devices/e1/MideaE1Device.d.ts +56 -56
- package/dist/devices/e1/MideaE1Device.js +128 -128
- package/dist/devices/e1/MideaE1Message.d.ts +28 -28
- package/dist/devices/e1/MideaE1Message.js +107 -107
- package/dist/devices/e2/MideaE2Device.d.ts +44 -44
- package/dist/devices/e2/MideaE2Device.js +129 -129
- package/dist/devices/e2/MideaE2Message.d.ts +33 -33
- package/dist/devices/e2/MideaE2Message.js +132 -132
- package/dist/devices/e3/MideaE3Device.d.ts +43 -43
- package/dist/devices/e3/MideaE3Device.js +137 -137
- package/dist/devices/e3/MideaE3Message.d.ts +51 -51
- package/dist/devices/e3/MideaE3Message.js +136 -136
- package/dist/devices/fa/MideaFADevice.d.ts +36 -36
- package/dist/devices/fa/MideaFADevice.js +106 -106
- package/dist/devices/fa/MideaFAMessage.d.ts +38 -38
- package/dist/devices/fa/MideaFAMessage.js +98 -98
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/platform.d.ts +60 -60
- package/dist/platform.js +212 -212
- package/dist/platformUtils.d.ts +108 -106
- package/dist/platformUtils.d.ts.map +1 -1
- package/dist/platformUtils.js +103 -101
- package/dist/platformUtils.js.map +1 -1
- package/dist/settings.d.ts +8 -8
- package/dist/settings.js +11 -11
- package/docs/ac.md +6 -0
- package/package.json +1 -1
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { Logger } from 'homebridge';
|
|
3
|
-
import EventEmitter from 'events';
|
|
4
|
-
export default class Discover extends EventEmitter {
|
|
5
|
-
private readonly logger;
|
|
6
|
-
private socket;
|
|
7
|
-
private readonly xml_parser;
|
|
8
|
-
private security;
|
|
9
|
-
private ips;
|
|
10
|
-
constructor(logger: Logger);
|
|
11
|
-
/*********************************************************************
|
|
12
|
-
* discoverDeviceByIP
|
|
13
|
-
* Sends discover message to a single IP address. Will resend the message
|
|
14
|
-
* an additional "retries" times spaced by 3 seconds if the target IP
|
|
15
|
-
* address has not responded (recorded in the above callback).
|
|
16
|
-
*/
|
|
17
|
-
discoverDeviceByIP(ip: string, retries?: number, timeout?: number): void;
|
|
18
|
-
/*********************************************************************
|
|
19
|
-
* ifBroadcastAddrs
|
|
20
|
-
* Broadcasts to 255.255.255.255 only gets sent out on the first network inteface.
|
|
21
|
-
* This function finds all network interfaces and returns the broadcast address
|
|
22
|
-
* for each in an array, e.g. ['192.168.1.255', '192.168.100.255']. If there are
|
|
23
|
-
* multiple interfaces this will cause broadcast to be sent out on each interface
|
|
24
|
-
* so all appliances are properly discovered.
|
|
25
|
-
*/
|
|
26
|
-
private ifBroadcastAddrs;
|
|
27
|
-
/*********************************************************************
|
|
28
|
-
* startDiscover
|
|
29
|
-
* Sends broadcast to network discover Midea devices. Will continue sending
|
|
30
|
-
* up to an additional "retries" times each spaced by 3 seconds.
|
|
31
|
-
*/
|
|
32
|
-
startDiscover(retries?: number, timeout?: number): void;
|
|
33
|
-
private getDeviceVersion;
|
|
34
|
-
private getDeviceInfo;
|
|
35
|
-
}
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Logger } from 'homebridge';
|
|
3
|
+
import EventEmitter from 'events';
|
|
4
|
+
export default class Discover extends EventEmitter {
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private socket;
|
|
7
|
+
private readonly xml_parser;
|
|
8
|
+
private security;
|
|
9
|
+
private ips;
|
|
10
|
+
constructor(logger: Logger);
|
|
11
|
+
/*********************************************************************
|
|
12
|
+
* discoverDeviceByIP
|
|
13
|
+
* Sends discover message to a single IP address. Will resend the message
|
|
14
|
+
* an additional "retries" times spaced by 3 seconds if the target IP
|
|
15
|
+
* address has not responded (recorded in the above callback).
|
|
16
|
+
*/
|
|
17
|
+
discoverDeviceByIP(ip: string, retries?: number, timeout?: number): void;
|
|
18
|
+
/*********************************************************************
|
|
19
|
+
* ifBroadcastAddrs
|
|
20
|
+
* Broadcasts to 255.255.255.255 only gets sent out on the first network inteface.
|
|
21
|
+
* This function finds all network interfaces and returns the broadcast address
|
|
22
|
+
* for each in an array, e.g. ['192.168.1.255', '192.168.100.255']. If there are
|
|
23
|
+
* multiple interfaces this will cause broadcast to be sent out on each interface
|
|
24
|
+
* so all appliances are properly discovered.
|
|
25
|
+
*/
|
|
26
|
+
private ifBroadcastAddrs;
|
|
27
|
+
/*********************************************************************
|
|
28
|
+
* startDiscover
|
|
29
|
+
* Sends broadcast to network discover Midea devices. Will continue sending
|
|
30
|
+
* up to an additional "retries" times each spaced by 3 seconds.
|
|
31
|
+
*/
|
|
32
|
+
startDiscover(retries?: number, timeout?: number): void;
|
|
33
|
+
private getDeviceVersion;
|
|
34
|
+
private getDeviceInfo;
|
|
35
|
+
}
|
|
36
36
|
//# sourceMappingURL=MideaDiscover.d.ts.map
|
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
/***********************************************************************
|
|
7
|
-
* Midea device discovery class where we broadcast to network to find
|
|
8
|
-
* connected devices.
|
|
9
|
-
*
|
|
10
|
-
* Copyright (c) 2023 Kovalovszky Patrik, https://github.com/kovapatrik
|
|
11
|
-
* Portions Copyright (c) 2023 David Kerr, https://github.com/dkerr64
|
|
12
|
-
*
|
|
13
|
-
* With thanks to https://github.com/georgezhao2010/midea_ac_lan
|
|
14
|
-
*
|
|
15
|
-
*/
|
|
16
|
-
const dgram_1 = __importDefault(require("dgram"));
|
|
17
|
-
const MideaConstants_1 = require("./MideaConstants");
|
|
18
|
-
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
19
|
-
const events_1 = __importDefault(require("events"));
|
|
20
|
-
const MideaSecurity_1 = require("./MideaSecurity");
|
|
21
|
-
// To access network interface detail...
|
|
22
|
-
const os_1 = __importDefault(require("os"));
|
|
23
|
-
class Discover extends events_1.default {
|
|
24
|
-
constructor(logger) {
|
|
25
|
-
super();
|
|
26
|
-
this.logger = logger;
|
|
27
|
-
this.ips = [];
|
|
28
|
-
this.security = new MideaSecurity_1.LocalSecurity();
|
|
29
|
-
this.xml_parser = new fast_xml_parser_1.XMLParser();
|
|
30
|
-
this.socket = dgram_1.default.createSocket('udp4');
|
|
31
|
-
this.socket.bind(0, undefined, () => {
|
|
32
|
-
this.socket.setBroadcast(true);
|
|
33
|
-
});
|
|
34
|
-
this.socket.on('error', (err) => {
|
|
35
|
-
this.logger.debug(`server error:\n${err.stack}`);
|
|
36
|
-
});
|
|
37
|
-
// Register callback function executed when message received on the socket as
|
|
38
|
-
// result of sending broadcast messages out to network / IP address.
|
|
39
|
-
this.socket.on('message', async (msg, rinfo) => {
|
|
40
|
-
if (!this.ips.includes(rinfo.address)) {
|
|
41
|
-
// Only add device if it has not already been added.
|
|
42
|
-
this.ips.push(rinfo.address);
|
|
43
|
-
const device_version = this.getDeviceVersion(msg);
|
|
44
|
-
const device_info = await this.getDeviceInfo(rinfo.address, device_version, msg);
|
|
45
|
-
this.logger.info(`Discovered device: ${JSON.stringify(device_info)}`);
|
|
46
|
-
// Send signal to Homebridge platform with details on the discovered device
|
|
47
|
-
this.emit('device', device_info);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
/*********************************************************************
|
|
52
|
-
* discoverDeviceByIP
|
|
53
|
-
* Sends discover message to a single IP address. Will resend the message
|
|
54
|
-
* an additional "retries" times spaced by 3 seconds if the target IP
|
|
55
|
-
* address has not responded (recorded in the above callback).
|
|
56
|
-
*/
|
|
57
|
-
discoverDeviceByIP(ip, retries = 3, timeout = 2000) {
|
|
58
|
-
let tries = 0;
|
|
59
|
-
function broadcast() {
|
|
60
|
-
if (this.ips.includes(ip) || tries++ > retries) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
this.logger.debug(`Sending discovery message to ${ip}, try ${tries}...`);
|
|
64
|
-
for (const port of [6445, 20086]) {
|
|
65
|
-
this.socket.send(Buffer.from(MideaConstants_1.DISCOVERY_MESSAGE), port, ip, (err) => {
|
|
66
|
-
if (err) {
|
|
67
|
-
this.logger.error(`Error while sending message to ${ip}: ${err}`);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
setTimeout(broadcast.bind(this), timeout);
|
|
72
|
-
}
|
|
73
|
-
broadcast.bind(this)();
|
|
74
|
-
}
|
|
75
|
-
/*********************************************************************
|
|
76
|
-
* ifBroadcastAddrs
|
|
77
|
-
* Broadcasts to 255.255.255.255 only gets sent out on the first network inteface.
|
|
78
|
-
* This function finds all network interfaces and returns the broadcast address
|
|
79
|
-
* for each in an array, e.g. ['192.168.1.255', '192.168.100.255']. If there are
|
|
80
|
-
* multiple interfaces this will cause broadcast to be sent out on each interface
|
|
81
|
-
* so all appliances are properly discovered.
|
|
82
|
-
*/
|
|
83
|
-
ifBroadcastAddrs() {
|
|
84
|
-
const list = [];
|
|
85
|
-
try {
|
|
86
|
-
const ifaces = os_1.default.networkInterfaces();
|
|
87
|
-
for (const iface of Object.values(ifaces)) {
|
|
88
|
-
if (iface) {
|
|
89
|
-
for (const f of iface) {
|
|
90
|
-
if (!f.internal && f.family === 'IPv4') {
|
|
91
|
-
// With thanks to https://github.com/aal89/broadcast-address/blob/master/broadcast-address.js
|
|
92
|
-
const addr_splitted = f.address.split('.');
|
|
93
|
-
const netmask_splitted = f.netmask.split('.');
|
|
94
|
-
// Bitwise OR over the splitted NAND netmask, then glue them back together with a dot character to form an ip
|
|
95
|
-
// we have to do a NAND operation because of the 2-complements; getting rid of all the 'prepended' 1's with & 0xFF
|
|
96
|
-
const broadcast = addr_splitted.map((e, i) => (~netmask_splitted[i] & 0xff) | Number.parseInt(e)).join('.');
|
|
97
|
-
list.push(broadcast);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (e) {
|
|
104
|
-
const msg = e instanceof Error ? e.stack : e;
|
|
105
|
-
this.logger.error(`Fatal error during plugin initialization:\n${msg}`);
|
|
106
|
-
}
|
|
107
|
-
// this.logger.info(`Broadcast addresses: ${JSON.stringify(list)}`);
|
|
108
|
-
return list;
|
|
109
|
-
}
|
|
110
|
-
/*********************************************************************
|
|
111
|
-
* startDiscover
|
|
112
|
-
* Sends broadcast to network discover Midea devices. Will continue sending
|
|
113
|
-
* up to an additional "retries" times each spaced by 3 seconds.
|
|
114
|
-
*/
|
|
115
|
-
startDiscover(retries = 3, timeout = 2000) {
|
|
116
|
-
let tries = 0;
|
|
117
|
-
// force timeout to be between 500ms and 5 seconds
|
|
118
|
-
timeout = Math.max(500, Math.min(timeout, 5000));
|
|
119
|
-
const broadcastAddrs = this.ifBroadcastAddrs();
|
|
120
|
-
function broadcast() {
|
|
121
|
-
if (tries++ > retries) {
|
|
122
|
-
this.logger.debug(`Device discovery complete after ${retries + 1} network broadcasts.`);
|
|
123
|
-
this.emit('complete');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
this.emit('retry', tries, this.ips.length);
|
|
128
|
-
}
|
|
129
|
-
for (const ip of broadcastAddrs) {
|
|
130
|
-
this.logger.debug(`Sending discovery message to ${ip}, try ${tries}...`);
|
|
131
|
-
for (const port of [6445, 20086]) {
|
|
132
|
-
this.socket.send(Buffer.from(MideaConstants_1.DISCOVERY_MESSAGE), port, ip, (err) => {
|
|
133
|
-
if (err) {
|
|
134
|
-
const msg = err instanceof Error ? err.stack : err;
|
|
135
|
-
this.logger.error(`Error while sending message to ${ip}:${port}:\n${msg}`);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
setTimeout(broadcast.bind(this), timeout);
|
|
141
|
-
}
|
|
142
|
-
broadcast.bind(this)();
|
|
143
|
-
}
|
|
144
|
-
getDeviceVersion(data) {
|
|
145
|
-
try {
|
|
146
|
-
this.xml_parser.parse(data.toString(), true);
|
|
147
|
-
return MideaConstants_1.ProtocolVersion.V1;
|
|
148
|
-
}
|
|
149
|
-
catch (_a) {
|
|
150
|
-
const start_of_packet = data.subarray(0, 2);
|
|
151
|
-
if (start_of_packet.compare(Buffer.from([0x5a, 0x5a])) === 0) {
|
|
152
|
-
return MideaConstants_1.ProtocolVersion.V2;
|
|
153
|
-
}
|
|
154
|
-
else if (start_of_packet.compare(Buffer.from([0x83, 0x70])) === 0) {
|
|
155
|
-
return MideaConstants_1.ProtocolVersion.V3;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
throw new Error('Unknown device version.');
|
|
159
|
-
}
|
|
160
|
-
async getDeviceInfo(ip, version, data) {
|
|
161
|
-
if (version === MideaConstants_1.ProtocolVersion.V1) {
|
|
162
|
-
// const root = this.xml_parser.parse(data.toString());
|
|
163
|
-
// const device = root["body"]["device"]
|
|
164
|
-
// if (device) {
|
|
165
|
-
// const port = device["port"];
|
|
166
|
-
// }
|
|
167
|
-
// throw new Error("Could not find 'body/device' in XML.");
|
|
168
|
-
throw new Error('Version 1 not implemented.');
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
let buffer = data;
|
|
172
|
-
// Strip V3 header and hash
|
|
173
|
-
if (version === MideaConstants_1.ProtocolVersion.V3) {
|
|
174
|
-
buffer = buffer.subarray(8, -16);
|
|
175
|
-
}
|
|
176
|
-
const encrypted_data = buffer.subarray(40, -16);
|
|
177
|
-
const device_id = buffer.readUIntLE(20, 6);
|
|
178
|
-
let decrypted_buffer;
|
|
179
|
-
try {
|
|
180
|
-
decrypted_buffer = this.security.aes_decrypt(encrypted_data);
|
|
181
|
-
}
|
|
182
|
-
catch (err) {
|
|
183
|
-
throw new Error(`Error while decrypting data: ${err}`);
|
|
184
|
-
}
|
|
185
|
-
// prettier-ignore
|
|
186
|
-
// eslint-disable-next-line max-len
|
|
187
|
-
const ip_address = `${decrypted_buffer.readUint8(3)}.${decrypted_buffer.readUint8(2)}.${decrypted_buffer.readUint8(1)}.${decrypted_buffer.readUint8(0)}`;
|
|
188
|
-
const port = decrypted_buffer.readUIntLE(4, 2);
|
|
189
|
-
if (ip_address !== ip) {
|
|
190
|
-
this.logger.warn(`IP address mismatch: ${ip_address} != ${ip}`);
|
|
191
|
-
}
|
|
192
|
-
const model = decrypted_buffer.subarray(17, 25).toString();
|
|
193
|
-
// Serial number
|
|
194
|
-
const sn = decrypted_buffer.subarray(8, 40).toString();
|
|
195
|
-
// Extract name/SSID
|
|
196
|
-
const name_length = decrypted_buffer.readUIntLE(40, 1);
|
|
197
|
-
const name = decrypted_buffer.subarray(41, 41 + name_length).toString();
|
|
198
|
-
const device_type = Number(`0x${name.split('_')[1]}`);
|
|
199
|
-
return {
|
|
200
|
-
ip: ip,
|
|
201
|
-
port: port,
|
|
202
|
-
id: device_id,
|
|
203
|
-
model: model,
|
|
204
|
-
sn: sn,
|
|
205
|
-
name: name,
|
|
206
|
-
type: device_type,
|
|
207
|
-
version: version,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
exports.default = Discover;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/***********************************************************************
|
|
7
|
+
* Midea device discovery class where we broadcast to network to find
|
|
8
|
+
* connected devices.
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2023 Kovalovszky Patrik, https://github.com/kovapatrik
|
|
11
|
+
* Portions Copyright (c) 2023 David Kerr, https://github.com/dkerr64
|
|
12
|
+
*
|
|
13
|
+
* With thanks to https://github.com/georgezhao2010/midea_ac_lan
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
const dgram_1 = __importDefault(require("dgram"));
|
|
17
|
+
const MideaConstants_1 = require("./MideaConstants");
|
|
18
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
19
|
+
const events_1 = __importDefault(require("events"));
|
|
20
|
+
const MideaSecurity_1 = require("./MideaSecurity");
|
|
21
|
+
// To access network interface detail...
|
|
22
|
+
const os_1 = __importDefault(require("os"));
|
|
23
|
+
class Discover extends events_1.default {
|
|
24
|
+
constructor(logger) {
|
|
25
|
+
super();
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
this.ips = [];
|
|
28
|
+
this.security = new MideaSecurity_1.LocalSecurity();
|
|
29
|
+
this.xml_parser = new fast_xml_parser_1.XMLParser();
|
|
30
|
+
this.socket = dgram_1.default.createSocket('udp4');
|
|
31
|
+
this.socket.bind(0, undefined, () => {
|
|
32
|
+
this.socket.setBroadcast(true);
|
|
33
|
+
});
|
|
34
|
+
this.socket.on('error', (err) => {
|
|
35
|
+
this.logger.debug(`server error:\n${err.stack}`);
|
|
36
|
+
});
|
|
37
|
+
// Register callback function executed when message received on the socket as
|
|
38
|
+
// result of sending broadcast messages out to network / IP address.
|
|
39
|
+
this.socket.on('message', async (msg, rinfo) => {
|
|
40
|
+
if (!this.ips.includes(rinfo.address)) {
|
|
41
|
+
// Only add device if it has not already been added.
|
|
42
|
+
this.ips.push(rinfo.address);
|
|
43
|
+
const device_version = this.getDeviceVersion(msg);
|
|
44
|
+
const device_info = await this.getDeviceInfo(rinfo.address, device_version, msg);
|
|
45
|
+
this.logger.info(`Discovered device: ${JSON.stringify(device_info)}`);
|
|
46
|
+
// Send signal to Homebridge platform with details on the discovered device
|
|
47
|
+
this.emit('device', device_info);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/*********************************************************************
|
|
52
|
+
* discoverDeviceByIP
|
|
53
|
+
* Sends discover message to a single IP address. Will resend the message
|
|
54
|
+
* an additional "retries" times spaced by 3 seconds if the target IP
|
|
55
|
+
* address has not responded (recorded in the above callback).
|
|
56
|
+
*/
|
|
57
|
+
discoverDeviceByIP(ip, retries = 3, timeout = 2000) {
|
|
58
|
+
let tries = 0;
|
|
59
|
+
function broadcast() {
|
|
60
|
+
if (this.ips.includes(ip) || tries++ > retries) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.logger.debug(`Sending discovery message to ${ip}, try ${tries}...`);
|
|
64
|
+
for (const port of [6445, 20086]) {
|
|
65
|
+
this.socket.send(Buffer.from(MideaConstants_1.DISCOVERY_MESSAGE), port, ip, (err) => {
|
|
66
|
+
if (err) {
|
|
67
|
+
this.logger.error(`Error while sending message to ${ip}: ${err}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
setTimeout(broadcast.bind(this), timeout);
|
|
72
|
+
}
|
|
73
|
+
broadcast.bind(this)();
|
|
74
|
+
}
|
|
75
|
+
/*********************************************************************
|
|
76
|
+
* ifBroadcastAddrs
|
|
77
|
+
* Broadcasts to 255.255.255.255 only gets sent out on the first network inteface.
|
|
78
|
+
* This function finds all network interfaces and returns the broadcast address
|
|
79
|
+
* for each in an array, e.g. ['192.168.1.255', '192.168.100.255']. If there are
|
|
80
|
+
* multiple interfaces this will cause broadcast to be sent out on each interface
|
|
81
|
+
* so all appliances are properly discovered.
|
|
82
|
+
*/
|
|
83
|
+
ifBroadcastAddrs() {
|
|
84
|
+
const list = [];
|
|
85
|
+
try {
|
|
86
|
+
const ifaces = os_1.default.networkInterfaces();
|
|
87
|
+
for (const iface of Object.values(ifaces)) {
|
|
88
|
+
if (iface) {
|
|
89
|
+
for (const f of iface) {
|
|
90
|
+
if (!f.internal && f.family === 'IPv4') {
|
|
91
|
+
// With thanks to https://github.com/aal89/broadcast-address/blob/master/broadcast-address.js
|
|
92
|
+
const addr_splitted = f.address.split('.');
|
|
93
|
+
const netmask_splitted = f.netmask.split('.');
|
|
94
|
+
// Bitwise OR over the splitted NAND netmask, then glue them back together with a dot character to form an ip
|
|
95
|
+
// we have to do a NAND operation because of the 2-complements; getting rid of all the 'prepended' 1's with & 0xFF
|
|
96
|
+
const broadcast = addr_splitted.map((e, i) => (~netmask_splitted[i] & 0xff) | Number.parseInt(e)).join('.');
|
|
97
|
+
list.push(broadcast);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
const msg = e instanceof Error ? e.stack : e;
|
|
105
|
+
this.logger.error(`Fatal error during plugin initialization:\n${msg}`);
|
|
106
|
+
}
|
|
107
|
+
// this.logger.info(`Broadcast addresses: ${JSON.stringify(list)}`);
|
|
108
|
+
return list;
|
|
109
|
+
}
|
|
110
|
+
/*********************************************************************
|
|
111
|
+
* startDiscover
|
|
112
|
+
* Sends broadcast to network discover Midea devices. Will continue sending
|
|
113
|
+
* up to an additional "retries" times each spaced by 3 seconds.
|
|
114
|
+
*/
|
|
115
|
+
startDiscover(retries = 3, timeout = 2000) {
|
|
116
|
+
let tries = 0;
|
|
117
|
+
// force timeout to be between 500ms and 5 seconds
|
|
118
|
+
timeout = Math.max(500, Math.min(timeout, 5000));
|
|
119
|
+
const broadcastAddrs = this.ifBroadcastAddrs();
|
|
120
|
+
function broadcast() {
|
|
121
|
+
if (tries++ > retries) {
|
|
122
|
+
this.logger.debug(`Device discovery complete after ${retries + 1} network broadcasts.`);
|
|
123
|
+
this.emit('complete');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
this.emit('retry', tries, this.ips.length);
|
|
128
|
+
}
|
|
129
|
+
for (const ip of broadcastAddrs) {
|
|
130
|
+
this.logger.debug(`Sending discovery message to ${ip}, try ${tries}...`);
|
|
131
|
+
for (const port of [6445, 20086]) {
|
|
132
|
+
this.socket.send(Buffer.from(MideaConstants_1.DISCOVERY_MESSAGE), port, ip, (err) => {
|
|
133
|
+
if (err) {
|
|
134
|
+
const msg = err instanceof Error ? err.stack : err;
|
|
135
|
+
this.logger.error(`Error while sending message to ${ip}:${port}:\n${msg}`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
setTimeout(broadcast.bind(this), timeout);
|
|
141
|
+
}
|
|
142
|
+
broadcast.bind(this)();
|
|
143
|
+
}
|
|
144
|
+
getDeviceVersion(data) {
|
|
145
|
+
try {
|
|
146
|
+
this.xml_parser.parse(data.toString(), true);
|
|
147
|
+
return MideaConstants_1.ProtocolVersion.V1;
|
|
148
|
+
}
|
|
149
|
+
catch (_a) {
|
|
150
|
+
const start_of_packet = data.subarray(0, 2);
|
|
151
|
+
if (start_of_packet.compare(Buffer.from([0x5a, 0x5a])) === 0) {
|
|
152
|
+
return MideaConstants_1.ProtocolVersion.V2;
|
|
153
|
+
}
|
|
154
|
+
else if (start_of_packet.compare(Buffer.from([0x83, 0x70])) === 0) {
|
|
155
|
+
return MideaConstants_1.ProtocolVersion.V3;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw new Error('Unknown device version.');
|
|
159
|
+
}
|
|
160
|
+
async getDeviceInfo(ip, version, data) {
|
|
161
|
+
if (version === MideaConstants_1.ProtocolVersion.V1) {
|
|
162
|
+
// const root = this.xml_parser.parse(data.toString());
|
|
163
|
+
// const device = root["body"]["device"]
|
|
164
|
+
// if (device) {
|
|
165
|
+
// const port = device["port"];
|
|
166
|
+
// }
|
|
167
|
+
// throw new Error("Could not find 'body/device' in XML.");
|
|
168
|
+
throw new Error('Version 1 not implemented.');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
let buffer = data;
|
|
172
|
+
// Strip V3 header and hash
|
|
173
|
+
if (version === MideaConstants_1.ProtocolVersion.V3) {
|
|
174
|
+
buffer = buffer.subarray(8, -16);
|
|
175
|
+
}
|
|
176
|
+
const encrypted_data = buffer.subarray(40, -16);
|
|
177
|
+
const device_id = buffer.readUIntLE(20, 6);
|
|
178
|
+
let decrypted_buffer;
|
|
179
|
+
try {
|
|
180
|
+
decrypted_buffer = this.security.aes_decrypt(encrypted_data);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
throw new Error(`Error while decrypting data: ${err}`);
|
|
184
|
+
}
|
|
185
|
+
// prettier-ignore
|
|
186
|
+
// eslint-disable-next-line max-len
|
|
187
|
+
const ip_address = `${decrypted_buffer.readUint8(3)}.${decrypted_buffer.readUint8(2)}.${decrypted_buffer.readUint8(1)}.${decrypted_buffer.readUint8(0)}`;
|
|
188
|
+
const port = decrypted_buffer.readUIntLE(4, 2);
|
|
189
|
+
if (ip_address !== ip) {
|
|
190
|
+
this.logger.warn(`IP address mismatch: ${ip_address} != ${ip}`);
|
|
191
|
+
}
|
|
192
|
+
const model = decrypted_buffer.subarray(17, 25).toString();
|
|
193
|
+
// Serial number
|
|
194
|
+
const sn = decrypted_buffer.subarray(8, 40).toString();
|
|
195
|
+
// Extract name/SSID
|
|
196
|
+
const name_length = decrypted_buffer.readUIntLE(40, 1);
|
|
197
|
+
const name = decrypted_buffer.subarray(41, 41 + name_length).toString();
|
|
198
|
+
const device_type = Number(`0x${name.split('_')[1]}`);
|
|
199
|
+
return {
|
|
200
|
+
ip: ip,
|
|
201
|
+
port: port,
|
|
202
|
+
id: device_id,
|
|
203
|
+
model: model,
|
|
204
|
+
sn: sn,
|
|
205
|
+
name: name,
|
|
206
|
+
type: device_type,
|
|
207
|
+
version: version,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.default = Discover;
|
|
213
213
|
//# sourceMappingURL=MideaDiscover.js.map
|
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { DeviceType } from './MideaConstants';
|
|
3
|
-
export declare enum MessageType {
|
|
4
|
-
UNKNOWN = 0,
|
|
5
|
-
SET = 2,
|
|
6
|
-
QUERY = 3,
|
|
7
|
-
NOTIFY1 = 4,
|
|
8
|
-
NOTIFY2 = 5,
|
|
9
|
-
EXCEPTION = 6,
|
|
10
|
-
QUERY_SN = 7,
|
|
11
|
-
EXCEPTION2 = 10,
|
|
12
|
-
QUERY_SUBTYPE = 160
|
|
13
|
-
}
|
|
14
|
-
declare abstract class MessageBase {
|
|
15
|
-
protected readonly HEADER_LENGTH = 10;
|
|
16
|
-
protected abstract device_type: DeviceType;
|
|
17
|
-
protected abstract message_type: MessageType;
|
|
18
|
-
protected abstract body_type: number | null;
|
|
19
|
-
protected abstract device_protocol_version: number;
|
|
20
|
-
protected abstract body: Buffer;
|
|
21
|
-
checksum(data: Buffer): number;
|
|
22
|
-
}
|
|
23
|
-
export declare abstract class MessageRequest extends MessageBase {
|
|
24
|
-
device_type: DeviceType;
|
|
25
|
-
message_type: MessageType;
|
|
26
|
-
body_type: number | null;
|
|
27
|
-
device_protocol_version: number;
|
|
28
|
-
protected abstract _body: Buffer;
|
|
29
|
-
constructor(device_type: DeviceType, message_type: MessageType, body_type: number | null, device_protocol_version: number);
|
|
30
|
-
get body(): Buffer;
|
|
31
|
-
get header(): Buffer;
|
|
32
|
-
serialize(): Buffer;
|
|
33
|
-
}
|
|
34
|
-
export declare class MessageQuerySubtype extends MessageRequest {
|
|
35
|
-
protected _body: Buffer;
|
|
36
|
-
constructor(device_type: DeviceType);
|
|
37
|
-
}
|
|
38
|
-
export declare class MessageQuestCustom extends MessageRequest {
|
|
39
|
-
protected _body: Buffer;
|
|
40
|
-
protected cmd_body: Buffer;
|
|
41
|
-
constructor(device_type: DeviceType, message_type: MessageType, cmd_body: Buffer);
|
|
42
|
-
get body(): Buffer;
|
|
43
|
-
}
|
|
44
|
-
export declare class MessageBody {
|
|
45
|
-
readonly data: Buffer;
|
|
46
|
-
constructor(data: Buffer);
|
|
47
|
-
get body_type(): number;
|
|
48
|
-
static read_byte(data: Buffer, offset: number, default_value: number): number;
|
|
49
|
-
}
|
|
50
|
-
export declare class NewProtocolMessageBody extends MessageBody {
|
|
51
|
-
protected packet_length: number;
|
|
52
|
-
constructor(body: Buffer, body_type: number);
|
|
53
|
-
static packet(param: number, value: Buffer, packet_length?: number): Buffer;
|
|
54
|
-
parse(): {
|
|
55
|
-
[key: string]: any;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
export declare class MessageResponse extends MessageBase {
|
|
59
|
-
protected header: Buffer;
|
|
60
|
-
protected device_type: DeviceType;
|
|
61
|
-
protected message_type: MessageType;
|
|
62
|
-
protected body_type: number;
|
|
63
|
-
device_protocol_version: number;
|
|
64
|
-
protected _body: MessageBody;
|
|
65
|
-
constructor(message: Buffer | null | undefined);
|
|
66
|
-
get body(): Buffer;
|
|
67
|
-
set_body(body: MessageBody): void;
|
|
68
|
-
get_body_type(): string;
|
|
69
|
-
get_body_attribute(name: string): any;
|
|
70
|
-
}
|
|
71
|
-
export declare class MessageSubtypeResponse extends MessageResponse {
|
|
72
|
-
sub_type: number;
|
|
73
|
-
constructor(message: Buffer | null | undefined);
|
|
74
|
-
}
|
|
75
|
-
export {};
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { DeviceType } from './MideaConstants';
|
|
3
|
+
export declare enum MessageType {
|
|
4
|
+
UNKNOWN = 0,
|
|
5
|
+
SET = 2,
|
|
6
|
+
QUERY = 3,
|
|
7
|
+
NOTIFY1 = 4,
|
|
8
|
+
NOTIFY2 = 5,
|
|
9
|
+
EXCEPTION = 6,
|
|
10
|
+
QUERY_SN = 7,
|
|
11
|
+
EXCEPTION2 = 10,
|
|
12
|
+
QUERY_SUBTYPE = 160
|
|
13
|
+
}
|
|
14
|
+
declare abstract class MessageBase {
|
|
15
|
+
protected readonly HEADER_LENGTH = 10;
|
|
16
|
+
protected abstract device_type: DeviceType;
|
|
17
|
+
protected abstract message_type: MessageType;
|
|
18
|
+
protected abstract body_type: number | null;
|
|
19
|
+
protected abstract device_protocol_version: number;
|
|
20
|
+
protected abstract body: Buffer;
|
|
21
|
+
checksum(data: Buffer): number;
|
|
22
|
+
}
|
|
23
|
+
export declare abstract class MessageRequest extends MessageBase {
|
|
24
|
+
device_type: DeviceType;
|
|
25
|
+
message_type: MessageType;
|
|
26
|
+
body_type: number | null;
|
|
27
|
+
device_protocol_version: number;
|
|
28
|
+
protected abstract _body: Buffer;
|
|
29
|
+
constructor(device_type: DeviceType, message_type: MessageType, body_type: number | null, device_protocol_version: number);
|
|
30
|
+
get body(): Buffer;
|
|
31
|
+
get header(): Buffer;
|
|
32
|
+
serialize(): Buffer;
|
|
33
|
+
}
|
|
34
|
+
export declare class MessageQuerySubtype extends MessageRequest {
|
|
35
|
+
protected _body: Buffer;
|
|
36
|
+
constructor(device_type: DeviceType);
|
|
37
|
+
}
|
|
38
|
+
export declare class MessageQuestCustom extends MessageRequest {
|
|
39
|
+
protected _body: Buffer;
|
|
40
|
+
protected cmd_body: Buffer;
|
|
41
|
+
constructor(device_type: DeviceType, message_type: MessageType, cmd_body: Buffer);
|
|
42
|
+
get body(): Buffer;
|
|
43
|
+
}
|
|
44
|
+
export declare class MessageBody {
|
|
45
|
+
readonly data: Buffer;
|
|
46
|
+
constructor(data: Buffer);
|
|
47
|
+
get body_type(): number;
|
|
48
|
+
static read_byte(data: Buffer, offset: number, default_value: number): number;
|
|
49
|
+
}
|
|
50
|
+
export declare class NewProtocolMessageBody extends MessageBody {
|
|
51
|
+
protected packet_length: number;
|
|
52
|
+
constructor(body: Buffer, body_type: number);
|
|
53
|
+
static packet(param: number, value: Buffer, packet_length?: number): Buffer;
|
|
54
|
+
parse(): {
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export declare class MessageResponse extends MessageBase {
|
|
59
|
+
protected header: Buffer;
|
|
60
|
+
protected device_type: DeviceType;
|
|
61
|
+
protected message_type: MessageType;
|
|
62
|
+
protected body_type: number;
|
|
63
|
+
device_protocol_version: number;
|
|
64
|
+
protected _body: MessageBody;
|
|
65
|
+
constructor(message: Buffer | null | undefined);
|
|
66
|
+
get body(): Buffer;
|
|
67
|
+
set_body(body: MessageBody): void;
|
|
68
|
+
get_body_type(): string;
|
|
69
|
+
get_body_attribute(name: string): any;
|
|
70
|
+
}
|
|
71
|
+
export declare class MessageSubtypeResponse extends MessageResponse {
|
|
72
|
+
sub_type: number;
|
|
73
|
+
constructor(message: Buffer | null | undefined);
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
76
|
//# sourceMappingURL=MideaMessage.d.ts.map
|