homebridge-melcloud-control 4.0.0-beta.6 → 4.0.0-beta.61
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 +35 -7
- package/homebridge-ui/public/index.html +22 -13
- package/homebridge-ui/server.js +4 -3
- package/index.js +10 -13
- package/package.json +1 -1
- package/src/melcloud.js +154 -88
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,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",
|
|
@@ -76,14 +76,22 @@
|
|
|
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
|
-
<button id="logIn" type="button" class="btn btn-secondary">
|
|
89
|
+
<button id="logIn" type="button" class="btn btn-secondary">Log In</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>
|
|
@@ -96,8 +104,7 @@
|
|
|
96
104
|
return;
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
this.
|
|
100
|
-
|
|
107
|
+
this.accountIndex = 0;
|
|
101
108
|
const accountsCount = pluginConfig[0].accounts.length;
|
|
102
109
|
for (let i = 0; i < accountsCount; i++) {
|
|
103
110
|
const button = document.createElement("button");
|
|
@@ -110,7 +117,7 @@
|
|
|
110
117
|
|
|
111
118
|
button.addEventListener('click', async () => {
|
|
112
119
|
for (let j = 0; j < accountsCount; j++) {
|
|
113
|
-
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-
|
|
120
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
const acc = pluginConfig[0].accounts[i];
|
|
@@ -118,9 +125,10 @@
|
|
|
118
125
|
document.getElementById('name').value = acc.name || '';
|
|
119
126
|
document.getElementById('user').value = acc.user || '';
|
|
120
127
|
document.getElementById('passwd').value = acc.passwd || '';
|
|
121
|
-
document.getElementById('language').value = acc.language || '';
|
|
122
|
-
document.getElementById('
|
|
123
|
-
|
|
128
|
+
document.getElementById('language').value = acc.language || '0';
|
|
129
|
+
document.getElementById('displayType').value = acc.displayType || 'disabled';
|
|
130
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
131
|
+
this.accountIndex = i;
|
|
124
132
|
});
|
|
125
133
|
|
|
126
134
|
if (i === accountsCount - 1) document.getElementById(`button0`).click();
|
|
@@ -129,13 +137,14 @@
|
|
|
129
137
|
document.getElementById('melCloudAccount').style.display = 'block';
|
|
130
138
|
|
|
131
139
|
document.getElementById('configForm').addEventListener('input', async () => {
|
|
132
|
-
const acc = pluginConfig[0].accounts[this.
|
|
140
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
133
141
|
acc.name = document.querySelector('#name').value;
|
|
134
142
|
acc.user = document.querySelector('#user').value;
|
|
135
143
|
acc.passwd = document.querySelector('#passwd').value;
|
|
136
144
|
acc.language = document.querySelector('#language').value;
|
|
145
|
+
acc.displayType = document.querySelector('#displayType').value;
|
|
137
146
|
|
|
138
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.
|
|
147
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.languaged && acc.displayType);
|
|
139
148
|
|
|
140
149
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
141
150
|
await homebridge.savePluginConfig(pluginConfig);
|
|
@@ -191,8 +200,8 @@
|
|
|
191
200
|
updateInfo('info', 'Connecting...', 'yellow');
|
|
192
201
|
|
|
193
202
|
try {
|
|
194
|
-
const acc = pluginConfig[0].accounts[this.
|
|
195
|
-
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
203
|
+
const acc = pluginConfig[0].accounts[this.accountIndex];
|
|
204
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language, displayType: acc.displayType };
|
|
196
205
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
197
206
|
|
|
198
207
|
// Initialize devices arrays
|
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.connectHomeCoocies()
|
|
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
|
|
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.61",
|
|
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/melcloud.js
CHANGED
|
@@ -7,15 +7,15 @@ import Functions from './functions.js';
|
|
|
7
7
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
8
8
|
|
|
9
9
|
class MelCloud extends EventEmitter {
|
|
10
|
-
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
10
|
+
constructor(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
11
11
|
super();
|
|
12
|
+
this.displayType = displayType;
|
|
12
13
|
this.user = user;
|
|
13
14
|
this.passwd = passwd;
|
|
14
15
|
this.language = language;
|
|
15
16
|
this.accountFile = accountFile;
|
|
16
17
|
this.buildingsFile = buildingsFile;
|
|
17
18
|
this.devicesFile = devicesFile;
|
|
18
|
-
this.cookiesFile = cookiesFile;
|
|
19
19
|
this.logWarn = logWarn;
|
|
20
20
|
this.logDebug = logDebug;
|
|
21
21
|
this.requestConfig = requestConfig;
|
|
@@ -58,7 +58,7 @@ class MelCloud extends EventEmitter {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async
|
|
61
|
+
async checkMelcloudDevicesList(contextKey) {
|
|
62
62
|
try {
|
|
63
63
|
const axiosInstanceGet = axios.create({
|
|
64
64
|
method: 'GET',
|
|
@@ -109,7 +109,7 @@ class MelCloud extends EventEmitter {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
async
|
|
112
|
+
async connectToMelCloud() {
|
|
113
113
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
114
114
|
|
|
115
115
|
try {
|
|
@@ -123,7 +123,6 @@ class MelCloud extends EventEmitter {
|
|
|
123
123
|
const account = accountData.data;
|
|
124
124
|
const accountInfo = account.LoginData;
|
|
125
125
|
const contextKey = accountInfo?.ContextKey;
|
|
126
|
-
const useFahrenheit = accountInfo?.UseFahrenheit ?? false;
|
|
127
126
|
this.contextKey = contextKey;
|
|
128
127
|
|
|
129
128
|
const debugData = {
|
|
@@ -153,44 +152,24 @@ class MelCloud extends EventEmitter {
|
|
|
153
152
|
});
|
|
154
153
|
|
|
155
154
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
156
|
-
|
|
157
155
|
this.emit('success', `Connect to MELCloud Success`);
|
|
158
156
|
|
|
159
|
-
return
|
|
160
|
-
accountInfo,
|
|
161
|
-
contextKey,
|
|
162
|
-
useFahrenheit
|
|
163
|
-
};
|
|
157
|
+
return accountInfo
|
|
164
158
|
} catch (error) {
|
|
165
159
|
throw new Error(`Connect to MELCloud error: ${error.message}`);
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
162
|
|
|
169
|
-
async
|
|
170
|
-
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
171
|
-
|
|
163
|
+
async checkMelcloudHomeDevicesList(contextKey) {
|
|
172
164
|
try {
|
|
173
|
-
const c1 = cookieC1.trim();
|
|
174
|
-
const c2 = cookieC2.trim();
|
|
175
|
-
|
|
176
|
-
const cookieString = [
|
|
177
|
-
'__Secure-monitorandcontrol=chunks-2',
|
|
178
|
-
`__Secure-monitorandcontrolC1=${c1}`,
|
|
179
|
-
`__Secure-monitorandcontrolC2=${c2}`,
|
|
180
|
-
].join('; ');
|
|
181
|
-
|
|
182
165
|
const axiosInstance = axios.create({
|
|
166
|
+
method: 'GET',
|
|
183
167
|
baseURL: ApiUrlsHome.BaseURL,
|
|
184
|
-
timeout: 10000,
|
|
185
|
-
httpsAgent: new Agent({
|
|
186
|
-
keepAlive: false,
|
|
187
|
-
rejectUnauthorized: false
|
|
188
|
-
}),
|
|
189
168
|
headers: {
|
|
190
169
|
'Accept': '*/*',
|
|
191
170
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
192
|
-
'Cookie':
|
|
193
|
-
'User-Agent': 'homebridge-melcloud-
|
|
171
|
+
'Cookie': contextKey,
|
|
172
|
+
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
194
173
|
'DNT': '1',
|
|
195
174
|
'Origin': 'https://melcloudhome.com',
|
|
196
175
|
'Referer': 'https://melcloudhome.com/dashboard',
|
|
@@ -198,93 +177,180 @@ class MelCloud extends EventEmitter {
|
|
|
198
177
|
'Sec-Fetch-Mode': 'cors',
|
|
199
178
|
'Sec-Fetch-Site': 'same-origin',
|
|
200
179
|
'X-CSRF': '1'
|
|
201
|
-
}
|
|
180
|
+
},
|
|
181
|
+
...this.axiosDefaults
|
|
202
182
|
});
|
|
203
183
|
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
184
|
+
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
185
|
+
const listDevicesData = await axiosInstance(ApiUrlsHome.GetUserContext);
|
|
186
|
+
const buildingsList = listDevicesData.data.buildings;
|
|
187
|
+
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
207
188
|
|
|
208
|
-
|
|
189
|
+
if (!buildingsList) {
|
|
190
|
+
if (this.logWarn) this.emit('warn', `No building found`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
195
|
+
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
196
|
+
|
|
197
|
+
const devices = buildingsList.flatMap(building => {
|
|
198
|
+
// Funkcja kapitalizująca klucze obiektu
|
|
199
|
+
const capitalizeKeys = obj =>
|
|
200
|
+
Object.fromEntries(
|
|
201
|
+
Object.entries(obj).map(([key, value]) => [
|
|
202
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
203
|
+
value
|
|
204
|
+
])
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Funkcja tworząca finalny obiekt Device
|
|
208
|
+
const createDevice = (device, type) => {
|
|
209
|
+
// Settings już kapitalizowane w nazwach
|
|
210
|
+
const settingsArray = device.Settings || [];
|
|
211
|
+
|
|
212
|
+
const settingsObject = Object.fromEntries(
|
|
213
|
+
settingsArray.map(({ name, value }) => {
|
|
214
|
+
let parsedValue = value;
|
|
215
|
+
if (value === "True") parsedValue = true;
|
|
216
|
+
else if (value === "False") parsedValue = false;
|
|
217
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
218
|
+
|
|
219
|
+
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
220
|
+
return [key, parsedValue];
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Scal Capabilities + Settings + DeviceType w Device
|
|
225
|
+
const deviceObject = {
|
|
226
|
+
...capitalizeKeys(device.Capabilities || {}),
|
|
227
|
+
...settingsObject,
|
|
228
|
+
DeviceType: type
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Usuń stare pola Settings i Capabilities
|
|
232
|
+
const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...rest,
|
|
236
|
+
Type: type,
|
|
237
|
+
DeviceID: Id,
|
|
238
|
+
DeviceName: GivenDisplayName,
|
|
239
|
+
Device: deviceObject
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return [
|
|
244
|
+
...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0)),
|
|
245
|
+
...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1)),
|
|
246
|
+
...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3))
|
|
247
|
+
];
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const devicesCount = devices.length;
|
|
251
|
+
if (devicesCount === 0) {
|
|
252
|
+
if (this.logWarn) this.emit('warn', `No devices found`);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await this.functions.saveData(this.devicesFile, devices);
|
|
257
|
+
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
209
258
|
|
|
210
|
-
return
|
|
259
|
+
return devices;
|
|
211
260
|
} catch (error) {
|
|
212
261
|
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
213
262
|
}
|
|
214
263
|
}
|
|
215
264
|
|
|
216
|
-
async
|
|
217
|
-
|
|
218
|
-
loginUrl.searchParams.set('client_id', '3g4d5l5kivuqi7oia68gib7uso');
|
|
219
|
-
loginUrl.searchParams.set('redirect_uri', 'https://auth.melcloudhome.com/signin-oidc-meu');
|
|
220
|
-
loginUrl.searchParams.set('response_type', 'code');
|
|
221
|
-
loginUrl.searchParams.set('scope', 'openid profile');
|
|
222
|
-
loginUrl.searchParams.set('response_mode', 'form_post');
|
|
223
|
-
|
|
224
|
-
const browser = await puppeteer.launch({
|
|
225
|
-
headless: true,
|
|
226
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
227
|
-
});
|
|
265
|
+
async connectToMelCloudHome() {
|
|
266
|
+
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
228
267
|
|
|
268
|
+
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
|
229
269
|
const page = await browser.newPage();
|
|
230
270
|
|
|
231
271
|
try {
|
|
232
|
-
|
|
233
|
-
await page.goto(
|
|
272
|
+
// Open MELCloud Home
|
|
273
|
+
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
|
|
274
|
+
const buttons = await page.$$('button.btn--blue');
|
|
275
|
+
let loginBtn = null;
|
|
276
|
+
for (const btn of buttons) {
|
|
277
|
+
const text = await page.evaluate(el => el.textContent, btn);
|
|
278
|
+
if (text.trim() === 'Zaloguj' || text.trim() === 'Log In') {
|
|
279
|
+
loginBtn = btn;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
|
|
234
285
|
|
|
235
|
-
|
|
286
|
+
// Set credentials and login
|
|
287
|
+
await Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
288
|
+
await page.waitForSelector('input[name="username"]', { timeout: 15000 });
|
|
236
289
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
237
290
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
238
291
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
'button[type="submit"]',
|
|
242
|
-
'input[type="submit"]',
|
|
243
|
-
'button[name="signIn"]',
|
|
244
|
-
'button.btn-primary'
|
|
245
|
-
];
|
|
246
|
-
|
|
247
|
-
let buttonFound = false;
|
|
248
|
-
for (const selector of buttonSelectors) {
|
|
249
|
-
const button = await page.$(selector);
|
|
250
|
-
if (button) {
|
|
251
|
-
this.emit('warn', `Found submit button: ${selector}`);
|
|
252
|
-
await Promise.all([
|
|
253
|
-
button.click(),
|
|
254
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 }) // czekamy na redirect
|
|
255
|
-
]);
|
|
256
|
-
buttonFound = true;
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
292
|
+
const button1 = await page.$('input[type="submit"]');
|
|
293
|
+
await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
260
294
|
|
|
261
|
-
|
|
262
|
-
|
|
295
|
+
// Get cookies C1 and C2
|
|
296
|
+
let c1 = null, c2 = null;
|
|
297
|
+
const start = Date.now();
|
|
298
|
+
|
|
299
|
+
// Loop max 20s
|
|
300
|
+
while ((!c1 || !c2) && Date.now() - start < 20000) {
|
|
301
|
+
const cookies = await page.cookies();
|
|
302
|
+
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
303
|
+
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
304
|
+
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
263
305
|
}
|
|
264
306
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
if (!c1 || !c2) {
|
|
308
|
+
if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
268
311
|
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
const c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || null;
|
|
312
|
+
const contextKey = ['__Secure-monitorandcontrol=chunks-2', `__Secure-monitorandcontrolC1=${c1}`, `__Secure-monitorandcontrolC2=${c2}`,].join('; ');
|
|
313
|
+
const accountInfo = { ContextKey: contextKey, UseFahrenheit: false };
|
|
314
|
+
this.contextKey = contextKey;
|
|
273
315
|
|
|
274
|
-
|
|
275
|
-
|
|
316
|
+
await this.functions.saveData(this.accountFile, accountInfo);
|
|
317
|
+
this.emit('success', `Connect to MELCloud Home Success`);
|
|
276
318
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.emit('error', `Login failed: ${err.message}`);
|
|
281
|
-
return null;
|
|
319
|
+
return accountInfo;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
282
322
|
} finally {
|
|
283
323
|
await browser.close();
|
|
284
324
|
}
|
|
285
325
|
}
|
|
286
326
|
|
|
327
|
+
async connect() {
|
|
328
|
+
let response = {};
|
|
329
|
+
switch (this.displayType) {
|
|
330
|
+
case "melcloud":
|
|
331
|
+
response = await this.connectToMelCloud();
|
|
332
|
+
return response
|
|
333
|
+
case "melcloudhome":
|
|
334
|
+
response = await this.connectToMelCloudHome();
|
|
335
|
+
return response
|
|
336
|
+
default:
|
|
337
|
+
return response
|
|
338
|
+
}
|
|
339
|
+
}
|
|
287
340
|
|
|
341
|
+
async checkDevicesList(contextKey) {
|
|
342
|
+
let devices = [];
|
|
343
|
+
switch (this.displayType) {
|
|
344
|
+
case "melcloud":
|
|
345
|
+
devices = await this.checkMelcloudDevicesList(contextKey);
|
|
346
|
+
return devices
|
|
347
|
+
case "melcloudhome":
|
|
348
|
+
devices = await this.checkMelcloudHomeDevicesList(contextKey);
|
|
349
|
+
return devices;
|
|
350
|
+
default:
|
|
351
|
+
return devices;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
288
354
|
|
|
289
355
|
async send(accountInfo) {
|
|
290
356
|
try {
|