homebridge-melcloud-control 4.3.2-beta.8 → 4.3.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 +7 -0
- package/package.json +1 -1
- package/src/functions.js +7 -0
- package/src/melcloudata.js +103 -93
- package/src/melcloudhome.js +1 -5
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
22
|
|
|
23
23
|
- Do not use Homebridge UI > v5.5.0 because of break config.json
|
|
24
24
|
|
|
25
|
+
# [4.3.2] - (21.11.2025)
|
|
26
|
+
|
|
27
|
+
## Changes
|
|
28
|
+
|
|
29
|
+
- stability improvements
|
|
30
|
+
- cleanup
|
|
31
|
+
|
|
25
32
|
# [4.3.0] - (20.11.2025)
|
|
26
33
|
|
|
27
34
|
## Changes
|
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.2
|
|
4
|
+
"version": "4.3.2",
|
|
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/functions.js
CHANGED
|
@@ -160,5 +160,12 @@ class Functions extends EventEmitter {
|
|
|
160
160
|
return v !== undefined && v !== null && !(typeof v === 'number' && Number.isNaN(v));
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
convertValue(v) {
|
|
164
|
+
let parsedValue = v;
|
|
165
|
+
if (v === "True") parsedValue = true;
|
|
166
|
+
else if (v === "False") parsedValue = false;
|
|
167
|
+
else if (!isNaN(v) && v !== "") parsedValue = Number(v);
|
|
168
|
+
return parsedValue;
|
|
169
|
+
}
|
|
163
170
|
}
|
|
164
171
|
export default Functions
|
package/src/melcloudata.js
CHANGED
|
@@ -70,17 +70,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
70
70
|
this.socketConnected = false;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async
|
|
73
|
+
async updateState(deviceData) {
|
|
74
74
|
try {
|
|
75
|
-
|
|
76
|
-
//read device info from file
|
|
77
|
-
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
78
|
-
if (!devicesData) return;
|
|
79
|
-
|
|
80
|
-
this.headers = devicesData.Headers;
|
|
81
|
-
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
82
|
-
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
83
|
-
|
|
84
75
|
if (this.accountType === 'melcloudhome') {
|
|
85
76
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
|
86
77
|
deviceData.Device.ActualFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
|
|
@@ -93,87 +84,6 @@ class MelCloudAta extends EventEmitter {
|
|
|
93
84
|
deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
|
|
94
85
|
deviceData.Device.DefaultCoolingSetTemperature = temps?.defaultCoolingSetTemperature ?? 24;
|
|
95
86
|
|
|
96
|
-
//web cocket connection
|
|
97
|
-
if (!this.connecting && !this.socketConnected) {
|
|
98
|
-
this.connecting = true;
|
|
99
|
-
|
|
100
|
-
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
|
|
101
|
-
try {
|
|
102
|
-
const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
|
|
103
|
-
.on('error', (error) => {
|
|
104
|
-
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
105
|
-
socket.close();
|
|
106
|
-
})
|
|
107
|
-
.on('close', () => {
|
|
108
|
-
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
109
|
-
this.cleanupSocket();
|
|
110
|
-
})
|
|
111
|
-
.on('open', () => {
|
|
112
|
-
this.socket = socket;
|
|
113
|
-
this.socketConnected = true;
|
|
114
|
-
this.connecting = false;
|
|
115
|
-
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
116
|
-
|
|
117
|
-
// heartbeat
|
|
118
|
-
this.heartbeat = setInterval(() => {
|
|
119
|
-
if (socket.readyState === socket.OPEN) {
|
|
120
|
-
if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
121
|
-
socket.ping();
|
|
122
|
-
}
|
|
123
|
-
}, 30000);
|
|
124
|
-
})
|
|
125
|
-
.on('pong', () => {
|
|
126
|
-
if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
127
|
-
})
|
|
128
|
-
.on('message', (message) => {
|
|
129
|
-
const parsedMessage = JSON.parse(message);
|
|
130
|
-
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
131
|
-
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
132
|
-
if (parsedMessage.message === 'Forbidden') return;
|
|
133
|
-
|
|
134
|
-
const messageData = parsedMessage?.[0]?.Data;
|
|
135
|
-
if (!messageData) return;
|
|
136
|
-
|
|
137
|
-
let updateDeviceState = false;
|
|
138
|
-
const unitId = messageData?.id;
|
|
139
|
-
switch (unitId) {
|
|
140
|
-
case this.deviceId:
|
|
141
|
-
const messageType = parsedMessage[0].messageType;
|
|
142
|
-
switch (messageType) {
|
|
143
|
-
case 'unitStateChanged':
|
|
144
|
-
const settings = Object.fromEntries(
|
|
145
|
-
messageData.settings.map(({ name, value }) => {
|
|
146
|
-
let parsedValue = value;
|
|
147
|
-
if (value === "True") parsedValue = true;
|
|
148
|
-
else if (value === "False") parsedValue = false;
|
|
149
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
150
|
-
return [name, parsedValue];
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
Object.assign(deviceData.Device, settings);
|
|
154
|
-
updateDeviceState = true;
|
|
155
|
-
break;
|
|
156
|
-
case 'unitWifiSignalChanged':
|
|
157
|
-
deviceData.Rssi = messageData.rssi;
|
|
158
|
-
updateDeviceState = true;
|
|
159
|
-
break;
|
|
160
|
-
default:
|
|
161
|
-
if (!this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
break;
|
|
165
|
-
default:
|
|
166
|
-
if (!this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (updateDeviceState) this.emit('deviceState', deviceData);
|
|
171
|
-
});
|
|
172
|
-
} catch (error) {
|
|
173
|
-
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
174
|
-
this.cleanupSocket();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
87
|
}
|
|
178
88
|
if (this.logDebug) this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`);
|
|
179
89
|
|
|
@@ -217,8 +127,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
217
127
|
}
|
|
218
128
|
|
|
219
129
|
//check state changes
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
130
|
+
const deviceDataNotChanged = JSON.stringify(deviceData) === JSON.stringify(this.deviceData);
|
|
131
|
+
if (deviceDataNotChanged) return;
|
|
222
132
|
this.deviceData = deviceData;
|
|
223
133
|
|
|
224
134
|
//emit info
|
|
@@ -227,6 +137,106 @@ class MelCloudAta extends EventEmitter {
|
|
|
227
137
|
//emit state
|
|
228
138
|
this.emit('deviceState', deviceData);
|
|
229
139
|
|
|
140
|
+
return true;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(`Update state error: ${error.message}`);
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
async checkState() {
|
|
147
|
+
try {
|
|
148
|
+
|
|
149
|
+
//read device info from file
|
|
150
|
+
const devicesData = await this.functions.readData(this.devicesFile, true);
|
|
151
|
+
if (!devicesData) return;
|
|
152
|
+
|
|
153
|
+
this.headers = devicesData.Headers;
|
|
154
|
+
const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
|
|
155
|
+
deviceData.Scenes = devicesData.Scenes ?? [];
|
|
156
|
+
|
|
157
|
+
//web cocket connection
|
|
158
|
+
if (this.accountType === 'melcloudhome' && !this.connecting && !this.socketConnected) {
|
|
159
|
+
this.connecting = true;
|
|
160
|
+
|
|
161
|
+
const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
|
|
162
|
+
try {
|
|
163
|
+
const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
|
|
164
|
+
.on('error', (error) => {
|
|
165
|
+
if (this.logError) this.emit('error', `Socket error: ${error}`);
|
|
166
|
+
socket.close();
|
|
167
|
+
})
|
|
168
|
+
.on('close', () => {
|
|
169
|
+
if (this.logDebug) this.emit('debug', `Socket closed`);
|
|
170
|
+
this.cleanupSocket();
|
|
171
|
+
})
|
|
172
|
+
.on('open', () => {
|
|
173
|
+
this.socket = socket;
|
|
174
|
+
this.socketConnected = true;
|
|
175
|
+
this.connecting = false;
|
|
176
|
+
if (this.logSuccess) this.emit('success', `Socket Connect Success`);
|
|
177
|
+
|
|
178
|
+
// heartbeat
|
|
179
|
+
this.heartbeat = setInterval(() => {
|
|
180
|
+
if (socket.readyState === socket.OPEN) {
|
|
181
|
+
if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
|
|
182
|
+
socket.ping();
|
|
183
|
+
}
|
|
184
|
+
}, 30000);
|
|
185
|
+
})
|
|
186
|
+
.on('pong', () => {
|
|
187
|
+
if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
|
|
188
|
+
})
|
|
189
|
+
.on('message', async (message) => {
|
|
190
|
+
const parsedMessage = JSON.parse(message);
|
|
191
|
+
const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
|
|
192
|
+
if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
|
|
193
|
+
if (parsedMessage.message === 'Forbidden') return;
|
|
194
|
+
|
|
195
|
+
const messageData = parsedMessage?.[0]?.Data;
|
|
196
|
+
if (!messageData) return;
|
|
197
|
+
|
|
198
|
+
let updateState = false;
|
|
199
|
+
const unitId = messageData?.id;
|
|
200
|
+
switch (unitId) {
|
|
201
|
+
case this.deviceId:
|
|
202
|
+
const messageType = parsedMessage[0].messageType;
|
|
203
|
+
switch (messageType) {
|
|
204
|
+
case 'unitStateChanged':
|
|
205
|
+
const settings = Object.fromEntries(
|
|
206
|
+
messageData.settings.map(({ name, value }) => {
|
|
207
|
+
let parsedValue = this.functions.convertValue(value);
|
|
208
|
+
return [name, parsedValue];
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
Object.assign(deviceData.Device, settings);
|
|
212
|
+
updateState = true;
|
|
213
|
+
break;
|
|
214
|
+
case 'unitWifiSignalChanged':
|
|
215
|
+
deviceData.Rssi = messageData.rssi;
|
|
216
|
+
updateState = true;
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
default:
|
|
224
|
+
if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
//update state
|
|
229
|
+
if (updateState) await this.updateState(deviceData);
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
|
|
233
|
+
this.cleanupSocket();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//update state
|
|
238
|
+
await this.updateState(deviceData);
|
|
239
|
+
|
|
230
240
|
return true;
|
|
231
241
|
} catch (error) {
|
|
232
242
|
throw new Error(`Check state error: ${error.message}`);
|
package/src/melcloudhome.js
CHANGED
|
@@ -145,11 +145,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
145
145
|
|
|
146
146
|
const settingsObject = Object.fromEntries(
|
|
147
147
|
settingsArray.map(({ name, value }) => {
|
|
148
|
-
let parsedValue = value;
|
|
149
|
-
if (value === "True") parsedValue = true;
|
|
150
|
-
else if (value === "False") parsedValue = false;
|
|
151
|
-
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
152
|
-
|
|
148
|
+
let parsedValue = this.functions.convertValue(value);
|
|
153
149
|
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
154
150
|
return [key, parsedValue];
|
|
155
151
|
})
|