homebridge-melcloud-control 4.0.0-beta.9 → 4.0.0-beta.90
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 +9 -0
- package/README.md +4 -3
- package/config.schema.json +36 -8
- package/homebridge-ui/public/index.html +65 -32
- package/homebridge-ui/server.js +4 -3
- package/index.js +12 -15
- package/package.json +1 -1
- package/src/constants.js +4 -3
- package/src/deviceata.js +18 -17
- package/src/melcloud.js +159 -106
- package/src/melcloudata.js +3 -175
- package/src/melcloudatw.js +0 -225
- package/src/melclouderv.js +0 -157
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
- do not configure it manually, always using Config UI X
|
|
17
17
|
- required Homebridge v2.0.0 and above
|
|
18
18
|
|
|
19
|
+
## [4.0.0] - (xx.10.2025)
|
|
20
|
+
|
|
21
|
+
## Changes
|
|
22
|
+
|
|
23
|
+
- added support for MELCloud Home [#215](https://github.com/grzegorz914/homebridge-melcloud-control/issues/215)
|
|
24
|
+
- redme updated
|
|
25
|
+
- config schema updated
|
|
26
|
+
- cleanup
|
|
27
|
+
|
|
19
28
|
## [3.9.5] - (02.09.2025)
|
|
20
29
|
|
|
21
30
|
## Changes
|
package/README.md
CHANGED
|
@@ -204,9 +204,10 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
204
204
|
| Key | Description |
|
|
205
205
|
| --- | --- |
|
|
206
206
|
| `name` | Here set the own account name. |
|
|
207
|
-
| `user` | Here set the
|
|
208
|
-
| `passwd` | Here set the
|
|
209
|
-
| `language` | Here select the
|
|
207
|
+
| `user` | Here set the account username. |
|
|
208
|
+
| `passwd` | Here set the account password. |
|
|
209
|
+
| `language` | Here select the account language. |
|
|
210
|
+
| `displayMode` | Here select the account type `None/Disabled`, `MELCloud`, `MELCloud Home`. |
|
|
210
211
|
| `ataDevices[]` | Array of ATA devices created automatically after login to MELCloud from plugin config UI. |
|
|
211
212
|
| `ataDevices[].id` | Read only data, do not change it. |
|
|
212
213
|
| `ataDevices[].type` | Read only data, do not change it. |
|
package/config.schema.json
CHANGED
|
@@ -201,6 +201,32 @@
|
|
|
201
201
|
}
|
|
202
202
|
]
|
|
203
203
|
},
|
|
204
|
+
"displayType": {
|
|
205
|
+
"title": "Account Type",
|
|
206
|
+
"type": "string",
|
|
207
|
+
"default": "disabled",
|
|
208
|
+
"description": "Here select the language used in MELCloud account.",
|
|
209
|
+
"oneOf": [
|
|
210
|
+
{
|
|
211
|
+
"title": "None/Disabled",
|
|
212
|
+
"enum": [
|
|
213
|
+
"disabled"
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"title": "MELCLoud",
|
|
218
|
+
"enum": [
|
|
219
|
+
"melcloud"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"title": "MELCLoud Home",
|
|
224
|
+
"enum": [
|
|
225
|
+
"melcloudhome"
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
204
230
|
"ataDevices": {
|
|
205
231
|
"title": "Devices ATA",
|
|
206
232
|
"type": "array",
|
|
@@ -209,8 +235,8 @@
|
|
|
209
235
|
"properties": {
|
|
210
236
|
"id": {
|
|
211
237
|
"title": "ID",
|
|
212
|
-
"type": "
|
|
213
|
-
"default": 0,
|
|
238
|
+
"type": "string",
|
|
239
|
+
"default": "0",
|
|
214
240
|
"readonly": true
|
|
215
241
|
},
|
|
216
242
|
"type": {
|
|
@@ -778,8 +804,8 @@
|
|
|
778
804
|
"properties": {
|
|
779
805
|
"id": {
|
|
780
806
|
"title": "ID",
|
|
781
|
-
"type": "
|
|
782
|
-
"default": 0,
|
|
807
|
+
"type": "string",
|
|
808
|
+
"default": "0",
|
|
783
809
|
"readonly": true
|
|
784
810
|
},
|
|
785
811
|
"type": {
|
|
@@ -1313,8 +1339,8 @@
|
|
|
1313
1339
|
"properties": {
|
|
1314
1340
|
"id": {
|
|
1315
1341
|
"title": "ID",
|
|
1316
|
-
"type": "
|
|
1317
|
-
"default": 0,
|
|
1342
|
+
"type": "string",
|
|
1343
|
+
"default": "0",
|
|
1318
1344
|
"readonly": true
|
|
1319
1345
|
},
|
|
1320
1346
|
"type": {
|
|
@@ -1798,7 +1824,8 @@
|
|
|
1798
1824
|
"name",
|
|
1799
1825
|
"user",
|
|
1800
1826
|
"passwd",
|
|
1801
|
-
"language"
|
|
1827
|
+
"language",
|
|
1828
|
+
"displayType"
|
|
1802
1829
|
]
|
|
1803
1830
|
}
|
|
1804
1831
|
}
|
|
@@ -1817,13 +1844,14 @@
|
|
|
1817
1844
|
"type": "password"
|
|
1818
1845
|
},
|
|
1819
1846
|
"accounts[].language",
|
|
1847
|
+
"accounts[].displayType",
|
|
1820
1848
|
{
|
|
1821
1849
|
"key": "accounts[]",
|
|
1822
1850
|
"type": "tabarray",
|
|
1823
1851
|
"title": "{{ value.title }}",
|
|
1824
1852
|
"items": [
|
|
1825
1853
|
{
|
|
1826
|
-
"key": "accounts[]",
|
|
1854
|
+
"key": "accounts[].ataDevices",
|
|
1827
1855
|
"title": "Air Conditioner",
|
|
1828
1856
|
"items": [
|
|
1829
1857
|
{
|
|
@@ -76,41 +76,53 @@
|
|
|
76
76
|
</select>
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
+
<div class="mb-2">
|
|
80
|
+
<label for="displayType" class="form-label">Account Type</label>
|
|
81
|
+
<select id="displayType" name="displayType" class="form-control">
|
|
82
|
+
<option value="disabled">None/Disabled</option>
|
|
83
|
+
<option value="melcloud">MELCloud</option>
|
|
84
|
+
<option value="melcloudhome">MELCloud Home</option>
|
|
85
|
+
</select>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
79
88
|
<div class="text-center">
|
|
80
89
|
<button id="logIn" type="button" class="btn btn-secondary">Connect to MELCloud</button>
|
|
81
90
|
<button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
|
|
82
91
|
</div>
|
|
83
92
|
</form>
|
|
93
|
+
<div id="accountButton" class="text-center mt-2"></div>
|
|
84
94
|
</div>
|
|
85
|
-
|
|
86
|
-
<div id="accountButton" class="mt-2"></div>
|
|
87
95
|
</div>
|
|
88
96
|
|
|
89
97
|
<script>
|
|
90
98
|
(async () => {
|
|
91
99
|
const pluginConfig = await homebridge.getPluginConfig();
|
|
100
|
+
|
|
101
|
+
// Ensure proper structure on load
|
|
92
102
|
if (!pluginConfig.length) {
|
|
93
|
-
pluginConfig.push({});
|
|
103
|
+
pluginConfig.push({ accounts: [] });
|
|
94
104
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
95
105
|
homebridge.showSchemaForm();
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
108
|
+
pluginConfig[0].accounts ??= [];
|
|
98
109
|
|
|
99
|
-
this.
|
|
100
|
-
|
|
110
|
+
this.accountIndex = 0;
|
|
101
111
|
const accountsCount = pluginConfig[0].accounts.length;
|
|
112
|
+
|
|
102
113
|
for (let i = 0; i < accountsCount; i++) {
|
|
114
|
+
const acc = pluginConfig[0].accounts[i];
|
|
103
115
|
const button = document.createElement("button");
|
|
104
116
|
button.type = "button";
|
|
105
117
|
button.id = `button${i}`;
|
|
106
118
|
button.className = "btn btn-primary";
|
|
107
119
|
button.style.textTransform = 'none';
|
|
108
|
-
button.innerText =
|
|
120
|
+
button.innerText = acc.name;
|
|
109
121
|
document.getElementById("accountButton").appendChild(button);
|
|
110
122
|
|
|
111
123
|
button.addEventListener('click', async () => {
|
|
112
124
|
for (let j = 0; j < accountsCount; j++) {
|
|
113
|
-
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-
|
|
125
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
const acc = pluginConfig[0].accounts[i];
|
|
@@ -118,24 +130,30 @@
|
|
|
118
130
|
document.getElementById('name').value = acc.name || '';
|
|
119
131
|
document.getElementById('user').value = acc.user || '';
|
|
120
132
|
document.getElementById('passwd').value = acc.passwd || '';
|
|
121
|
-
document.getElementById('language').value = acc.language || '';
|
|
122
|
-
document.getElementById('
|
|
123
|
-
|
|
133
|
+
document.getElementById('language').value = acc.language || '0';
|
|
134
|
+
document.getElementById('displayType').value = acc.displayType || 'disabled';
|
|
135
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
124
136
|
});
|
|
125
137
|
|
|
126
|
-
if (i === accountsCount - 1
|
|
138
|
+
if (i === accountsCount - 1 && accountsCount > 0)
|
|
139
|
+
document.getElementById(`button0`).click();
|
|
140
|
+
|
|
141
|
+
this.accountIndex = i;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
document.getElementById('melCloudAccount').style.display = 'block';
|
|
130
145
|
|
|
146
|
+
// Safe update
|
|
131
147
|
document.getElementById('configForm').addEventListener('input', async () => {
|
|
132
|
-
const acc = pluginConfig[0].accounts[this.
|
|
148
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
149
|
+
if (!acc) return;
|
|
133
150
|
acc.name = document.querySelector('#name').value;
|
|
134
151
|
acc.user = document.querySelector('#user').value;
|
|
135
152
|
acc.passwd = document.querySelector('#passwd').value;
|
|
136
153
|
acc.language = document.querySelector('#language').value;
|
|
154
|
+
acc.displayType = document.querySelector('#displayType').value;
|
|
137
155
|
|
|
138
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
156
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
139
157
|
|
|
140
158
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
141
159
|
await homebridge.savePluginConfig(pluginConfig);
|
|
@@ -167,9 +185,10 @@
|
|
|
167
185
|
function removeStaleDevices(configDevices, melcloudDevices) {
|
|
168
186
|
const melcloudIds = melcloudDevices.map(d => d.DeviceID);
|
|
169
187
|
const removedDevices = [];
|
|
188
|
+
|
|
170
189
|
for (let i = configDevices.length - 1; i >= 0; i--) {
|
|
171
190
|
const device = configDevices[i];
|
|
172
|
-
if (device.id !== 0 && !melcloudIds.includes(device.id)) {
|
|
191
|
+
if (device.id !== "0" && !melcloudIds.includes(device.id)) {
|
|
173
192
|
removedDevices.push(device);
|
|
174
193
|
configDevices.splice(i, 1);
|
|
175
194
|
}
|
|
@@ -191,8 +210,8 @@
|
|
|
191
210
|
updateInfo('info', 'Connecting...', 'yellow');
|
|
192
211
|
|
|
193
212
|
try {
|
|
194
|
-
const acc = pluginConfig[0].accounts[this.
|
|
195
|
-
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
213
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
214
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language, displayType: acc.displayType };
|
|
196
215
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
197
216
|
|
|
198
217
|
// Initialize devices arrays
|
|
@@ -213,24 +232,36 @@
|
|
|
213
232
|
const removedAtw = removeStaleDevices(acc.atwDevices, devicesByType.atw);
|
|
214
233
|
const removedErv = removeStaleDevices(acc.ervDevices, devicesByType.erv);
|
|
215
234
|
|
|
216
|
-
// Function to handle device & presets
|
|
217
235
|
const handleDevices = (devicesInCloud, devicesInConfig, typeString, newArr, newPresets) => {
|
|
218
236
|
devicesInCloud.forEach(device => {
|
|
219
237
|
const { DeviceID: id, Type: type, DeviceName: name, Presets: presets = [] } = device;
|
|
220
|
-
const devObj = {
|
|
238
|
+
const devObj = structuredClone({
|
|
239
|
+
id,
|
|
240
|
+
type,
|
|
241
|
+
typeString,
|
|
242
|
+
name,
|
|
243
|
+
displayMode: 1,
|
|
244
|
+
presets: [],
|
|
245
|
+
buttonsSensors: []
|
|
246
|
+
});
|
|
247
|
+
|
|
221
248
|
if (!devicesInConfig.some(d => d.id === id)) {
|
|
222
249
|
devicesInConfig.push(devObj);
|
|
223
|
-
newArr.push(devObj);
|
|
250
|
+
newArr.push(structuredClone(devObj));
|
|
224
251
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
p
|
|
228
|
-
|
|
229
|
-
|
|
252
|
+
|
|
253
|
+
presets.forEach(preset => {
|
|
254
|
+
const p = structuredClone({
|
|
255
|
+
id: preset.ID,
|
|
256
|
+
name: preset.NumberDescription,
|
|
257
|
+
displayType: 0,
|
|
258
|
+
namePrefix: false
|
|
259
|
+
});
|
|
260
|
+
|
|
230
261
|
const devConfig = devicesInConfig.find(d => d.id === id);
|
|
231
|
-
if (devConfig && !devConfig.presets.some(x => x.id === p.
|
|
262
|
+
if (devConfig && !devConfig.presets.some(x => x.id === p.id)) {
|
|
232
263
|
devConfig.presets.push(p);
|
|
233
|
-
newPresets.push(p);
|
|
264
|
+
newPresets.push(structuredClone(p));
|
|
234
265
|
}
|
|
235
266
|
});
|
|
236
267
|
});
|
|
@@ -238,9 +269,8 @@
|
|
|
238
269
|
|
|
239
270
|
handleDevices(devicesByType.ata, acc.ataDevices, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
|
|
240
271
|
handleDevices(devicesByType.atw, acc.atwDevices, "Heat Pump", newDevices.atw, newDevices.atwPresets);
|
|
241
|
-
handleDevices(devicesByType.erv, acc.ervDevices, "Energy Recovery
|
|
272
|
+
handleDevices(devicesByType.erv, acc.ervDevices, "Energy Recovery Ventilation", newDevices.erv, newDevices.ervPresets);
|
|
242
273
|
|
|
243
|
-
// Display summary
|
|
244
274
|
const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
|
|
245
275
|
const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
|
|
246
276
|
const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
|
|
@@ -248,9 +278,12 @@
|
|
|
248
278
|
if (!newDevicesCount && !newPresetsCount && !removedDevicesCount) {
|
|
249
279
|
updateInfo('info', 'No changes detected.', 'white');
|
|
250
280
|
} else {
|
|
251
|
-
if (newDevicesCount)
|
|
252
|
-
|
|
253
|
-
if (
|
|
281
|
+
if (newDevicesCount)
|
|
282
|
+
updateInfo('info', `Found new devices: ATA: ${newDevices.ata.length}, ATW: ${newDevices.atw.length}, ERV: ${newDevices.erv.length}.`, 'green');
|
|
283
|
+
if (newPresetsCount)
|
|
284
|
+
updateInfo('info1', `Found new presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}.`, 'green');
|
|
285
|
+
if (removedDevicesCount)
|
|
286
|
+
updateInfo('info2', `Removed devices: ATA: ${removedAta.length}, ATW: ${removedAtw.length}, ERV: ${removedErv.length}.`, 'orange');
|
|
254
287
|
}
|
|
255
288
|
|
|
256
289
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
@@ -259,7 +292,7 @@
|
|
|
259
292
|
|
|
260
293
|
} catch (error) {
|
|
261
294
|
updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
|
|
262
|
-
updateInfo('info1', `Error: ${error}`, 'red');
|
|
295
|
+
updateInfo('info1', `Error: ${JSON.stringify(error)}`, 'red');
|
|
263
296
|
document.getElementById('logIn').className = "btn btn-secondary";
|
|
264
297
|
} finally {
|
|
265
298
|
homebridge.hideSpinner();
|
package/homebridge-ui/server.js
CHANGED
|
@@ -17,14 +17,15 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
17
17
|
const user = payload.user;
|
|
18
18
|
const passwd = payload.passwd;
|
|
19
19
|
const language = payload.language;
|
|
20
|
+
const displayType = payload.displayType;
|
|
20
21
|
const accountFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Account`;
|
|
21
22
|
const buildingsFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Buildings`;
|
|
22
23
|
const devicesFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Devices`;
|
|
23
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
|
|
24
|
+
const melCloud = new MelCloud(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
|
|
24
25
|
|
|
25
26
|
try {
|
|
26
|
-
const
|
|
27
|
-
const devices = await melCloud.checkDevicesList(
|
|
27
|
+
const accountInfo = await melCloud.connect();
|
|
28
|
+
const devices = await melCloud.checkDevicesList(accountInfo.ContextKey);
|
|
28
29
|
return devices;
|
|
29
30
|
} catch (error) {
|
|
30
31
|
throw new Error(`MELCloud error: ${error.message ?? error}.`);
|
package/index.js
CHANGED
|
@@ -30,6 +30,9 @@ class MelCloudPlatform {
|
|
|
30
30
|
api.on('didFinishLaunching', async () => {
|
|
31
31
|
//loop through accounts
|
|
32
32
|
for (const account of config.accounts) {
|
|
33
|
+
const displayType = account.displayType || 'disabled';
|
|
34
|
+
if (displayType === 'disabled') continue;
|
|
35
|
+
|
|
33
36
|
const accountName = account.name;
|
|
34
37
|
const user = account.user;
|
|
35
38
|
const passwd = account.passwd;
|
|
@@ -64,7 +67,7 @@ class MelCloudPlatform {
|
|
|
64
67
|
passwd: 'removed',
|
|
65
68
|
mqtt: {
|
|
66
69
|
auth: {
|
|
67
|
-
...
|
|
70
|
+
...account.mqtt?.auth,
|
|
68
71
|
passwd: 'removed',
|
|
69
72
|
}
|
|
70
73
|
},
|
|
@@ -76,7 +79,6 @@ class MelCloudPlatform {
|
|
|
76
79
|
const accountFile = `${prefDir}/${accountName}_Account`;
|
|
77
80
|
const buildingsFile = `${prefDir}/${accountName}_Buildings`;
|
|
78
81
|
const devicesFile = `${prefDir}/${accountName}_Devices`;
|
|
79
|
-
const cookiesFile = `${prefDir}/${accountName}cookies`;
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
//set account refresh interval
|
|
@@ -88,31 +90,26 @@ class MelCloudPlatform {
|
|
|
88
90
|
.on('start', async () => {
|
|
89
91
|
try {
|
|
90
92
|
//melcloud account
|
|
91
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
93
|
+
const melCloud = new MelCloud(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logLevel.warn, logLevel.debug, false)
|
|
92
94
|
.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
|
|
93
95
|
.on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
|
|
94
96
|
.on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
|
|
95
97
|
.on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${msg}`))
|
|
96
98
|
.on('error', (msg) => logLevel.error && log.error(`${accountName}, ${msg}`));
|
|
97
99
|
|
|
98
|
-
await melCloud.connectHomeCookies()
|
|
99
|
-
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
100
|
//connect
|
|
103
|
-
let
|
|
101
|
+
let accountInfo;
|
|
104
102
|
try {
|
|
105
|
-
|
|
103
|
+
accountInfo = await melCloud.connect();
|
|
106
104
|
} catch (error) {
|
|
107
105
|
if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
|
|
108
106
|
return;
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const useFahrenheit = response.useFahrenheit ?? false;
|
|
109
|
+
const contextKey = accountInfo.ContextKey;
|
|
110
|
+
const useFahrenheit = accountInfo.UseFahrenheit;
|
|
114
111
|
|
|
115
|
-
if (contextKey
|
|
112
|
+
if (!contextKey) {
|
|
116
113
|
return;
|
|
117
114
|
}
|
|
118
115
|
|
|
@@ -135,9 +132,9 @@ class MelCloudPlatform {
|
|
|
135
132
|
|
|
136
133
|
for (const device of devices) {
|
|
137
134
|
//chack device from config exist on melcloud
|
|
138
|
-
|
|
135
|
+
device.id = displayType === 'melcloud' ? parseInt(device.id) : device.id;
|
|
139
136
|
const displayMode = device.displayMode > 0;
|
|
140
|
-
const deviceExistInMelCloud = devicesInMelcloud.some(dev => dev.DeviceID ===
|
|
137
|
+
const deviceExistInMelCloud = devicesInMelcloud.some(dev => dev.DeviceID === device.id);
|
|
141
138
|
if (!deviceExistInMelCloud || !displayMode) {
|
|
142
139
|
continue;
|
|
143
140
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.0.0-beta.
|
|
4
|
+
"version": "4.0.0-beta.90",
|
|
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
|
@@ -18,7 +18,7 @@ export const ApiUrls = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const ApiUrlsHome = {
|
|
21
|
-
LoginUrl:"https://live-melcloudhome.auth.eu-west-1.amazoncognito.com/login?client_id=3g4d5l5kivuqi7oia68gib7uso&redirect_uri=https%3A%2F%2Fauth.melcloudhome.com%2Fsignin-oidc-meu&response_type=code&scope=openid%20profile&response_mode=form_post",
|
|
21
|
+
LoginUrl: "https://live-melcloudhome.auth.eu-west-1.amazoncognito.com/login?client_id=3g4d5l5kivuqi7oia68gib7uso&redirect_uri=https%3A%2F%2Fauth.melcloudhome.com%2Fsignin-oidc-meu&response_type=code&scope=openid%20profile&response_mode=form_post",
|
|
22
22
|
BaseURL: 'https://melcloudhome.com',
|
|
23
23
|
GetUserContext: "/api/user/context",
|
|
24
24
|
SetAta: "/api/ataunit/deviceid",
|
|
@@ -37,7 +37,7 @@ export const TemperatureDisplayUnits = ["°C", "°F"];
|
|
|
37
37
|
|
|
38
38
|
export const AirConditioner = {
|
|
39
39
|
System: ["AIR CONDITIONER OFF", "AIR CONDITIONER ON", "AIR CONDITIONER OFFLINE"],
|
|
40
|
-
|
|
40
|
+
OperationMode: [
|
|
41
41
|
"0", "HEAT", "DRY", "COOL", "4", "5", "6", "FAN", "AUTO",
|
|
42
42
|
"ISEE HEAT", "ISEE DRY", "ISEE COOL"
|
|
43
43
|
],
|
|
@@ -74,7 +74,8 @@ export const AirConditioner = {
|
|
|
74
74
|
Presets: 287,
|
|
75
75
|
HolidayMode: 131072,
|
|
76
76
|
All: 281483566710825
|
|
77
|
-
}
|
|
77
|
+
},
|
|
78
|
+
OperationModeMap: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Auto": 8, "Isee Heat": 9, "Isee Dry": 10, "Isee Cool": 11 },
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
export const HeatPump = {
|
package/src/deviceata.js
CHANGED
|
@@ -16,6 +16,7 @@ class DeviceAta extends EventEmitter {
|
|
|
16
16
|
AccessoryUUID = api.hap.uuid;
|
|
17
17
|
|
|
18
18
|
//account config
|
|
19
|
+
this.accountMelcloud = account.displayType === 'melcloud' ? true : false;
|
|
19
20
|
this.device = device;
|
|
20
21
|
this.displayMode = device.displayMode;
|
|
21
22
|
this.temperatureSensor = device.temperatureSensor || false;
|
|
@@ -327,7 +328,7 @@ class DeviceAta extends EventEmitter {
|
|
|
327
328
|
|
|
328
329
|
deviceData.Device.EffectiveFlags = AirConditioner.EffectiveFlags.OperationModeSetTemperature;
|
|
329
330
|
await this.melCloudAta.send(deviceData, this.displayMode);
|
|
330
|
-
const operationModeText = AirConditioner.
|
|
331
|
+
const operationModeText = AirConditioner.OperationMode[deviceData.Device.OperationMode];
|
|
331
332
|
if (this.logInfo) this.emit('info', `Set operation mode: ${operationModeText}`);
|
|
332
333
|
} catch (error) {
|
|
333
334
|
if (this.logWarn) this.emit('warn', `Set operation mode error: ${error}`);
|
|
@@ -522,7 +523,7 @@ class DeviceAta extends EventEmitter {
|
|
|
522
523
|
};
|
|
523
524
|
|
|
524
525
|
await this.melCloudAta.send(deviceData, this.displayMode);
|
|
525
|
-
const operationModeText = AirConditioner.
|
|
526
|
+
const operationModeText = AirConditioner.OperationMode[deviceData.Device.OperationMode];
|
|
526
527
|
if (this.logInfo) this.emit('info', `Set operation mode: ${operationModeText}`);
|
|
527
528
|
} catch (error) {
|
|
528
529
|
if (this.logWarn) this.emit('warn', `Set operation mode error: ${error}`);
|
|
@@ -925,8 +926,8 @@ class DeviceAta extends EventEmitter {
|
|
|
925
926
|
this.emit('devInfo', `Account: ${this.accountName}`);
|
|
926
927
|
if (modelIndoor) this.emit('devInfo', `Indoor: ${modelIndoor}`);
|
|
927
928
|
if (modelOutdoor) this.emit('devInfo', `Outdoor: ${modelOutdoor}`);
|
|
928
|
-
this.emit('devInfo', `Serial: ${serialNumber}`);
|
|
929
|
-
this.emit('devInfo', `Firmware: ${firmwareAppVersion}`);
|
|
929
|
+
if (serialNumber) this.emit('devInfo', `Serial: ${serialNumber}`);
|
|
930
|
+
if (firmwareAppVersion) this.emit('devInfo', `Firmware: ${firmwareAppVersion}`);
|
|
930
931
|
this.emit('devInfo', `Manufacturer: ${manufacturer}`);
|
|
931
932
|
this.emit('devInfo', '----------------------------------');
|
|
932
933
|
this.displayDeviceInfo = false;
|
|
@@ -952,16 +953,16 @@ class DeviceAta extends EventEmitter {
|
|
|
952
953
|
|
|
953
954
|
//device info
|
|
954
955
|
const hasAutomaticFanSpeed = deviceData.Device.HasAutomaticFanSpeed ?? false;
|
|
955
|
-
const airDirectionFunction = deviceData.Device.AirDirectionFunction
|
|
956
|
-
const swingFunction = deviceData.Device.SwingFunction
|
|
956
|
+
const airDirectionFunction = this.accountMelcloud ? deviceData.Device.AirDirectionFunction : deviceData.Device.HasAirDirectionFunction;;
|
|
957
|
+
const swingFunction = this.accountMelcloud ? deviceData.Device.SwingFunction : deviceData.Device.HasSwing;
|
|
957
958
|
const hasOutdoorTemperature = deviceData.Device.HasOutdoorTemperature ?? false;
|
|
958
|
-
const
|
|
959
|
-
const
|
|
960
|
-
const modelSupportsAuto1 = deviceData.Device.ModelSupportsAuto
|
|
959
|
+
const modelSupportsFanSpeed = deviceData.Device.ModelSupportsFanSpeed || deviceData.Device.NumberOfFanSpeeds > 0;
|
|
960
|
+
const numberOfFanSpeeds = modelSupportsFanSpeed ? deviceData.Device.NumberOfFanSpeeds : 0;
|
|
961
|
+
const modelSupportsAuto1 = this.accountMelcloud ? deviceData.Device.ModelSupportsAuto : deviceData.Device.HasAutoOperationMode;
|
|
961
962
|
const modelSupportsAuto = this.autoDryFanMode >= 1 && modelSupportsAuto1
|
|
962
|
-
const modelSupportsHeat1 = deviceData.Device.ModelSupportsHeat
|
|
963
|
+
const modelSupportsHeat1 = this.accountMelcloud ? deviceData.Device.ModelSupportsHeat : deviceData.Device.HasHeatOperationMode
|
|
963
964
|
const modelSupportsHeat = this.heatDryFanMode >= 1 && modelSupportsHeat1;
|
|
964
|
-
const modelSupportsDry = deviceData.Device.ModelSupportsDry
|
|
965
|
+
const modelSupportsDry = this.accountMelcloud ? deviceData.Device.ModelSupportsDry : deviceData.Device.HasDryOperationMode;
|
|
965
966
|
const modelSupportsCool = this.coolDryFanMode >= 1;
|
|
966
967
|
const minTempHeat = 10;
|
|
967
968
|
const maxTempHeat = 31;
|
|
@@ -977,8 +978,8 @@ class DeviceAta extends EventEmitter {
|
|
|
977
978
|
const defaultCoolingSetTemperature = deviceData.Device.DefaultCoolingSetTemperature ?? 23;
|
|
978
979
|
const actualFanSpeed = deviceData.Device.ActualFanSpeed;
|
|
979
980
|
const automaticFanSpeed = deviceData.Device.AutomaticFanSpeed;
|
|
980
|
-
const fanSpeed = deviceData.Device.FanSpeed
|
|
981
|
-
const operationMode = deviceData.Device.OperationMode;
|
|
981
|
+
const fanSpeed = this.accountMelcloud ? deviceData.Device.FanSpeed : deviceData.Device.SetFanSpeed;
|
|
982
|
+
const operationMode = this.accountMelcloud ? deviceData.Device.OperationMode : AirConditioner.OperationModeMap[deviceData.Device.OperationMode];
|
|
982
983
|
const vaneVerticalDirection = deviceData.Device.VaneVerticalDirection;
|
|
983
984
|
const vaneVerticalSwing = deviceData.Device.VaneVerticalSwing;
|
|
984
985
|
const vaneHorizontalDirection = deviceData.Device.VaneHorizontalDirection;
|
|
@@ -1108,9 +1109,9 @@ class DeviceAta extends EventEmitter {
|
|
|
1108
1109
|
.updateCharacteristic(Characteristic.LockPhysicalControls, obj.lockPhysicalControl)
|
|
1109
1110
|
.updateCharacteristic(Characteristic.TemperatureDisplayUnits, obj.useFahrenheit)
|
|
1110
1111
|
.updateCharacteristic(Characteristic.CoolingThresholdTemperature, defaultCoolingSetTemperature);
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1112
|
+
if (modelSupportsHeat) this.melCloudService?.updateCharacteristic(Characteristic.HeatingThresholdTemperature, defaultHeatingSetTemperature);
|
|
1113
|
+
if (modelSupportsFanSpeed) this.melCloudService?.updateCharacteristic(Characteristic.RotationSpeed, obj.fanSpeed);
|
|
1114
|
+
if (swingFunction) this.melCloudService?.updateCharacteristic(Characteristic.SwingMode, obj.swingMode);
|
|
1114
1115
|
break;
|
|
1115
1116
|
case 2: //Thermostat
|
|
1116
1117
|
switch (operationMode) {
|
|
@@ -1312,7 +1313,7 @@ class DeviceAta extends EventEmitter {
|
|
|
1312
1313
|
//log current state
|
|
1313
1314
|
if (this.logInfo) {
|
|
1314
1315
|
this.emit('info', `Power: ${power ? 'ON' : 'OFF'}`);
|
|
1315
|
-
this.emit('info', `Target operation mode: ${AirConditioner.
|
|
1316
|
+
this.emit('info', `Target operation mode: ${AirConditioner.OperationMode[operationMode]}`);
|
|
1316
1317
|
this.emit('info', `Current operation mode: ${this.displayMode === 1 ? AirConditioner.CurrentOperationModeHeatherCooler[obj.currentOperationMode] : AirConditioner.CurrentOperationModeThermostat[obj.currentOperationMode]}`);
|
|
1317
1318
|
this.emit('info', `Target temperature: ${setTemperature}${obj.temperatureUnit}`);
|
|
1318
1319
|
this.emit('info', `Current temperature: ${roomTemperature}${obj.temperatureUnit}`);
|