homebridge-melcloud-control 4.0.0-beta.20 → 4.0.0-beta.200
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 +9 -8
- package/config.schema.json +39 -11
- package/homebridge-ui/public/index.html +72 -35
- package/homebridge-ui/server.js +4 -3
- package/index.js +31 -21
- package/package.json +2 -2
- package/src/constants.js +15 -22
- package/src/deviceata.js +190 -181
- package/src/deviceatw.js +5 -3
- package/src/deviceerv.js +5 -3
- package/src/functions.js +7 -3
- package/src/melcloud.js +306 -112
- package/src/melcloudata.js +139 -308
- package/src/melcloudatw.js +3 -228
- package/src/melclouderv.js +3 -160
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
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://github.com/grzegorz914/homebridge-melcloud-control/pulls)
|
|
14
14
|
[](https://github.com/grzegorz914/homebridge-melcloud-control/issues)
|
|
15
15
|
|
|
16
|
-
Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation Lossnay, manufactured by Mistsubishi and connected to MELCloud.
|
|
16
|
+
Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation Lossnay, manufactured by Mistsubishi and connected to MELCloud or MELCloud Home.
|
|
17
17
|
|
|
18
18
|
</span>
|
|
19
19
|
|
|
@@ -21,14 +21,14 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
21
21
|
|
|
22
22
|
| Package | Installation | Role | Required |
|
|
23
23
|
| --- | --- | --- | --- |
|
|
24
|
-
| [Homebridge
|
|
25
|
-
| [
|
|
24
|
+
| [Homebridge](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required |
|
|
25
|
+
| [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x) | [Config UI X Wiki](https://github.com/homebridge/homebridge-config-ui-x/wiki) | Homebridge Web User Interface | Required |
|
|
26
26
|
| [MELCloud](https://github.com/grzegorz914/homebridge-melcloud-control) | [Plug-In Wiki](https://github.com/grzegorz914/homebridge-melcloud-control/wiki) | Homebridge Plug-In | Required |
|
|
27
27
|
|
|
28
28
|
### About The Plugin
|
|
29
29
|
|
|
30
30
|
* All devices are detected automatically.
|
|
31
|
-
* Control devices over MELCloud
|
|
31
|
+
* Control devices over MELCloud and MELCloud Home.
|
|
32
32
|
* Support multiple accounts, buildings, floors, areas.
|
|
33
33
|
* Support temperature display units `Celsius/Fahrenheit`.
|
|
34
34
|
* Support assing inividual operating mode for `Heat/Cool/Auto`.
|
|
@@ -187,7 +187,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
187
187
|
### Configuration
|
|
188
188
|
|
|
189
189
|
* Run this plugin as a [Child Bridge](https://github.com/homebridge/homebridge/wiki/Child-Bridges) (Highly Recommended), this prevent crash Homebridge if plugin crashes.
|
|
190
|
-
* Install and use [Homebridge
|
|
190
|
+
* Install and use [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x/wiki) to configure this plugin, required for version v3 and above.
|
|
191
191
|
* The `sample-config.json` can be edited and used as an alternative.
|
|
192
192
|
|
|
193
193
|
<p align="center">
|
|
@@ -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,6 +1844,7 @@
|
|
|
1817
1844
|
"type": "password"
|
|
1818
1845
|
},
|
|
1819
1846
|
"accounts[].language",
|
|
1847
|
+
"accounts[].displayType",
|
|
1820
1848
|
{
|
|
1821
1849
|
"key": "accounts[]",
|
|
1822
1850
|
"type": "tabarray",
|
|
@@ -1955,7 +1983,7 @@
|
|
|
1955
1983
|
},
|
|
1956
1984
|
"accounts[].atwDevices[].displayMode",
|
|
1957
1985
|
{
|
|
1958
|
-
"key": "accounts[].atwDevices
|
|
1986
|
+
"key": "accounts[].atwDevices",
|
|
1959
1987
|
"type": "section",
|
|
1960
1988
|
"title": "Settings",
|
|
1961
1989
|
"expandable": true,
|
|
@@ -1970,7 +1998,7 @@
|
|
|
1970
1998
|
}
|
|
1971
1999
|
},
|
|
1972
2000
|
{
|
|
1973
|
-
"key": "accounts[].atwDevices
|
|
2001
|
+
"key": "accounts[].atwDevices",
|
|
1974
2002
|
"title": "Temperature Sensors",
|
|
1975
2003
|
"type": "section",
|
|
1976
2004
|
"expandable": true,
|
|
@@ -2074,7 +2102,7 @@
|
|
|
2074
2102
|
},
|
|
2075
2103
|
"accounts[].ervDevices[].displayMode",
|
|
2076
2104
|
{
|
|
2077
|
-
"key": "accounts[].ervDevices
|
|
2105
|
+
"key": "accounts[].ervDevices",
|
|
2078
2106
|
"title": "Settings",
|
|
2079
2107
|
"type": "section",
|
|
2080
2108
|
"expandable": true,
|
|
@@ -2088,7 +2116,7 @@
|
|
|
2088
2116
|
}
|
|
2089
2117
|
},
|
|
2090
2118
|
{
|
|
2091
|
-
"key": "accounts[].ervDevices
|
|
2119
|
+
"key": "accounts[].ervDevices",
|
|
2092
2120
|
"title": "Temperature Sensors",
|
|
2093
2121
|
"type": "section",
|
|
2094
2122
|
"expandable": true,
|
|
@@ -76,41 +76,56 @@
|
|
|
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
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
const config = await homebridge.getPluginConfig();
|
|
100
|
+
|
|
101
|
+
if (!config.length) {
|
|
102
|
+
config.push({ accounts: [] });
|
|
103
|
+
await homebridge.updatePluginConfig(config);
|
|
95
104
|
homebridge.showSchemaForm();
|
|
96
105
|
return;
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
config[0].accounts ??= [];
|
|
100
109
|
|
|
110
|
+
// 🧩 Deep clone — UI-safe copy (JSON method resets references)
|
|
111
|
+
const pluginConfig = JSON.parse(JSON.stringify(config));
|
|
112
|
+
|
|
113
|
+
this.accountIndex = 0;
|
|
101
114
|
const accountsCount = pluginConfig[0].accounts.length;
|
|
115
|
+
|
|
102
116
|
for (let i = 0; i < accountsCount; i++) {
|
|
117
|
+
const acc = pluginConfig[0].accounts[i];
|
|
103
118
|
const button = document.createElement("button");
|
|
104
119
|
button.type = "button";
|
|
105
120
|
button.id = `button${i}`;
|
|
106
121
|
button.className = "btn btn-primary";
|
|
107
122
|
button.style.textTransform = 'none';
|
|
108
|
-
button.innerText =
|
|
123
|
+
button.innerText = acc.name;
|
|
109
124
|
document.getElementById("accountButton").appendChild(button);
|
|
110
125
|
|
|
111
126
|
button.addEventListener('click', async () => {
|
|
112
127
|
for (let j = 0; j < accountsCount; j++) {
|
|
113
|
-
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-
|
|
128
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
114
129
|
}
|
|
115
130
|
|
|
116
131
|
const acc = pluginConfig[0].accounts[i];
|
|
@@ -118,24 +133,30 @@
|
|
|
118
133
|
document.getElementById('name').value = acc.name || '';
|
|
119
134
|
document.getElementById('user').value = acc.user || '';
|
|
120
135
|
document.getElementById('passwd').value = acc.passwd || '';
|
|
121
|
-
document.getElementById('language').value = acc.language || '';
|
|
122
|
-
document.getElementById('
|
|
123
|
-
|
|
136
|
+
document.getElementById('language').value = acc.language || '0';
|
|
137
|
+
document.getElementById('displayType').value = acc.displayType || 'disabled';
|
|
138
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
124
139
|
});
|
|
125
140
|
|
|
126
|
-
if (i === accountsCount - 1
|
|
141
|
+
if (i === accountsCount - 1 && accountsCount > 0)
|
|
142
|
+
document.getElementById(`button0`).click();
|
|
143
|
+
|
|
144
|
+
this.accountIndex = i;
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
document.getElementById('melCloudAccount').style.display = 'block';
|
|
130
148
|
|
|
149
|
+
// Safe update
|
|
131
150
|
document.getElementById('configForm').addEventListener('input', async () => {
|
|
132
|
-
const acc = pluginConfig[0].accounts[this.
|
|
151
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
152
|
+
if (!acc) return;
|
|
133
153
|
acc.name = document.querySelector('#name').value;
|
|
134
154
|
acc.user = document.querySelector('#user').value;
|
|
135
155
|
acc.passwd = document.querySelector('#passwd').value;
|
|
136
156
|
acc.language = document.querySelector('#language').value;
|
|
157
|
+
acc.displayType = document.querySelector('#displayType').value;
|
|
137
158
|
|
|
138
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
|
|
159
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
139
160
|
|
|
140
161
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
141
162
|
await homebridge.savePluginConfig(pluginConfig);
|
|
@@ -167,9 +188,10 @@
|
|
|
167
188
|
function removeStaleDevices(configDevices, melcloudDevices) {
|
|
168
189
|
const melcloudIds = melcloudDevices.map(d => d.DeviceID);
|
|
169
190
|
const removedDevices = [];
|
|
191
|
+
|
|
170
192
|
for (let i = configDevices.length - 1; i >= 0; i--) {
|
|
171
193
|
const device = configDevices[i];
|
|
172
|
-
if (device.id !== 0 && !melcloudIds.includes(device.id)) {
|
|
194
|
+
if (device.id !== "0" && !melcloudIds.includes(device.id)) {
|
|
173
195
|
removedDevices.push(device);
|
|
174
196
|
configDevices.splice(i, 1);
|
|
175
197
|
}
|
|
@@ -186,13 +208,14 @@
|
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
document.getElementById('logIn').addEventListener('click', async () => {
|
|
189
|
-
homebridge.showSpinner();
|
|
190
211
|
document.getElementById(`logIn`).className = "btn btn-primary";
|
|
212
|
+
|
|
191
213
|
updateInfo('info', 'Connecting...', 'yellow');
|
|
214
|
+
homebridge.showSpinner();
|
|
192
215
|
|
|
193
216
|
try {
|
|
194
|
-
const acc = pluginConfig[0].accounts[this.
|
|
195
|
-
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
217
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
218
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language, displayType: acc.displayType };
|
|
196
219
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
197
220
|
|
|
198
221
|
// Initialize devices arrays
|
|
@@ -213,24 +236,36 @@
|
|
|
213
236
|
const removedAtw = removeStaleDevices(acc.atwDevices, devicesByType.atw);
|
|
214
237
|
const removedErv = removeStaleDevices(acc.ervDevices, devicesByType.erv);
|
|
215
238
|
|
|
216
|
-
// Function to handle device & presets
|
|
217
239
|
const handleDevices = (devicesInCloud, devicesInConfig, typeString, newArr, newPresets) => {
|
|
218
240
|
devicesInCloud.forEach(device => {
|
|
219
241
|
const { DeviceID: id, Type: type, DeviceName: name, Presets: presets = [] } = device;
|
|
220
|
-
const devObj = {
|
|
242
|
+
const devObj = structuredClone({
|
|
243
|
+
id,
|
|
244
|
+
type,
|
|
245
|
+
typeString,
|
|
246
|
+
name,
|
|
247
|
+
displayMode: 1,
|
|
248
|
+
presets: [],
|
|
249
|
+
buttonsSensors: []
|
|
250
|
+
});
|
|
251
|
+
|
|
221
252
|
if (!devicesInConfig.some(d => d.id === id)) {
|
|
222
253
|
devicesInConfig.push(devObj);
|
|
223
|
-
newArr.push(devObj);
|
|
254
|
+
newArr.push(structuredClone(devObj));
|
|
224
255
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
p
|
|
228
|
-
|
|
229
|
-
|
|
256
|
+
|
|
257
|
+
presets.forEach(preset => {
|
|
258
|
+
const p = structuredClone({
|
|
259
|
+
id: preset.ID,
|
|
260
|
+
name: preset.NumberDescription,
|
|
261
|
+
displayType: 0,
|
|
262
|
+
namePrefix: false
|
|
263
|
+
});
|
|
264
|
+
|
|
230
265
|
const devConfig = devicesInConfig.find(d => d.id === id);
|
|
231
|
-
if (devConfig && !devConfig.presets.some(x => x.id === p.
|
|
266
|
+
if (devConfig && !devConfig.presets.some(x => x.id === p.id)) {
|
|
232
267
|
devConfig.presets.push(p);
|
|
233
|
-
newPresets.push(p);
|
|
268
|
+
newPresets.push(structuredClone(p));
|
|
234
269
|
}
|
|
235
270
|
});
|
|
236
271
|
});
|
|
@@ -238,9 +273,8 @@
|
|
|
238
273
|
|
|
239
274
|
handleDevices(devicesByType.ata, acc.ataDevices, "Air Conditioner", newDevices.ata, newDevices.ataPresets);
|
|
240
275
|
handleDevices(devicesByType.atw, acc.atwDevices, "Heat Pump", newDevices.atw, newDevices.atwPresets);
|
|
241
|
-
handleDevices(devicesByType.erv, acc.ervDevices, "Energy Recovery
|
|
276
|
+
handleDevices(devicesByType.erv, acc.ervDevices, "Energy Recovery Ventilation", newDevices.erv, newDevices.ervPresets);
|
|
242
277
|
|
|
243
|
-
// Display summary
|
|
244
278
|
const newDevicesCount = newDevices.ata.length + newDevices.atw.length + newDevices.erv.length;
|
|
245
279
|
const newPresetsCount = newDevices.ataPresets.length + newDevices.atwPresets.length + newDevices.ervPresets.length;
|
|
246
280
|
const removedDevicesCount = removedAta.length + removedAtw.length + removedErv.length;
|
|
@@ -248,9 +282,12 @@
|
|
|
248
282
|
if (!newDevicesCount && !newPresetsCount && !removedDevicesCount) {
|
|
249
283
|
updateInfo('info', 'No changes detected.', 'white');
|
|
250
284
|
} else {
|
|
251
|
-
if (newDevicesCount)
|
|
252
|
-
|
|
253
|
-
if (
|
|
285
|
+
if (newDevicesCount)
|
|
286
|
+
updateInfo('info', `Found new devices: ATA: ${newDevices.ata.length}, ATW: ${newDevices.atw.length}, ERV: ${newDevices.erv.length}.`, 'green');
|
|
287
|
+
if (newPresetsCount)
|
|
288
|
+
updateInfo('info1', `Found new presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}.`, 'green');
|
|
289
|
+
if (removedDevicesCount)
|
|
290
|
+
updateInfo('info2', `Removed devices: ATA: ${removedAta.length}, ATW: ${removedAtw.length}, ERV: ${removedErv.length}.`, 'orange');
|
|
254
291
|
}
|
|
255
292
|
|
|
256
293
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
@@ -259,7 +296,7 @@
|
|
|
259
296
|
|
|
260
297
|
} catch (error) {
|
|
261
298
|
updateInfo('info', 'Check Your credentials data and try again.', 'yellow');
|
|
262
|
-
updateInfo('info1', `Error: ${error}`, 'red');
|
|
299
|
+
updateInfo('info1', `Error: ${JSON.stringify(error)}`, 'red');
|
|
263
300
|
document.getElementById('logIn').className = "btn btn-secondary";
|
|
264
301
|
} finally {
|
|
265
302
|
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 accountType = account.displayType || 'disabled';
|
|
34
|
+
if (accountType === '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,8 +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
|
-
|
|
81
82
|
|
|
82
83
|
//set account refresh interval
|
|
83
84
|
const refreshInterval = (account.refreshInterval ?? 120) * 1000
|
|
@@ -88,31 +89,26 @@ class MelCloudPlatform {
|
|
|
88
89
|
.on('start', async () => {
|
|
89
90
|
try {
|
|
90
91
|
//melcloud account
|
|
91
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
92
|
+
const melCloud = new MelCloud(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logLevel.warn, logLevel.debug, false)
|
|
92
93
|
.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
|
|
93
94
|
.on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
|
|
94
95
|
.on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
|
|
95
96
|
.on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${msg}`))
|
|
96
97
|
.on('error', (msg) => logLevel.error && log.error(`${accountName}, ${msg}`));
|
|
97
98
|
|
|
98
|
-
await melCloud.connectHomeCookies();
|
|
99
|
-
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
99
|
//connect
|
|
103
|
-
let
|
|
100
|
+
let accountInfo;
|
|
104
101
|
try {
|
|
105
|
-
|
|
102
|
+
accountInfo = await melCloud.connect();
|
|
106
103
|
} catch (error) {
|
|
107
104
|
if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
|
|
108
105
|
return;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const useFahrenheit = response.useFahrenheit ?? false;
|
|
108
|
+
const contextKey = accountInfo.ContextKey;
|
|
109
|
+
const useFahrenheit = accountInfo.UseFahrenheit;
|
|
114
110
|
|
|
115
|
-
if (contextKey
|
|
111
|
+
if (!contextKey) {
|
|
116
112
|
return;
|
|
117
113
|
}
|
|
118
114
|
|
|
@@ -135,9 +131,8 @@ class MelCloudPlatform {
|
|
|
135
131
|
|
|
136
132
|
for (const device of devices) {
|
|
137
133
|
//chack device from config exist on melcloud
|
|
138
|
-
const deviceId = device.id;
|
|
139
134
|
const displayMode = device.displayMode > 0;
|
|
140
|
-
const deviceExistInMelCloud = devicesInMelcloud.some(dev => dev.DeviceID ===
|
|
135
|
+
const deviceExistInMelCloud = devicesInMelcloud.some(dev => dev.DeviceID === device.id);
|
|
141
136
|
if (!deviceExistInMelCloud || !displayMode) {
|
|
142
137
|
continue;
|
|
143
138
|
}
|
|
@@ -146,19 +141,33 @@ class MelCloudPlatform {
|
|
|
146
141
|
const deviceType = device.type;
|
|
147
142
|
const deviceTypeText = device.typeString;
|
|
148
143
|
const deviceRefreshInterval = (device.refreshInterval ?? 5) * 1000;
|
|
144
|
+
const defaultTempsFile = `${prefDir}/${accountName}_${device.id}_Temps`;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
[defaultTempsFile].forEach(file => {
|
|
148
|
+
const temps = {
|
|
149
|
+
defaultCoolingSetTemperature: 24,
|
|
150
|
+
defaultHeatingSetTemperature: 20
|
|
151
|
+
};
|
|
152
|
+
if (!existsSync(file)) writeFileSync(file, temps);
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (logLevel.error) log.error(`Device: ${host} ${deviceName}, File init error: ${error}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
149
158
|
|
|
150
159
|
let configuredDevice;
|
|
151
160
|
switch (deviceType) {
|
|
152
161
|
case 0: //ATA
|
|
153
|
-
configuredDevice = new DeviceAta(api, account, device,
|
|
162
|
+
configuredDevice = new DeviceAta(api, account, device, devicesFile, defaultTempsFile, useFahrenheit, restFul, mqtt);
|
|
154
163
|
break;
|
|
155
164
|
case 1: //ATW
|
|
156
|
-
configuredDevice = new DeviceAtw(api, account, device, contextKey, devicesFile, useFahrenheit, restFul, mqtt);
|
|
165
|
+
configuredDevice = new DeviceAtw(api, account, device, contextKey, devicesFile, defaultTempsFile, useFahrenheit, restFul, mqtt);
|
|
157
166
|
break;
|
|
158
167
|
case 2:
|
|
159
168
|
break;
|
|
160
169
|
case 3: //ERV
|
|
161
|
-
configuredDevice = new DeviceErv(api, account, device, contextKey, devicesFile, useFahrenheit, restFul, mqtt);
|
|
170
|
+
configuredDevice = new DeviceErv(api, account, device, contextKey, devicesFile, defaultTempsFile, useFahrenheit, restFul, mqtt);
|
|
162
171
|
break;
|
|
163
172
|
default:
|
|
164
173
|
if (logLevel.warn) log.warn(`${accountName}, ${deviceTypeText}, ${deviceName}, unknown device: ${deviceType}.`);
|
|
@@ -185,8 +194,9 @@ class MelCloudPlatform {
|
|
|
185
194
|
api.publishExternalAccessories(PluginName, [accessory]);
|
|
186
195
|
if (logLevel.success) log.success(`${accountName}, ${deviceTypeText}, ${deviceName}, Published as external accessory.`);
|
|
187
196
|
|
|
188
|
-
//start impulse generators
|
|
189
|
-
|
|
197
|
+
//start impulse generators\
|
|
198
|
+
const timmers = accountType === 'melcloudhome' ? [{ name: 'connect', sampling: 150000 }, { name: 'checkDevicesList', sampling: deviceRefreshInterval }] : [{ name: 'checkDevicesList', sampling: refreshInterval }];
|
|
199
|
+
await melCloud.impulseGenerator.state(true, timmers);
|
|
190
200
|
await configuredDevice.startStopImpulseGenerator(true, [{ name: 'checkState', sampling: deviceRefreshInterval }]);
|
|
191
201
|
|
|
192
202
|
//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.0.0-beta.
|
|
4
|
+
"version": "4.0.0-beta.200",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"async-mqtt": "^2.6.3",
|
|
40
40
|
"axios": "^1.12.2",
|
|
41
41
|
"express": "^5.1.0",
|
|
42
|
-
"puppeteer": "^24.26.
|
|
42
|
+
"puppeteer": "^24.26.1"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"homebridge",
|
package/src/constants.js
CHANGED
|
@@ -18,8 +18,7 @@ export const ApiUrls = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const ApiUrlsHome = {
|
|
21
|
-
|
|
22
|
-
BaseURL: 'https://melcloudhome.com',
|
|
21
|
+
BaseURL: "https://melcloudhome.com",
|
|
23
22
|
GetUserContext: "/api/user/context",
|
|
24
23
|
SetAta: "/api/ataunit/deviceid",
|
|
25
24
|
SetAtw: "/api/atwunit/deviceid",
|
|
@@ -36,25 +35,19 @@ export const DeviceType = [
|
|
|
36
35
|
export const TemperatureDisplayUnits = ["°C", "°F"];
|
|
37
36
|
|
|
38
37
|
export const AirConditioner = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"STRONG", "VERY STRONG", "OFF"
|
|
53
|
-
],
|
|
54
|
-
CurrentOperationModeHeatherCooler: [
|
|
55
|
-
"INACTIVE", "IDLE", "HEATING", "COOLING"
|
|
56
|
-
],
|
|
57
|
-
CurrentOperationModeThermostat: ["INACTIVE", "HEATING", "COOLING"],
|
|
38
|
+
SystemMapEnumToString: { 0: "Air Conditioner Off", 1: "ir Conditioner On", 2: "ir Conditioner Offline" },
|
|
39
|
+
AirDirectionMapEnumToString: { 0: "Auto", 1: "Swing" },
|
|
40
|
+
CurrentOperationModeHeatherCoolerMapEnumToString: { 0: "Inactive", 1: "Idle", 2: "Heating", 3: "Cooling" },
|
|
41
|
+
CurrentOperationModeThermostatMapEnumToString: { 0: "Inactive", 1: "Heating", 2: "Cooling" },
|
|
42
|
+
OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Automatic": 8, "Isee Heat": 9, "Isee Dry": 10, "Isee Cool": 11 },
|
|
43
|
+
OperationModeMapEnumToString: { 0: "0", 1: "Heat", 2: "Dry", 3: "Cool", 4: "4", 5: "5", 6: "6", 7: "Fan", 8: "Automatic", 9: "Isee Heat", 10: "Isee Dry", 11: "Isee Cool" },
|
|
44
|
+
FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 },
|
|
45
|
+
FanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
|
|
46
|
+
FanSpeedCurrentMapEnumToString: { 0: "Quiet", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
|
|
47
|
+
VaneVerticalDirectionMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "Six": 6, "Swing": 7 },
|
|
48
|
+
VaneVerticalDirectionMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Swing" },
|
|
49
|
+
VaneHorizontalDirectionMapStringToEnum: { "Auto": 0, "Left": 1, "LeftCentre": 2, "Centre": 3, "RightCentre": 4, "Right": 5, "Six": 6, "Seven": 7, "Split": 8, "Nine": 9, "Ten": 10, "Eleven": 11, "Swing": 12 },
|
|
50
|
+
VaneHorizontalDirectionMapEnumToString: { 0: "Auto", 1: "Left", 2: "LeftCentre", 3: "Centre", 4: "RightCentre", 5: "Right", 6: "Six", 7: "Seven", 8: "Split", 9: "Nine", 10: "Ten", 11: "Eleven", 12: "Swing" },
|
|
58
51
|
EffectiveFlags: {
|
|
59
52
|
Power: 1,
|
|
60
53
|
OperationMode: 2,
|
|
@@ -74,7 +67,7 @@ export const AirConditioner = {
|
|
|
74
67
|
Presets: 287,
|
|
75
68
|
HolidayMode: 131072,
|
|
76
69
|
All: 281483566710825
|
|
77
|
-
}
|
|
70
|
+
},
|
|
78
71
|
};
|
|
79
72
|
|
|
80
73
|
export const HeatPump = {
|