homebridge-melcloud-control 4.3.0-beta.24 → 4.3.0-beta.26

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 CHANGED
@@ -194,7 +194,7 @@ class MelCloudPlatform {
194
194
 
195
195
  //start impulse generators\
196
196
  await configuredDevice.startStopImpulseGenerator(true, [{ name: 'checkState', sampling: deviceRefreshInterval }]);
197
- const timmers = accountType === 'melcloudhome' ? [] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
197
+ const timmers = accountType === 'melcloudhome' ? [{ name: 'connect', sampling: 3600000 }, { name: 'checkDevicesList', sampling: 3000 }] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
198
198
  await melCloud.impulseGenerator.state(true, timmers, false);
199
199
 
200
200
  //stop impulse generator
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.0-beta.24",
4
+ "version": "4.3.0-beta.26",
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/constants.js CHANGED
@@ -12,6 +12,7 @@ export const ApiUrls = {
12
12
  SetAta: "/Device/SetAta",
13
13
  SetAtw: "/Device/SetAtw",
14
14
  SetErv: "/Device/SetErv",
15
+ GetRefreshUnit: "/Device/RequestRefresh?id=deviceid",
15
16
  UpdateApplicationOptions: "/User/UpdateApplicationOptions",
16
17
  HolidayModeUpdate: "/HolidayMode/Update",
17
18
  EnergyCostReport: "/EnergyCost/Report",
@@ -42,7 +43,8 @@ export const ApiUrlsHome = {
42
43
  PutDeviceSettings: "https://melcloudhome.com/dashboard",
43
44
  PutScheduleEnabled: "https://melcloudhome.com/ata/deviceid/schedule",
44
45
  },
45
- Origin: "https://melcloudhome.com"
46
+ Origin: "https://melcloudhome.com",
47
+ WebSocketURL: "wss://ws.melcloudhome.com/?hash="
46
48
  };
47
49
 
48
50
  export const DeviceType = [
@@ -53,15 +53,90 @@ class MelCloudAta extends EventEmitter {
53
53
  }
54
54
  }
55
55
 
56
- cleanupSocket = () => {
56
+ cleanupSocket() {
57
57
  if (this.heartbeat) {
58
58
  clearInterval(this.heartbeat);
59
59
  this.heartbeat = null;
60
60
  }
61
- this.socket = null;
61
+
62
+ if (this.socket) {
63
+ try { this.socket.close(); } catch { }
64
+ this.socket = null;
65
+ }
66
+
62
67
  this.socketConnected = false;
68
+ }
69
+
70
+ reconnect() {
71
+ this.cleanupSocket();
72
+
73
+ const delay = Math.min(this.reconnectDelay, 30000); // max 30s
74
+ if (this.logDebug) this.emit('debug', `Reconnect in ${delay}ms`);
75
+
76
+ setTimeout(() => {
77
+ this.reconnectDelay *= 2; // exponential backoff
78
+ this.reconnectDelay = Math.max(1000, this.reconnectDelay);
79
+ this.connectSocket(deviceData);
80
+ }, delay);
63
81
  };
64
82
 
83
+ async connectSocket(deviceData) {
84
+ if (this.connecting || this.socketConnected) return;
85
+
86
+ this.connecting = true;
87
+ try {
88
+ const socket = new WebSocket(`ApiUrlsHome.WebSocketURL${deviceData.WsHeaders.hash}`, { headers: deviceData.WsHeaders.headers })
89
+ .on('error', (err) => {
90
+ if (this.logDebug) this.emit('debug', `Socket error: ${err}`);
91
+ })
92
+ .on('close', () => {
93
+ if (this.logDebug) this.emit('debug', `Socket closed`);
94
+ this.reconnect();
95
+ })
96
+ .on('open', () => {
97
+ this.socket = socket;
98
+ this.socketConnected = true;
99
+ this.connecting = false;
100
+ this.reconnectDelay = 1000;
101
+ this.emit('success', `Socket Connect Success`);
102
+
103
+ // heartbeat
104
+ this.heartbeat = setInterval(() => {
105
+ if (socket.readyState === socket.OPEN) {
106
+ if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
107
+ socket.ping();
108
+ }
109
+ }, 30000);
110
+ })
111
+ .on('pong', () => {
112
+ if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
113
+ })
114
+ .on('message', (message) => {
115
+ const parsed = JSON.parse(message);
116
+ const msgData = parsed[0].Data;
117
+ const unitId = msgData.id;
118
+
119
+ if (!this.logDebug) {
120
+ const stringifyMessage = JSON.stringify(parsed, null, 2);
121
+ this.emit('warn', `Incoming message: ${stringifyMessage}`);
122
+ }
123
+
124
+ if (unitId === this.deviceId) {
125
+ const type = parsed[0].messageType;
126
+
127
+ const settings = Object.fromEntries(msgData.settings.map(s => [s.name, s.value]));
128
+ Object.assign(deviceData.Device, settings);
129
+
130
+ this.emit('deviceState', deviceData);
131
+ }
132
+ });
133
+
134
+ } catch (e) {
135
+ this.emit('debug', `Socket connection failed: ${e}`);
136
+ reconnect();
137
+ }
138
+ }
139
+
65
140
  async checkState() {
66
141
  try {
67
142
 
@@ -77,72 +152,14 @@ class MelCloudAta extends EventEmitter {
77
152
  deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
78
153
  deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
79
154
  deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
80
- deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
81
-
82
- if (!this.socketConnected) {
83
- const socket = new WebSocket(`wss://ws.melcloudhome.com/?hash=${devicesData.WsHeaders.hash}`, { headers: devicesData.WsHeaders.headers })
84
- .on('error', (error) => {
85
- if (this.logDebug) this.emit('debug', `Socket error: ${error}`);
86
- socket.close();
87
- })
88
- .on('close', () => {
89
- if (this.logDebug) this.emit('debug', `Socket closed`);
90
- this.cleanupSocket();
91
- })
92
- .on('open', () => {
93
- // connect to device success
94
- this.socket = socket;
95
- this.socketConnected = true;
96
- this.emit('success', `Socket Connect Success`);
97
-
98
- // start heartbeat
99
- this.heartbeat = setInterval(() => {
100
- if (socket.readyState === socket.OPEN) {
101
- if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
102
- socket.ping();
103
- }
104
- }, 30000);
105
- })
106
- .on('pong', () => {
107
- if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
108
- })
109
- .on('message', (message) => {
110
- const parsedMessage = JSON.parse(message);
111
- const messageData = parsedMessage[0].Data;
112
- const unitId = messageData.id;
113
- const unitType = messageData.unitType;
114
- const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
115
- if (!this.logDebug) this.emit('warn', `Incoming message:', ${stringifyMessage}`);
116
-
117
- switch (unitId) {
118
- case this.deviceId:
119
- const messageType = parsedMessage[0].messageType;
120
- switch (messageType) {
121
- case 'unitStateChanged':
122
- const settings = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
123
- Object.assign(deviceData.Device, settings);
124
-
125
- //emit state
126
- this.emit('deviceState', deviceData);
127
- break;
128
- case 'unitWiFiChanged':
129
- const state = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
130
- Object.assign(deviceData.Device, state);
131
-
132
- //emit state
133
- this.emit('deviceState', deviceData);
134
- break;
135
- }
136
- break;
137
- }
138
- });
139
- }
155
+ deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
140
156
 
141
157
  //read default temps
142
158
  const temps = await this.functions.readData(this.defaultTempsFile, true);
143
159
  deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
144
160
  deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
145
161
 
162
+ await this.connectSocket(deviceData);
146
163
  }
147
164
  if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
148
165
 
@@ -300,6 +317,11 @@ class MelCloudAta extends EventEmitter {
300
317
  this.headers.Referer = ApiUrlsHome.Referers.GetPutScenes;
301
318
  break;
302
319
  default:
320
+ if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
321
+ if (this.logDebug) this.emit('debug', `Socket not open, cannot send`);
322
+ return false;
323
+ }
324
+
303
325
  if (displayType === 1 && deviceData.Device.OperationMode === 8) {
304
326
  deviceData.Device.SetTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
305
327
 
@@ -325,7 +347,14 @@ class MelCloudAta extends EventEmitter {
325
347
  method = 'PUT';
326
348
  path = ApiUrlsHome.PutAta.replace('deviceid', deviceData.DeviceID);
327
349
  this.headers.Referer = ApiUrlsHome.Referers.PutDeviceSettings;
328
- break
350
+
351
+ try {
352
+ this.socket.send(JSON.stringify(payload));
353
+ return true;
354
+ } catch (err) {
355
+ if (this.logDebug) this.emit('debug', `Socket send error: ${err}`);
356
+ return false;
357
+ }
329
358
  }
330
359
 
331
360
  this.headers['Content-Type'] = 'application/json; charset=utf-8';