homebridge-melcloud-control 2.3.3 → 2.5.0-beta.0

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.
@@ -3,6 +3,7 @@
3
3
  "pluginType": "platform",
4
4
  "singular": true,
5
5
  "fixArrays": true,
6
+ "customUi": true,
6
7
  "headerDisplay": "This plugin works with Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation connected to MELCloud service. Devices are exposed to HomeKit as separate accessories and each needs to be manually paired.",
7
8
  "footerDisplay": "For documentation please see [GitHub repository](https://github.com/grzegorz914/homebridge-melcloud-control).",
8
9
  "schema": {
@@ -0,0 +1,196 @@
1
+ <div>
2
+ <img src="homebridge-melcloud-control.png" style="text-align: center;" height="150" />
3
+ </div>
4
+ <div id="accounts">
5
+ </div>
6
+ <div id="melCloudAccount" style="display: none;" class="card card-body">
7
+ <form id="configForm">
8
+ <div class="text-center">
9
+ <label id="accountName" style="font-size: 23px">Account Name</label><br>
10
+ <label id="info" style="text-align: center; font-size: 17px;">MELCloud Account</label><br><br>
11
+ </div>
12
+ <div class="form-group">
13
+ <label for="name">Account Name</label>
14
+ <input id="name" type="text" class="form-control"><br>
15
+ <label for="user">MELCloud User</label>
16
+ <input id="user" type="text" class="form-control"><br>
17
+ <label for="passwd">MELCloud Password</label>
18
+ <input id="passwd" type="text" class="form-control"><br>
19
+ <label for="language">Language</label>
20
+ <input id="language" type="text" class="form-control"><br>
21
+ </div>
22
+ <div class="text-center">
23
+ <button id="logIn" type="button" class="btn btn-primary">Log In</button>
24
+ <button id="getDevices" type="button" class="btn btn-primary">Get Devices</button>
25
+ <button id="configButton" type="button" class="btn btn-primary"><i class="fas fa-gear"></i></button>
26
+ </div>
27
+ </form>
28
+ </div>
29
+
30
+ <script>
31
+ (async () => {
32
+ //get the plugin config array
33
+ const pluginConfig = await homebridge.getPluginConfig();
34
+
35
+ if (!pluginConfig.length) {
36
+ pluginConfig.push({});
37
+ await homebridge.updatePluginConfig(pluginConfig);
38
+ homebridge.showSchemaForm();
39
+ return;
40
+ }
41
+
42
+ //get accounts count
43
+ const accountsCount = pluginConfig[0].accounts.length;
44
+ this.deviceIndex = 0;
45
+ for (let i = 0; i < accountsCount; i++) {
46
+ //create buttons
47
+ const button = document.createElement("button");
48
+ button.setAttribute("type", "button");
49
+ button.setAttribute("id", `button${i}`);
50
+ button.setAttribute("class", "btn btn-primary");
51
+ button.innerText = pluginConfig[0].accounts[i].name;
52
+ document.getElementById("accounts").appendChild(button);
53
+
54
+ //get actuall value on account select
55
+ document.getElementById(`button${i}`).addEventListener('click', async () => {
56
+ for (let j = 0; j < accountsCount; j++) {
57
+ const setRemoveAtribute = j === i ? document.getElementById(`button${j}`).setAttribute("class", "btn btn-secondary") : document.getElementById(`button${j}`).setAttribute("class", "btn btn-primary");
58
+ }
59
+
60
+ document.getElementById('accountName').value = pluginConfig[0].accounts[i].name || '';
61
+ document.getElementById('name').value = pluginConfig[0].accounts[i].name || '';
62
+ document.getElementById('user').value = pluginConfig[0].accounts[i].user || '';
63
+ document.getElementById('passwd').value = pluginConfig[0].accounts[i].passwd || '';
64
+ document.getElementById('lnguage').value = pluginConfig[0].accounts[i].language || '';
65
+
66
+ const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
67
+ const setButtonName = accountConfigured ? document.getElementById('logIn').removeAttribute('disabled') : document.getElementById('logIn').setAttribute('disabled');
68
+ await homebridge.updatePluginConfig(pluginConfig);
69
+ this.deviceIndex = i;
70
+ });
71
+ const click = i === accountsCount - 1 ? document.getElementById(`button0`).click() : false;
72
+ const update = i === accountsCount - 1 ? await homebridge.updatePluginConfig(pluginConfig) : false;
73
+ };
74
+
75
+ //load melCloudAccount form
76
+ document.getElementById('melCloudAccount').style.display = 'block';
77
+
78
+ //watch for changes to the form
79
+ document.getElementById('configForm').addEventListener('input', async () => {
80
+ pluginConfig[0].accounts[this.deviceIndex].name = document.querySelector('#name').value;
81
+ pluginConfig[0].accounts[this.deviceIndex].user = document.querySelector('#user').value;
82
+ pluginConfig[0].accounts[this.deviceIndex].passwd = document.querySelector('#passwd').value;
83
+ pluginConfig[0].accounts[this.deviceIndex].language = document.querySelector('#language').value;
84
+
85
+ const accountConfigured = pluginConfig[0].accounts[i].name && pluginConfig[0].accounts[i].user && pluginConfig[0].accounts[i].passwd && pluginConfig[0].accounts[i].language;
86
+ const setButtonMode = accountConfigured ? document.getElementById('logIn').removeAttribute('disabled') : document.getElementById('logIn').setAttribute('disabled');
87
+
88
+ await homebridge.updatePluginConfig(pluginConfig);
89
+ await homebridge.savePluginConfig(pluginConfig);
90
+ });
91
+
92
+ //watch for changes to the config button
93
+ document.getElementById('configButton').addEventListener('click', async () => {
94
+ const showHideSettings = this.configButtonState ? homebridge.hideSchemaForm() : homebridge.showSchemaForm();
95
+ this.configButtonState = !this.configButtonState;
96
+ });
97
+
98
+ //watch for click on the login button
99
+ document.getElementById('logIn').addEventListener('click', async () => {
100
+ homebridge.showSpinner();
101
+
102
+ try {
103
+ await homebridge.updatePluginConfig(pluginConfig)
104
+ document.getElementById('info').innerHTML = '';
105
+
106
+ const accountName = pluginConfig[0].accounts[this.deviceIndex].name;
107
+ const user = pluginConfig[0].accounts[this.deviceIndex].user;
108
+ const passwd = pluginConfig[0].accounts[this.deviceIndex].passwd;
109
+ const language = pluginConfig[0].accounts[this.deviceIndex].language;
110
+ const enableDebugMode = pluginConfig[0].accounts[this.deviceIndex].enableDebugMode;
111
+ const refreshInterval = pluginConfig[0].accounts[this.deviceIndex].refreshInterval;
112
+
113
+ const payload = {
114
+ mode: 'connect',
115
+ accountName: accountName,
116
+ user: user,
117
+ passwd: passwd,
118
+ language: language,
119
+ enableDebugMode: enableDebugMode,
120
+ refreshInterval: refreshInterval
121
+ };
122
+ const response = await homebridge.request('/connect', payload);
123
+ const info = response.info;
124
+ const status = response.status;
125
+ const data = response.data;
126
+ const error = response.error;
127
+
128
+ switch (status) {
129
+ case 0:
130
+ document.getElementById('info').innerHTML = info;
131
+ document.getElementById('getDevices').removeAttribute('disabled');
132
+ break;
133
+ case 2:
134
+ document.getElementById('getDevices').setAttribute('disabled');
135
+ document.getElementById('info').innerHTML = `MELCloud error: ${JSON.stringify(error, null, 2)}`;
136
+ document.getElementById('info').style.color = 'red';
137
+ break;
138
+ };
139
+
140
+ homebridge.hideSpinner();
141
+ } catch (e) {
142
+ homebridge.toast.error(`Data error: ${e.message}`);
143
+ } finally {
144
+ homebridge.hideSpinner();
145
+ };
146
+ });
147
+
148
+ //get devices
149
+ document.getElementById('getDevices').addEventListener('click', async () => {
150
+ homebridge.showSpinner();
151
+ try {
152
+ const devicesId = [];
153
+
154
+ const payload = {
155
+ mode: 'getdevices'
156
+ };
157
+ const response = await homebridge.request('/getDevices', payload);
158
+
159
+ const info = response.info;
160
+ const status = response.status;
161
+ const devices = response.data;
162
+ const error = response.error;
163
+
164
+ switch (status) {
165
+ case 1:
166
+ //get device info fom devices
167
+ for (const deviceInfo of devices) {
168
+ const deviceId = deviceInfo.DeviceID;
169
+ const deviceName = deviceInfo.DeviceName;
170
+
171
+ //prepare device if not in devices array
172
+ if (!devicesId.includes(deviceId)) {
173
+ devicesId.push(deviceId);
174
+ }
175
+ };
176
+
177
+ document.getElementById('info').innerHTML = `${info} Found: ${devicesId.length} devices. Now You can start configure it.`;
178
+ await homebridge.updatePluginConfig(pluginConfig);
179
+ await homebridge.savePluginConfig(pluginConfig);
180
+ break;
181
+ case 2:
182
+ document.getElementById('info').innerHTML = `MELCloud error: ${JSON.stringify(error, null, 2)}`;
183
+ document.getElementById('info').style.color = 'red';
184
+ break;
185
+ };
186
+
187
+ homebridge.hideSpinner();
188
+ } catch (e) {
189
+ homebridge.toast.error(`Get devices error: ${e.message}`);
190
+ } finally {
191
+ homebridge.hideSpinner();
192
+ }
193
+ });
194
+
195
+ })();
196
+ </script>
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ const { HomebridgePluginUiServer, RequestError } = require('@homebridge/plugin-ui-utils');
4
+ const MelCloud = require('../src/melcloud.js')
5
+
6
+ class PluginUiServer extends HomebridgePluginUiServer {
7
+ constructor() {
8
+ super();
9
+
10
+ //get devices
11
+ this.onRequest('/getdevices', this.start.bind(this));
12
+
13
+ //connect
14
+ this.onRequest('/connect', this.start.bind(this));
15
+
16
+ //this MUST be called when you are ready to accept requests
17
+ this.ready();
18
+ };
19
+
20
+ async start(payload) {
21
+
22
+ const mode = payload.mode;
23
+ const accountName = payload.accountName;
24
+ const user = payload.user;
25
+ const passwd = payload.passwd;
26
+ const language = payload.language;
27
+ const enableDebugMode = payload.enableDebugMode;
28
+ const refreshInterval = payload.refreshInterval;
29
+ const accountInfoFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Account`;
30
+ const deviceInfoFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Device_`;
31
+ const melCloud = new MelCloud(user, passwd, language, accountInfoFile, deviceInfoFile, enableDebugMode, refreshInterval);
32
+
33
+ let data = {};
34
+ switch (mode) {
35
+ case 'connect':
36
+ try {
37
+ const response = await melCloud.connect();
38
+ return data = {
39
+ info: 'Connect to MELCloud success, now pres the button Get Devices.',
40
+ status: 1,
41
+ data: response
42
+ };
43
+ } catch (error) {
44
+ return data = {
45
+ info: 'Connect error:',
46
+ status: 2,
47
+ data: error
48
+ };
49
+ };
50
+ case 'getdevices':
51
+ try {
52
+ const response = await melCloud.chackDevicesList();
53
+ return data = {
54
+ info: 'Get devices success.',
55
+ status: 0,
56
+ data: response
57
+ };
58
+ } catch (error) {
59
+ return data = {
60
+ info: 'Get devices error:',
61
+ status: 2,
62
+ data: error
63
+ };
64
+ };
65
+ };
66
+ };
67
+ };
68
+
69
+ (() => {
70
+ return new PluginUiServer();
71
+ })();
package/index.js CHANGED
@@ -62,6 +62,7 @@ class MelCloudPlatform {
62
62
  const prefDir = path.join(api.user.storagePath(), 'melcloud');
63
63
  const accountInfoFile = `${prefDir}/${accountName}_Account`;
64
64
  const buildingsFile = `${prefDir}/${accountName}_Buildings`;
65
+ const deviceInfoFile = `${prefDir}/${accountName}_Device_`;
65
66
 
66
67
  //create directory if it doesn't exist
67
68
  try {
@@ -77,7 +78,7 @@ class MelCloudPlatform {
77
78
  const deviceRefreshInterval = account.deviceRefreshInterval * 1000 || 5000;
78
79
 
79
80
  //melcloud account
80
- const melCloud = new MelCloud(prefDir, accountName, user, passwd, language, enableDebugMode, accountInfoFile, buildingsFile, refreshInterval);
81
+ const melCloud = new MelCloud(user, passwd, language, accountInfoFile, buildingsFile, deviceInfoFile, enableDebugMode, refreshInterval);
81
82
  melCloud.on('checkDevicesListComplete', (accountInfo, contextKey, deviceInfo) => {
82
83
  const deviceId = deviceInfo.DeviceID.toString();
83
84
  const deviceType = deviceInfo.Type;
@@ -98,13 +99,13 @@ class MelCloudPlatform {
98
99
 
99
100
  //publish device
100
101
  api.publishExternalAccessories(CONSTANTS.PluginName, [accessory]);
101
- log.info(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
102
+ log.success(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
102
103
  })
103
104
  .on('devInfo', (devInfo) => {
104
105
  log.info(devInfo);
105
106
  })
106
107
  .on('success', (message) => {
107
- log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
108
+ log.success(`${deviceTypeText}, ${deviceName}, ${message}`);
108
109
  })
109
110
  .on('message', (message) => {
110
111
  log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
@@ -130,13 +131,13 @@ class MelCloudPlatform {
130
131
 
131
132
  //publish device
132
133
  api.publishExternalAccessories(CONSTANTS.PluginName, [accessory]);
133
- log.info(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
134
+ log.success(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
134
135
  })
135
136
  .on('devInfo', (devInfo) => {
136
137
  log.info(devInfo);
137
138
  })
138
139
  .on('success', (message) => {
139
- log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
140
+ log.success(`${deviceTypeText}, ${deviceName}, ${message}`);
140
141
  })
141
142
  .on('message', (message) => {
142
143
  log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
@@ -162,13 +163,13 @@ class MelCloudPlatform {
162
163
 
163
164
  //publish device
164
165
  api.publishExternalAccessories(CONSTANTS.PluginName, [accessory]);
165
- log.info(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
166
+ log.success(`${accountName}, ${deviceTypeText} ${deviceName}, published as external accessory.`);
166
167
  })
167
168
  .on('devInfo', (devInfo) => {
168
169
  log.info(devInfo);
169
170
  })
170
171
  .on('success', (message) => {
171
- log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
172
+ log.success(`${deviceTypeText}, ${deviceName}, ${message}`);
172
173
  })
173
174
  .on('message', (message) => {
174
175
  log.info(`${deviceTypeText}, ${deviceName}, ${message}`);
@@ -189,7 +190,7 @@ class MelCloudPlatform {
189
190
  }
190
191
  })
191
192
  .on('success', (message) => {
192
- log.info(`Account ${accountName}, ${message}`);
193
+ log.success(`Account ${accountName}, ${message}`);
193
194
  })
194
195
  .on('message', (message) => {
195
196
  log.info(`Account ${accountName}, ${message}`);
@@ -202,9 +203,8 @@ class MelCloudPlatform {
202
203
  })
203
204
  .on('error', async (error) => {
204
205
  log.error(`Account ${accountName}, ${error}, check again in: ${refreshInterval / 1000}s.`);
205
- melCloud.impulseGenerator.stop();
206
206
  await new Promise(resolve => setTimeout(resolve, refreshInterval));
207
- melCloud.connect();
207
+ await melCloud.connect();
208
208
  });
209
209
  };
210
210
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "2.3.3",
4
+ "version": "2.5.0-beta.0",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -19,6 +19,7 @@
19
19
  "main": "index.js",
20
20
  "files": [
21
21
  "src",
22
+ "homebridge-ui",
22
23
  "index.js",
23
24
  "config.schema.json",
24
25
  "package.json",
@@ -28,9 +29,10 @@
28
29
  ],
29
30
  "engines": {
30
31
  "node": ">=18.0.0",
31
- "homebridge": ">=1.8.0"
32
+ "homebridge": ">=2.0.0"
32
33
  },
33
34
  "dependencies": {
35
+ "@homebridge/plugin-ui-utils": "^1.0.3",
34
36
  "async-mqtt": "^2.6.3",
35
37
  "axios": "^1.7.5",
36
38
  "express": "^4.19.2"
package/src/melcloud.js CHANGED
@@ -8,13 +8,12 @@ const ImpulseGenerator = require('./impulsegenerator.js');
8
8
  const CONSTANTS = require('./constants.json');
9
9
 
10
10
  class MelCloud extends EventEmitter {
11
- constructor(prefDir, accountName, user, passwd, language, enableDebugMode, accountInfoFile, buildingsFile, refreshInterval) {
11
+ constructor(user, passwd, language, accountInfoFile, deviceInfoFile, buildingsFile, enableDebugMode, refreshInterval) {
12
12
  super();
13
- this.prefDir = prefDir;
14
- this.accountName = accountName;
15
13
  this.enableDebugMode = enableDebugMode;
16
14
  this.accountInfoFile = accountInfoFile;
17
15
  this.buildingsFile = buildingsFile;
16
+ this.deviceInfoFile = deviceInfoFile;
18
17
  this.refreshInterval = refreshInterval;
19
18
  this.devicesId = [];
20
19
 
@@ -46,9 +45,6 @@ class MelCloud extends EventEmitter {
46
45
  const debug = this.enableDebugMode ? this.emit('debug', `Connecting to MELCloud.`) : false;
47
46
 
48
47
  try {
49
- //create timers array
50
- const timers = [{ name: 'checkDevicesList', sampling: this.refreshInterval }];
51
-
52
48
  const axiosInstanceLogin = axios.create({
53
49
  method: 'POST',
54
50
  baseURL: CONSTANTS.ApiUrls.BaseURL,
@@ -83,9 +79,6 @@ class MelCloud extends EventEmitter {
83
79
  return;
84
80
  };
85
81
 
86
- //emit connect success
87
- this.emit('success', `Connect to MELCloud Success.`)
88
-
89
82
  //create axios instance get
90
83
  this.axiosInstanceGet = axios.create({
91
84
  method: 'GET',
@@ -130,8 +123,13 @@ class MelCloud extends EventEmitter {
130
123
  //check devices list
131
124
  await this.chackDevicesList();
132
125
 
126
+ //emit connect success
127
+ this.emit('success', `Connect to MELCloud Success.`)
128
+
133
129
  //start impulse generator
134
- this.impulseGenerator.start(timers);
130
+ const timers = [{ name: 'checkDevicesList', sampling: this.refreshInterval }];
131
+ this.impulseGenerator.start(timers)
132
+
135
133
  return true;
136
134
  } catch (error) {
137
135
  this.emit('error', `Connect to MELCloud error: ${error.message ?? error}.`);
@@ -183,7 +181,7 @@ class MelCloud extends EventEmitter {
183
181
  const deviceName = deviceInfo.DeviceName;
184
182
 
185
183
  //save every device info to the file
186
- const deviceInfoFile = `${this.prefDir}/${this.accountName}_Device_${deviceId}`;
184
+ const deviceInfoFile = `${this.deviceInfoFile}${deviceId}`;
187
185
  await this.saveData(deviceInfoFile, deviceInfo);
188
186
  const debug = this.enableDebugMode ? this.emit('debug', `Device: ${deviceName} info saved.`) : false;
189
187
 
@@ -192,9 +190,11 @@ class MelCloud extends EventEmitter {
192
190
  this.devicesId.push(deviceId);
193
191
  this.emit('checkDevicesListComplete', this.accountInfo, this.contextKey, deviceInfo);
194
192
  }
195
- }
196
- return true;
193
+ };
194
+
195
+ return devices;
197
196
  } catch (error) {
197
+ this.impulseGenerator.stop();
198
198
  throw new Error(`Scanning for devices error: ${error.message ?? error}.`);
199
199
  };
200
200
  }