homebridge-melcloud-control 4.0.0-beta.22 → 4.0.0-beta.220

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 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
  [![GitHub pull requests](https://img.shields.io/github/issues-pr/grzegorz914/homebridge-melcloud-control.svg)](https://github.com/grzegorz914/homebridge-melcloud-control/pulls)
14
14
  [![GitHub issues](https://img.shields.io/github/issues/grzegorz914/homebridge-melcloud-control.svg)](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 v2.0](https://github.com/homebridge/homebridge) | [Homebridge Wiki](https://github.com/homebridge/homebridge/wiki) | HomeKit Bridge | Required ab v3.0.0 |
25
- | [Config UI X](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 ab v3.0.0|
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 server.
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 Config UI X](https://github.com/homebridge/homebridge-config-ui-x/wiki) to configure this plugin, required for version v3 and above.
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,15 +204,16 @@ 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 MELCloud username. |
208
- | `passwd` | Here set the MELCloud password. |
209
- | `language` | Here select the MELCloud language. |
207
+ | `user` | Here set the account username. |
208
+ | `passwd` | Here set the account password. |
209
+ | `language` | Here select the account language. |
210
+ | `type` | 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. |
213
214
  | `ataDevices[].typeString` | Read only data, do not change it. |
214
215
  | `ataDevices[].name` | Here You can schange the `Accessory Name` which is exposed to the `Homebridge/HomeKit`. |
215
- | `ataDevices[].displayMode` | Here select device control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
216
+ | `ataDevices[].displayType` | Here select device control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
216
217
  | `ataDevices[].heatDryFanMode` | Here select the operatiing mode for `Heat`, if this mode is not supported, it will be disabled. |
217
218
  | `ataDevices[].coolDryFanMode` | Here select the operatiing mode for `Cool`, if this mode is not supported, it will be disabled. |
218
219
  | `ataDevices[].autoDryFanMode` | Here select the operatiing mode for `Auto`, if this mode is not supported, it will be disabled.. |
@@ -233,7 +234,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
233
234
  | `atwDevices[].type` | Read only data, do not change it. |
234
235
  | `atwDevices[].typeString` | Read only data, do not change it. |
235
236
  | `atwDevices[].name` | Here You can schange the `Accessory Name` which is exposed to the `Homebridge/HomeKit`. |
236
- | `atwDevices[].displayMode` | Here select main control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
237
+ | `atwDevices[].displayType` | Here select main control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
237
238
  | `atwDevices[].hideZone` | Here select which zone need to be hidden `None/Disabled`, `Heat Pump`, `Heat Pump / Zone 1`, `Heat Pump / Zone 1 / Hot Water`, `Heat Pump / Zone 1 / Zone 2`, `Heat Pump / Hot Water`,`Heat Pump / Hot Water / Zone 2`, `Heat Pump / Zone 2`, `Zone 1`, `Zone 1 / Hot Water`, `Zone 1 / Hot Water / Zone 2`, `Zone 1 / Zone 2`, `Hot Water`, `Hot Water / Zone 2`, `Zone 2`, `All`. |
238
239
  | `atwDevices[].temperatureSensor` | This enable extra `Room` temperature sensors to use with automations in HomeKit app. |
239
240
  | `atwDevices[].temperatureSensorFlow` | This enable extra `Flow` temperature sensors to use with automations in HomeKit app. |
@@ -259,7 +260,7 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
259
260
  | `ervDevices[].type` | Read only data, do not change it. |
260
261
  | `ervDevices[].typeString` | Read only data, do not change it. |
261
262
  | `ervDevices[].name` | Here You can schange the `Accessory Name` which is exposed to the `Homebridge/HomeKit`. |
262
- | `ervDevices[].displayMode` | Here select main control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
263
+ | `ervDevices[].displayType` | Here select main control mode `None/Disabled`, `Heater/Cooler`, `Thermostat`. |
263
264
  | `ervDevices[].temperatureSensor` | This enable extra `Room` temperature sensors to use with automations in HomeKit app. |
264
265
  | `ervDevices[].temperatureSensorOutdoor` | This enable extra `Outdoor` temperature sensors to use with automations in HomeKit app. |
265
266
  | `ervDevices[].temperatureSensorSupply` | This enable extra `Supply` temperature sensors to use with automations in HomeKit app. |
@@ -201,6 +201,32 @@
201
201
  }
202
202
  ]
203
203
  },
204
+ "type": {
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": "integer",
213
- "default": 0,
238
+ "type": "string",
239
+ "default": "0",
214
240
  "readonly": true
215
241
  },
216
242
  "type": {
@@ -225,12 +251,12 @@
225
251
  "default": "Air Conditioner",
226
252
  "readonly": true
227
253
  },
228
- "displayMode": {
254
+ "displayType": {
229
255
  "title": "Display Type",
230
256
  "type": "integer",
231
257
  "minimum": 0,
232
258
  "maximum": 2,
233
- "default": 1,
259
+ "default": 0,
234
260
  "description": "Select accessory control type displayed in HomeKit app.",
235
261
  "oneOf": [
236
262
  {
@@ -760,14 +786,14 @@
760
786
  }
761
787
  },
762
788
  "required": [
763
- "displayMode",
789
+ "displayType",
764
790
  "heatDryFanMode",
765
791
  "coolDryFanMode",
766
792
  "autoDryFanMode"
767
793
  ]
768
794
  },
769
795
  "condition": {
770
- "functionBody": "const accounts = model.accounts[arrayIndices]?.ataDevices || []; return accounts.length > 0 && accounts.every(device => device.id != null);"
796
+ "functionBody": "const id = model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].id; return id && id !== '0';"
771
797
  }
772
798
  },
773
799
  "atwDevices": {
@@ -778,8 +804,8 @@
778
804
  "properties": {
779
805
  "id": {
780
806
  "title": "ID",
781
- "type": "integer",
782
- "default": 0,
807
+ "type": "string",
808
+ "default": "0",
783
809
  "readonly": true
784
810
  },
785
811
  "type": {
@@ -799,12 +825,12 @@
799
825
  "type": "string",
800
826
  "default": "Heat Pump"
801
827
  },
802
- "displayMode": {
828
+ "displayType": {
803
829
  "title": "Display Type",
804
830
  "type": "integer",
805
831
  "minimum": 0,
806
832
  "maximum": 2,
807
- "default": 1,
833
+ "default": 0,
808
834
  "description": "Select accessory control type displayed in HomeKit app.",
809
835
  "oneOf": [
810
836
  {
@@ -1297,12 +1323,12 @@
1297
1323
  }
1298
1324
  },
1299
1325
  "required": [
1300
- "displayMode",
1326
+ "displayType",
1301
1327
  "hideZone"
1302
1328
  ]
1303
1329
  },
1304
1330
  "condition": {
1305
- "functionBody": "const accounts = model.accounts[arrayIndices]?.atwDevices || []; return accounts.length > 0 && accounts.every(device => device.id != null);"
1331
+ "functionBody": "const id = model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].id; return id && id !== '0';"
1306
1332
  }
1307
1333
  },
1308
1334
  "ervDevices": {
@@ -1313,8 +1339,8 @@
1313
1339
  "properties": {
1314
1340
  "id": {
1315
1341
  "title": "ID",
1316
- "type": "integer",
1317
- "default": 0,
1342
+ "type": "string",
1343
+ "default": "0",
1318
1344
  "readonly": true
1319
1345
  },
1320
1346
  "type": {
@@ -1334,12 +1360,12 @@
1334
1360
  "type": "string",
1335
1361
  "default": "Ventilation"
1336
1362
  },
1337
- "displayMode": {
1363
+ "displayType": {
1338
1364
  "title": "Display Type",
1339
1365
  "type": "integer",
1340
1366
  "minimum": 0,
1341
1367
  "maximum": 2,
1342
- "default": 1,
1368
+ "default": 0,
1343
1369
  "description": "Select accessory control type displayed in HomeKit app.",
1344
1370
  "oneOf": [
1345
1371
  {
@@ -1646,11 +1672,11 @@
1646
1672
  }
1647
1673
  },
1648
1674
  "required": [
1649
- "displayMode"
1675
+ "displayType"
1650
1676
  ]
1651
1677
  },
1652
1678
  "condition": {
1653
- "functionBody": "const accounts = model.accounts[arrayIndices]?.ervDevices || []; return accounts.length > 0 && accounts.every(device => device.id != null);"
1679
+ "functionBody": "const id = model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].id; return id;"
1654
1680
  }
1655
1681
  },
1656
1682
  "refreshInterval": {
@@ -1798,7 +1824,8 @@
1798
1824
  "name",
1799
1825
  "user",
1800
1826
  "passwd",
1801
- "language"
1827
+ "language",
1828
+ "type"
1802
1829
  ]
1803
1830
  }
1804
1831
  }
@@ -1817,6 +1844,7 @@
1817
1844
  "type": "password"
1818
1845
  },
1819
1846
  "accounts[].language",
1847
+ "accounts[].type",
1820
1848
  {
1821
1849
  "key": "accounts[]",
1822
1850
  "type": "tabarray",
@@ -1839,7 +1867,7 @@
1839
1867
  "key": "accounts[].ataDevices[].typeString",
1840
1868
  "readonly": true
1841
1869
  },
1842
- "accounts[].ataDevices[].displayMode",
1870
+ "accounts[].ataDevices[].displayType",
1843
1871
  {
1844
1872
  "key": "accounts[].ataDevices[]",
1845
1873
  "title": "Settings",
@@ -1854,7 +1882,7 @@
1854
1882
  "accounts[].ataDevices[].refreshInterval"
1855
1883
  ],
1856
1884
  "condition": {
1857
- "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayMode > 0;"
1885
+ "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayType > 0;"
1858
1886
  }
1859
1887
  },
1860
1888
  {
@@ -1868,7 +1896,7 @@
1868
1896
  "accounts[].ataDevices[].temperatureSensorOutdoor"
1869
1897
  ],
1870
1898
  "condition": {
1871
- "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayMode > 0;"
1899
+ "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayType > 0;"
1872
1900
  }
1873
1901
  },
1874
1902
  {
@@ -1894,7 +1922,7 @@
1894
1922
  }
1895
1923
  ],
1896
1924
  "condition": {
1897
- "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayMode > 0;"
1925
+ "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayType > 0;"
1898
1926
  }
1899
1927
  },
1900
1928
  {
@@ -1917,24 +1945,15 @@
1917
1945
  }
1918
1946
  ],
1919
1947
  "condition": {
1920
- "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayMode > 0;"
1948
+ "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].displayType > 0;"
1921
1949
  }
1922
1950
  }
1923
- ],
1924
- "condition": {
1925
- "functionBody": "return model.accounts[arrayIndices].ataDevices.length > 0;"
1926
- }
1927
- },
1928
- {
1929
- "key": "accounts[]",
1930
- "type": "tabarray",
1931
- "title": "{{ value.title }}",
1932
- "items": [],
1933
- "condition": {
1934
- "functionBody": "return model.accounts[arrayIndices].ataDevices.length === 0;"
1935
- }
1951
+ ]
1936
1952
  }
1937
- ]
1953
+ ],
1954
+ "condition": {
1955
+ "functionBody": "return model.accounts[arrayIndices[0]].ataDevices[arrayIndices[1]].id !== '0';"
1956
+ }
1938
1957
  },
1939
1958
  {
1940
1959
  "key": "accounts[]",
@@ -1953,7 +1972,7 @@
1953
1972
  "key": "accounts[].atwDevices[].typeString",
1954
1973
  "readonly": true
1955
1974
  },
1956
- "accounts[].atwDevices[].displayMode",
1975
+ "accounts[].atwDevices[].displayType",
1957
1976
  {
1958
1977
  "key": "accounts[].atwDevices[]",
1959
1978
  "type": "section",
@@ -1966,7 +1985,7 @@
1966
1985
  "accounts[].atwDevices[].refreshInterval"
1967
1986
  ],
1968
1987
  "condition": {
1969
- "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayMode > 0;"
1988
+ "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayType > 0;"
1970
1989
  }
1971
1990
  },
1972
1991
  {
@@ -1987,7 +2006,7 @@
1987
2006
  "accounts[].atwDevices[].temperatureSensorReturnZone2"
1988
2007
  ],
1989
2008
  "condition": {
1990
- "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayMode > 0;"
2009
+ "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayType > 0;"
1991
2010
  }
1992
2011
  },
1993
2012
  {
@@ -2013,7 +2032,7 @@
2013
2032
  }
2014
2033
  ],
2015
2034
  "condition": {
2016
- "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayMode > 0;"
2035
+ "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayType > 0;"
2017
2036
  }
2018
2037
  },
2019
2038
  {
@@ -2036,7 +2055,7 @@
2036
2055
  }
2037
2056
  ],
2038
2057
  "condition": {
2039
- "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayMode > 0;"
2058
+ "functionBody": "return model.accounts[arrayIndices[0]].atwDevices[arrayIndices[1]].displayType > 0;"
2040
2059
  }
2041
2060
  }
2042
2061
  ],
@@ -2072,7 +2091,7 @@
2072
2091
  "key": "accounts[].ervDevices[].typeString",
2073
2092
  "readonly": true
2074
2093
  },
2075
- "accounts[].ervDevices[].displayMode",
2094
+ "accounts[].ervDevices[].displayType",
2076
2095
  {
2077
2096
  "key": "accounts[].ervDevices[]",
2078
2097
  "title": "Settings",
@@ -2084,7 +2103,7 @@
2084
2103
  "accounts[].ervDevices[].refreshInterval"
2085
2104
  ],
2086
2105
  "condition": {
2087
- "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayMode > 0;"
2106
+ "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayType > 0;"
2088
2107
  }
2089
2108
  },
2090
2109
  {
@@ -2099,7 +2118,7 @@
2099
2118
  "accounts[].ervDevices[].temperatureSensorSupply"
2100
2119
  ],
2101
2120
  "condition": {
2102
- "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayMode > 0;"
2121
+ "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayType > 0;"
2103
2122
  }
2104
2123
  },
2105
2124
  {
@@ -2125,7 +2144,7 @@
2125
2144
  }
2126
2145
  ],
2127
2146
  "condition": {
2128
- "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayMode > 0;"
2147
+ "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayType > 0;"
2129
2148
  }
2130
2149
  },
2131
2150
  {
@@ -2148,7 +2167,7 @@
2148
2167
  }
2149
2168
  ],
2150
2169
  "condition": {
2151
- "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayMode > 0;"
2170
+ "functionBody": "return model.accounts[arrayIndices[0]].ervDevices[arrayIndices[1]].displayType > 0;"
2152
2171
  }
2153
2172
  }
2154
2173
  ],
@@ -76,41 +76,56 @@
76
76
  </select>
77
77
  </div>
78
78
 
79
+ <div class="mb-2">
80
+ <label for="accountType" class="form-label">Account Type</label>
81
+ <select id="accountType" name="accountType" 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 pluginConfig = await homebridge.getPluginConfig();
92
- if (!pluginConfig.length) {
93
- pluginConfig.push({});
94
- await homebridge.updatePluginConfig(pluginConfig);
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
- this.deviceIndex = 0;
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 = pluginConfig[0].accounts[i].name;
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-secondary' : 'btn btn-primary');
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('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language);
123
- this.deviceIndex = i;
136
+ document.getElementById('language').value = acc.language || '0';
137
+ document.getElementById('accountType').value = acc.type || 'disabled';
138
+ document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.type);
124
139
  });
125
140
 
126
- if (i === accountsCount - 1) document.getElementById(`button0`).click();
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.deviceIndex];
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.type = document.querySelector('#accountType').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.type);
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.deviceIndex];
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, accountType: acc.type };
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 = { id, type, typeString, name, displayMode: 1, presets: [], buttonsSensors: [] };
242
+ const devObj = structuredClone({
243
+ id,
244
+ type,
245
+ typeString,
246
+ name,
247
+ displayType: 0,
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
- presets.forEach(p => {
226
- p.id = p.ID;
227
- p.name = p.NumberDescription;
228
- p.displayType = 0;
229
- p.namePrefix = false;
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.ID)) {
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 ventiltion", newDevices.erv, newDevices.ervPresets);
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) updateInfo('info', `Found new devices: ATA: ${newDevices.ata.length}, ATW: ${newDevices.atw.length}, ERV: ${newDevices.erv.length}.`, 'green');
252
- if (newPresetsCount) updateInfo('info1', `Found new presets: ATA: ${newDevices.ataPresets.length}, ATW: ${newDevices.atwPresets.length}, ERV: ${newDevices.ervPresets.length}.`, 'green');
253
- if (removedDevicesCount) updateInfo('info2', `Removed devices: ATA: ${removedAta.length}, ATW: ${removedAtw.length}, ERV: ${removedErv.length}.`, 'orange');
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();
@@ -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 accountType = payload.accountType;
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(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
24
25
 
25
26
  try {
26
- const response = await melCloud.connect();
27
- const devices = await melCloud.checkDevicesList(response.contextKey);
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}.`);