homebridge-melcloud-control 4.3.4 → 4.3.5-beta.1
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/index.js +3 -3
- package/package.json +1 -1
- package/src/deviceata.js +3 -2
- package/src/melcloudata.js +59 -85
- package/src/melcloudhome.js +58 -2
package/index.js
CHANGED
|
@@ -179,15 +179,15 @@ class MelCloudPlatform {
|
|
|
179
179
|
let configuredDevice;
|
|
180
180
|
switch (deviceType) {
|
|
181
181
|
case 0: //ATA
|
|
182
|
-
configuredDevice = new DeviceAta(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile);
|
|
182
|
+
configuredDevice = new DeviceAta(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile), devicesList.WebSocket;
|
|
183
183
|
break;
|
|
184
184
|
case 1: //ATW
|
|
185
|
-
configuredDevice = new DeviceAtw(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile);
|
|
185
|
+
configuredDevice = new DeviceAtw(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, devicesList.WebSocket);
|
|
186
186
|
break;
|
|
187
187
|
case 2:
|
|
188
188
|
break;
|
|
189
189
|
case 3: //ERV
|
|
190
|
-
configuredDevice = new DeviceErv(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile);
|
|
190
|
+
configuredDevice = new DeviceErv(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, devicesList.WebSocket);
|
|
191
191
|
break;
|
|
192
192
|
default:
|
|
193
193
|
if (logLevel.warn) log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, unknown device: ${deviceType}.`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.5-beta.1",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/deviceata.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, AirConditioner } from './constants.js';
|
|
|
7
7
|
let Accessory, Characteristic, Service, Categories, AccessoryUUID;
|
|
8
8
|
|
|
9
9
|
class DeviceAta extends EventEmitter {
|
|
10
|
-
constructor(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile) {
|
|
10
|
+
constructor(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, webSocket) {
|
|
11
11
|
super();
|
|
12
12
|
|
|
13
13
|
Accessory = api.platformAccessory;
|
|
@@ -17,6 +17,7 @@ class DeviceAta extends EventEmitter {
|
|
|
17
17
|
AccessoryUUID = api.hap.uuid;
|
|
18
18
|
|
|
19
19
|
//account config
|
|
20
|
+
this.webSocket = webSocket;
|
|
20
21
|
this.account = account;
|
|
21
22
|
this.accountType = account.type;
|
|
22
23
|
this.accountName = account.name;
|
|
@@ -1330,7 +1331,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1330
1331
|
async start() {
|
|
1331
1332
|
try {
|
|
1332
1333
|
//melcloud device
|
|
1333
|
-
this.melCloudAta = new MelCloudAta(this.account, this.device, this.devicesFile, this.defaultTempsFile, this.accountFile)
|
|
1334
|
+
this.melCloudAta = new MelCloudAta(this.account, this.device, this.devicesFile, this.defaultTempsFile, this.accountFile, this.webSocket)
|
|
1334
1335
|
.on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
|
|
1335
1336
|
if (this.logDeviceInfo && this.displayDeviceInfo) {
|
|
1336
1337
|
this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
|
package/src/melcloudata.js
CHANGED
|
@@ -6,7 +6,7 @@ import Functions from './functions.js';
|
|
|
6
6
|
import { ApiUrls, ApiUrlsHome, AirConditioner } from './constants.js';
|
|
7
7
|
|
|
8
8
|
class MelCloudAta extends EventEmitter {
|
|
9
|
-
constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
|
|
9
|
+
constructor(account, device, devicesFile, defaultTempsFile, accountFile, webSocket) {
|
|
10
10
|
super();
|
|
11
11
|
this.accountType = account.type;
|
|
12
12
|
this.logSuccess = account.log?.success;
|
|
@@ -19,6 +19,7 @@ class MelCloudAta extends EventEmitter {
|
|
|
19
19
|
this.devicesFile = devicesFile;
|
|
20
20
|
this.defaultTempsFile = defaultTempsFile;
|
|
21
21
|
this.accountFile = accountFile;
|
|
22
|
+
this.webSocket = webSocket;
|
|
22
23
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
23
24
|
.on('warn', warn => this.emit('warn', warn))
|
|
24
25
|
.on('error', error => this.emit('error', error))
|
|
@@ -155,95 +156,68 @@ class MelCloudAta extends EventEmitter {
|
|
|
155
156
|
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
156
157
|
|
|
157
158
|
//web cocket connection
|
|
158
|
-
if (this.accountType === 'melcloudhome' && !this.connecting
|
|
159
|
+
if (this.accountType === 'melcloudhome' && !this.connecting) {
|
|
159
160
|
this.connecting = true;
|
|
160
161
|
|
|
161
162
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
})
|
|
189
|
-
.on('message', async (message) => {
|
|
190
|
-
const parsedMessage = JSON.parse(message);
|
|
191
|
-
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
192
|
-
if (parsedMessage.message === 'Forbidden') return;
|
|
193
|
-
|
|
194
|
-
const messageData = parsedMessage?.[0]?.Data;
|
|
195
|
-
if (!messageData) return;
|
|
196
|
-
|
|
197
|
-
let updateState = false;
|
|
198
|
-
const unitId = messageData?.id;
|
|
199
|
-
switch (unitId) {
|
|
200
|
-
case this.deviceId:
|
|
201
|
-
if (!this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
202
|
-
const messageType = parsedMessage[0].messageType;
|
|
203
|
-
const settings = this.functions.parseArrayNameValue(messageData.settings);
|
|
204
|
-
switch (messageType) {
|
|
205
|
-
case 'unitStateChanged':
|
|
206
|
-
|
|
207
|
-
//update values
|
|
208
|
-
for (const [key, value] of Object.entries(settings)) {
|
|
209
|
-
if (!this.functions.isValidValue(value)) continue;
|
|
210
|
-
|
|
211
|
-
//update holiday mode
|
|
212
|
-
if (key === 'HolidayMode') {
|
|
213
|
-
deviceData.HolidayMode.Enabled = value;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
//update device settings
|
|
218
|
-
if (key in deviceData.Device) {
|
|
219
|
-
deviceData.Device[key] = value;
|
|
220
|
-
}
|
|
163
|
+
this.webSocket.on('message', async (message) => {
|
|
164
|
+
const parsedMessage = JSON.parse(message);
|
|
165
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
166
|
+
if (parsedMessage.message === 'Forbidden') return;
|
|
167
|
+
|
|
168
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
169
|
+
if (!messageData) return;
|
|
170
|
+
|
|
171
|
+
let updateState = false;
|
|
172
|
+
const unitId = messageData?.id;
|
|
173
|
+
switch (unitId) {
|
|
174
|
+
case this.deviceId:
|
|
175
|
+
if (!this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
176
|
+
const messageType = parsedMessage[0].messageType;
|
|
177
|
+
const settings = this.functions.parseArrayNameValue(messageData.settings);
|
|
178
|
+
switch (messageType) {
|
|
179
|
+
case 'unitStateChanged':
|
|
180
|
+
|
|
181
|
+
//update values
|
|
182
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
183
|
+
if (!this.functions.isValidValue(value)) continue;
|
|
184
|
+
|
|
185
|
+
//update holiday mode
|
|
186
|
+
if (key === 'HolidayMode') {
|
|
187
|
+
deviceData.HolidayMode.Enabled = value;
|
|
188
|
+
continue;
|
|
221
189
|
}
|
|
222
|
-
updateState = true;
|
|
223
|
-
break;
|
|
224
|
-
case 'unitHolidayModeTriggered':
|
|
225
|
-
deviceData.Device.Power = settings.Power;
|
|
226
|
-
deviceData.HolidayMode.Enabled = settings.HolidayMode;
|
|
227
|
-
deviceData.HolidayMode.Active = messageData.active;
|
|
228
|
-
updateState = true;
|
|
229
|
-
break;
|
|
230
|
-
case 'unitWifiSignalChanged':
|
|
231
|
-
deviceData.Rssi = messageData.rssi;
|
|
232
|
-
updateState = true;
|
|
233
|
-
break;
|
|
234
|
-
default:
|
|
235
|
-
if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
default:
|
|
240
|
-
if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
190
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
191
|
+
//update device settings
|
|
192
|
+
if (key in deviceData.Device) {
|
|
193
|
+
deviceData.Device[key] = value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
updateState = true;
|
|
197
|
+
break;
|
|
198
|
+
case 'unitHolidayModeTriggered':
|
|
199
|
+
deviceData.Device.Power = settings.Power;
|
|
200
|
+
deviceData.HolidayMode.Enabled = settings.HolidayMode;
|
|
201
|
+
deviceData.HolidayMode.Active = messageData.active;
|
|
202
|
+
updateState = true;
|
|
203
|
+
break;
|
|
204
|
+
case 'unitWifiSignalChanged':
|
|
205
|
+
deviceData.Rssi = messageData.rssi;
|
|
206
|
+
updateState = true;
|
|
207
|
+
break;
|
|
208
|
+
default:
|
|
209
|
+
if (!this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
if (!this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//update state
|
|
219
|
+
if (updateState) await this.updateState(deviceData);
|
|
220
|
+
});
|
|
247
221
|
} catch (error) {
|
|
248
222
|
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
249
223
|
this.cleanupSocket();
|
package/src/melcloudhome.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import axios from 'axios';
|
|
3
|
+
import WebSocket from 'ws';
|
|
3
4
|
import { exec } from 'child_process';
|
|
4
5
|
import { promisify } from 'util';
|
|
5
6
|
import EventEmitter from 'events';
|
|
@@ -25,6 +26,10 @@ class MelCloudHome extends EventEmitter {
|
|
|
25
26
|
this.devicesFile = devicesFile;
|
|
26
27
|
this.headers = {};
|
|
27
28
|
this.webSocketOptions = {};
|
|
29
|
+
this.socket = null;
|
|
30
|
+
this.connecting = false;
|
|
31
|
+
this.socketConnected = false;
|
|
32
|
+
this.heartbeat = null;
|
|
28
33
|
|
|
29
34
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
30
35
|
.on('warn', warn => this.emit('warn', warn))
|
|
@@ -63,6 +68,20 @@ class MelCloudHome extends EventEmitter {
|
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
cleanupSocket() {
|
|
72
|
+
if (this.heartbeat) {
|
|
73
|
+
clearInterval(this.heartbeat);
|
|
74
|
+
this.heartbeat = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (this.socket) {
|
|
78
|
+
try { this.socket.close(); } catch { }
|
|
79
|
+
this.socket = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.socketConnected = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
66
85
|
async checkScenesList() {
|
|
67
86
|
try {
|
|
68
87
|
if (this.logDebug) this.emit('debug', `Scanning for scenes`);
|
|
@@ -200,12 +219,50 @@ class MelCloudHome extends EventEmitter {
|
|
|
200
219
|
if (this.logDebug) this.emit('debug', `Get scenes error: ${error} `);
|
|
201
220
|
}
|
|
202
221
|
|
|
222
|
+
//web cocket connection
|
|
223
|
+
if (!this.connecting && !this.socketConnected) {
|
|
224
|
+
this.connecting = true;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const url = `${ApiUrlsHome.WebSocketURL}${this.webSocketOptions.Hash}`;
|
|
228
|
+
const socket = new WebSocket(url, { headers: this.webSocketOptions.Headers })
|
|
229
|
+
.on('error', (error) => {
|
|
230
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
231
|
+
socket.close();
|
|
232
|
+
})
|
|
233
|
+
.on('close', () => {
|
|
234
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
235
|
+
this.cleanupSocket();
|
|
236
|
+
})
|
|
237
|
+
.on('open', () => {
|
|
238
|
+
this.socket = socket;
|
|
239
|
+
this.socketConnected = true;
|
|
240
|
+
this.connecting = false;
|
|
241
|
+
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
242
|
+
|
|
243
|
+
// heartbeat
|
|
244
|
+
this.heartbeat = setInterval(() => {
|
|
245
|
+
if (socket.readyState === socket.OPEN) {
|
|
246
|
+
if (!this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
247
|
+
socket.ping();
|
|
248
|
+
}
|
|
249
|
+
}, 30000);
|
|
250
|
+
})
|
|
251
|
+
.on('pong', () => {
|
|
252
|
+
if (!this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
256
|
+
this.cleanupSocket();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
203
260
|
devicesList.State = true;
|
|
204
261
|
devicesList.Info = `Found ${devicesCount} devices and ${scenes.length} scenes`;
|
|
205
262
|
devicesList.Devices = devices;
|
|
206
263
|
devicesList.Scenes = scenes;
|
|
207
264
|
devicesList.Headers = this.headers;
|
|
208
|
-
devicesList.
|
|
265
|
+
devicesList.WebSocket = this.socket;
|
|
209
266
|
|
|
210
267
|
await this.functions.saveData(this.devicesFile, devicesList);
|
|
211
268
|
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
@@ -379,7 +436,6 @@ class MelCloudHome extends EventEmitter {
|
|
|
379
436
|
}
|
|
380
437
|
};
|
|
381
438
|
|
|
382
|
-
|
|
383
439
|
accountInfo.State = true;
|
|
384
440
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
385
441
|
await this.functions.saveData(this.accountFile, accountInfo);
|