homebridge-melcloud-control 4.3.0-beta.23 → 4.3.0-beta.25

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: 'connect', sampling: 3600000 }, { name: 'checkDevicesList', sampling: 3000 }] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
197
+ const timmers = accountType === 'melcloudhome' ? [] : [{ 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.23",
4
+ "version": "4.3.0-beta.25",
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",
@@ -53,15 +53,92 @@ 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
+ const url = `wss://ws.melcloudhome.com/?hash=${deviceData.WsHeaders.hash}`;
88
+
89
+ try {
90
+ const socket = new WebSocket(url, { headers: deviceData.WsHeaders.headers })
91
+ .on('error', (err) => {
92
+ if (this.logDebug) this.emit('debug', `Socket error: ${err}`);
93
+ })
94
+ .on('close', () => {
95
+ if (this.logDebug) this.emit('debug', `Socket closed`);
96
+ this.reconnect();
97
+ })
98
+ .on('open', () => {
99
+ this.socket = socket;
100
+ this.socketConnected = true;
101
+ this.connecting = false;
102
+ this.reconnectDelay = 1000; // reset backoff
103
+ this.emit('success', `Socket Connect Success`);
104
+
105
+ // heartbeat
106
+ this.heartbeat = setInterval(() => {
107
+ if (socket.readyState === socket.OPEN) {
108
+ if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
109
+ socket.ping();
110
+ }
111
+ }, 30000);
112
+ })
113
+ .on('pong', () => {
114
+ if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
115
+ })
116
+ .on('message', (message) => {
117
+ const parsed = JSON.parse(message);
118
+ const msgData = parsed[0].Data;
119
+ const unitId = msgData.id;
120
+
121
+ if (!this.logDebug) {
122
+ const stringifyMessage = JSON.stringify(parsed, null, 2);
123
+ this.emit('warn', `Incoming message: ${stringifyMessage}`);
124
+ }
125
+
126
+ if (unitId === this.deviceId) {
127
+ const type = parsed[0].messageType;
128
+
129
+ const settings = Object.fromEntries(msgData.settings.map(s => [s.name, s.value]));
130
+ Object.assign(deviceData.Device, settings);
131
+
132
+ this.emit('deviceState', deviceData);
133
+ }
134
+ });
135
+
136
+ } catch (e) {
137
+ this.emit('debug', `Socket connection failed: ${e}`);
138
+ reconnect();
139
+ }
140
+ }
141
+
65
142
  async checkState() {
66
143
  try {
67
144
 
@@ -70,7 +147,6 @@ class MelCloudAta extends EventEmitter {
70
147
  if (!devicesData) return;
71
148
 
72
149
  this.headers = devicesData.Headers;
73
- this.hash = devicesData.Hash;
74
150
  const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
75
151
  if (this.accountType === 'melcloudhome') {
76
152
  deviceData.Scenes = devicesData.Scenes ?? [];
@@ -78,62 +154,14 @@ class MelCloudAta extends EventEmitter {
78
154
  deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
79
155
  deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
80
156
  deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
81
- deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
82
-
83
- if (!this.socketConnected) {
84
- const socket = new WebSocket(`wss://ws.melcloudhome.com/?hash=${devicesData.WsHeaders.hash}`, { headers: WsHeaders.headers })
85
- .on('error', (error) => {
86
- if (this.logDebug) this.emit('debug', `Socket error: ${error}`);
87
- socket.close();
88
- })
89
- .on('close', () => {
90
- if (this.logDebug) this.emit('debug', `Socket closed`);
91
- this.cleanupSocket();
92
- })
93
- .on('open', () => {
94
- // connect to device success
95
- this.socket = socket;
96
- this.socketConnected = true;
97
- this.emit('success', `Socket Connect Success`);
98
-
99
- // start heartbeat
100
- this.heartbeat = setInterval(() => {
101
- if (socket.readyState === socket.OPEN) {
102
- if (!this.logDebug) this.emit('warn', `Socket send heartbeat`);
103
- socket.ping();
104
- }
105
- }, 30000);
106
- })
107
- .on('pong', () => {
108
- if (!this.logDebug) this.emit('warn', `Socket received heartbeat`);
109
- })
110
- .on('message', (message) => {
111
- const parsedMessage = JSON.parse(message);
112
- const messageType = parsedMessage[0].messageType;
113
- const messageData = parsedMessage[0].Data;
114
- const unitId = messageData.id;
115
- const unitType = messageData.unitType;
116
- const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
117
- if (!this.logDebug) this.emit('warn', `Incoming message:', ${stringifyMessage}`);
118
-
119
- switch (unitId) {
120
- case this.deviceId:
121
- const settings = Object.fromEntries(messageData.settings.map(s => [s.name, s.value]));
122
- if (!this.logDebug) this.emit('warn', `Settings:', ${settings}`);
123
- Object.assign(deviceData.Device, settings);
124
-
125
- //emit state
126
- this.emit('deviceState', deviceData);
127
- break;
128
- }
129
- });
130
- }
157
+ deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection
131
158
 
132
159
  //read default temps
133
160
  const temps = await this.functions.readData(this.defaultTempsFile, true);
134
161
  deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
135
162
  deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
136
163
 
164
+ await this.connectSocket(deviceData);
137
165
  }
138
166
  if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
139
167