homebridge-melcloud-control 4.3.5-beta.2 → 4.3.5-beta.21

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/index.js CHANGED
@@ -74,7 +74,6 @@ class MelCloudPlatform {
74
74
  //define directory and file paths
75
75
  const accountFile = `${prefDir}/${accountName}_Account`;
76
76
  const buildingsFile = `${prefDir}/${accountName}_Buildings`;
77
- const devicesFile = `${prefDir}/${accountName}_Devices`;
78
77
 
79
78
  //set account refresh interval
80
79
  const refreshInterval = (account.refreshInterval ?? 120) * 1000
@@ -85,22 +84,22 @@ class MelCloudPlatform {
85
84
  .on('start', async () => {
86
85
  try {
87
86
  //melcloud account
88
- let configureAccount;
87
+ let melcloud;
89
88
  let timmers = []
90
89
  switch (account.type) {
91
90
  case 'melcloud':
92
91
  timmers = [{ name: 'checkDevicesList', sampling: refreshInterval }];
93
- configureAccount = new MelCloud(account, accountFile, buildingsFile, devicesFile, true);
92
+ melcloud = new MelCloud(account, accountFile, buildingsFile, true);
94
93
  break;
95
94
  case 'melcloudhome':
96
95
  timmers = [{ name: 'connect', sampling: 3300000 }, { name: 'checkDevicesList', sampling: 3000 }];
97
- configureAccount = new MelCloudHome(account, accountFile, buildingsFile, devicesFile, true);
96
+ melcloud = new MelCloudHome(account, accountFile, buildingsFile, true);
98
97
  break;
99
98
  default:
100
99
  if (logLevel.warn) log.warn(`Unknown account type: ${account.type}.`);
101
100
  return;
102
101
  }
103
- configureAccount.on('success', (msg) => log.success(`${accountName}, ${msg}`))
102
+ melcloud.on('success', (msg) => log.success(`${accountName}, ${msg}`))
104
103
  .on('info', (msg) => log.info(`${accountName}, ${msg}`))
105
104
  .on('debug', (msg) => log.info(`${accountName}, debug: ${msg}`))
106
105
  .on('warn', (msg) => log.warn(`${accountName}, ${msg}`))
@@ -109,7 +108,7 @@ class MelCloudPlatform {
109
108
  //connect
110
109
  let accountInfo;
111
110
  try {
112
- accountInfo = await configureAccount.connect();
111
+ accountInfo = await melcloud.connect();
113
112
  if (!accountInfo.State) {
114
113
  if (logLevel.warn) log.warn(`${accountName}, ${accountInfo.Info}`);
115
114
  return;
@@ -118,21 +117,24 @@ class MelCloudPlatform {
118
117
  if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
119
118
  return;
120
119
  }
121
- if (logLevel.success) log.success(accountInfo.Info);
120
+ if (logLevel.success) log.success(`${accountName}, ${accountInfo.Info}`);
122
121
 
123
122
  //check devices list
124
- let devicesList;
123
+ let melcloudDevicesList;
125
124
  try {
126
- devicesList = await configureAccount.checkDevicesList();
127
- if (!devicesList.State) {
128
- if (logLevel.warn) log.warn(`${accountName}, ${devicesList.Info}`);
125
+ melcloudDevicesList = await melcloud.checkDevicesList();
126
+ if (!melcloudDevicesList.State) {
127
+ if (logLevel.warn) log.warn(`${accountName}, ${melcloudDevicesList.Info}`);
129
128
  return;
130
129
  }
131
130
  } catch (error) {
132
131
  if (logLevel.error) log.error(`${accountName}, Check devices list error: ${error.message ?? error}`);
133
132
  return;
134
133
  }
135
- if (logLevel.debug) log.info(devicesList.Info);
134
+ if (logLevel.debug) log.info(melcloudDevicesList.Info);
135
+
136
+ //start account impulse generator
137
+ await melcloud.impulseGenerator.state(true, timmers, false);
136
138
 
137
139
  //configured devices
138
140
  const ataDevices = (account.ataDevices || []).filter(device => device.id != null && String(device.id) !== '0');
@@ -144,14 +146,13 @@ class MelCloudPlatform {
144
146
  for (const [index, device] of devices.entries()) {
145
147
  //chack device from config exist on melcloud
146
148
  const displayType = device.displayType > 0;
147
- const deviceExistInMelCloud = devicesList.Devices.some(dev => dev.DeviceID === device.id);
149
+ const deviceExistInMelCloud = melcloudDevicesList.Devices.some(dev => dev.DeviceID === device.id);
148
150
  if (!deviceExistInMelCloud || !displayType) continue;
149
151
 
150
152
  device.id = String(device.id);
151
153
  const deviceName = device.name;
152
154
  const deviceType = device.type;
153
155
  const deviceTypeString = device.typeString;
154
- const deviceRefreshInterval = (device.refreshInterval ?? 5) * 1000;
155
156
  const defaultTempsFile = `${prefDir}/${accountName}_${device.id}_Temps`;
156
157
 
157
158
  // set rest ful port
@@ -179,15 +180,15 @@ class MelCloudPlatform {
179
180
  let configuredDevice;
180
181
  switch (deviceType) {
181
182
  case 0: //ATA
182
- configuredDevice = new DeviceAta(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile), devicesList.WebSocket;
183
+ configuredDevice = new DeviceAta(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
183
184
  break;
184
185
  case 1: //ATW
185
- configuredDevice = new DeviceAtw(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, devicesList.WebSocket);
186
+ configuredDevice = new DeviceAtw(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
186
187
  break;
187
188
  case 2:
188
189
  break;
189
190
  case 3: //ERV
190
- configuredDevice = new DeviceErv(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, devicesList.WebSocket);
191
+ configuredDevice = new DeviceErv(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud, melcloudDevicesList);
191
192
  break;
192
193
  default:
193
194
  if (logLevel.warn) log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, unknown device: ${deviceType}.`);
@@ -205,15 +206,9 @@ class MelCloudPlatform {
205
206
  if (accessory) {
206
207
  api.publishExternalAccessories(PluginName, [accessory]);
207
208
  if (logLevel.success) log.success(`${accountName}, ${deviceTypeString}, ${deviceName}, Published as external accessory.`);
208
-
209
- //start impulse generators for device
210
- await configuredDevice.startStopImpulseGenerator(true, [{ name: 'checkState', sampling: deviceRefreshInterval }]);
211
209
  }
212
210
  }
213
211
 
214
- //start account impulse generator
215
- await configureAccount.impulseGenerator.state(true, timmers, false);
216
-
217
212
  //stop start impulse generator
218
213
  await impulseGenerator.state(false);
219
214
  } catch (error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.3.5-beta.2",
4
+ "version": "4.3.5-beta.21",
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/deviceata.js CHANGED
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, AirConditioner } from './constants.js';
7
7
  let Accessory, Characteristic, Service, Categories, AccessoryUUID;
8
8
 
9
9
  class DeviceAta extends EventEmitter {
10
- constructor(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile, webSocket) {
10
+ constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud) {
11
11
  super();
12
12
 
13
13
  Accessory = api.platformAccessory;
@@ -17,7 +17,7 @@ class DeviceAta extends EventEmitter {
17
17
  AccessoryUUID = api.hap.uuid;
18
18
 
19
19
  //account config
20
- this.webSocket = webSocket;
20
+ this.melcloud = melcloud;
21
21
  this.account = account;
22
22
  this.accountType = account.type;
23
23
  this.accountName = account.name;
@@ -49,7 +49,6 @@ class DeviceAta extends EventEmitter {
49
49
  this.buttons = (device.buttonsSensors || []).filter(button => (button.displayType ?? 0) > 0);
50
50
 
51
51
  //files
52
- this.devicesFile = devicesFile;
53
52
  this.defaultTempsFile = defaultTempsFile;
54
53
  this.accountInfo = accountInfo;
55
54
  this.accountFile = accountFile;
@@ -108,16 +107,6 @@ class DeviceAta extends EventEmitter {
108
107
  this.accessory = {};
109
108
  };
110
109
 
111
- async startStopImpulseGenerator(state, timers = []) {
112
- try {
113
- //start impulse generator
114
- await this.melCloudAta.impulseGenerator.state(state, timers)
115
- return true;
116
- } catch (error) {
117
- throw new Error(`Impulse generator start error: ${error}`);
118
- }
119
- }
120
-
121
110
  async externalIntegrations() {
122
111
  //RESTFul server
123
112
  const restFulEnabled = this.restFul.enable || false;
@@ -1331,7 +1320,7 @@ class DeviceAta extends EventEmitter {
1331
1320
  async start() {
1332
1321
  try {
1333
1322
  //melcloud device
1334
- this.melCloudAta = new MelCloudAta(this.account, this.device, this.devicesFile, this.defaultTempsFile, this.accountFile, this.webSocket)
1323
+ this.melCloudAta = new MelCloudAta(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1335
1324
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
1336
1325
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1337
1326
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
@@ -1887,10 +1876,8 @@ class DeviceAta extends EventEmitter {
1887
1876
  //start external integrations
1888
1877
  if (this.restFul.enable || this.mqtt.enable) await this.externalIntegrations();
1889
1878
 
1890
- //check state
1891
- await this.melCloudAta.checkState();
1892
-
1893
1879
  //prepare accessory
1880
+ await new Promise(r => setTimeout(r, 5000));
1894
1881
  const accessory = await this.prepareAccessory();
1895
1882
  return accessory;
1896
1883
  } catch (error) {
package/src/deviceatw.js CHANGED
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, HeatPump } from './constants.js';
7
7
  let Accessory, Characteristic, Service, Categories, AccessoryUUID;
8
8
 
9
9
  class DeviceAtw extends EventEmitter {
10
- constructor(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile) {
10
+ constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud) {
11
11
  super();
12
12
 
13
13
  Accessory = api.platformAccessory;
@@ -17,6 +17,7 @@ class DeviceAtw extends EventEmitter {
17
17
  AccessoryUUID = api.hap.uuid;
18
18
 
19
19
  //account config
20
+ this.melcloud = melcloud;
20
21
  this.account = account;
21
22
  this.accountType = account.type;
22
23
  this.accountName = account.name;
@@ -52,7 +53,6 @@ class DeviceAtw extends EventEmitter {
52
53
  this.buttons = (device.buttonsSensors || []).filter(button => (button.displayType ?? 0) > 0);
53
54
 
54
55
  //files
55
- this.devicesFile = devicesFile;
56
56
  this.defaultTempsFile = defaultTempsFile;
57
57
  this.accountInfo = accountInfo;
58
58
  this.accountFile = accountFile;
@@ -111,17 +111,6 @@ class DeviceAtw extends EventEmitter {
111
111
  this.accessory = {};
112
112
  };
113
113
 
114
- async startStopImpulseGenerator(state, timers = []) {
115
- try {
116
- //start impulse generator
117
- await this.melCloudAtw.impulseGenerator.state(state, timers)
118
- return true;
119
- } catch (error) {
120
- throw new Error(`Impulse generator start error: ${error}`);
121
- }
122
- }
123
-
124
-
125
114
  async externalIntegrations() {
126
115
  //RESTFul server
127
116
  const restFulEnabled = this.restFul.enable || false;
@@ -1577,7 +1566,7 @@ class DeviceAtw extends EventEmitter {
1577
1566
  async start() {
1578
1567
  try {
1579
1568
  //melcloud device
1580
- this.melCloudAtw = new MelCloudAtw(this.account, this.device, this.devicesFile, this.defaultTempsFile, this.accountFile)
1569
+ this.melCloudAtw = new MelCloudAtw(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1581
1570
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, supportsHotWaterTank, supportsZone2) => {
1582
1571
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1583
1572
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
@@ -2316,10 +2305,8 @@ class DeviceAtw extends EventEmitter {
2316
2305
  //start external integrations
2317
2306
  if (this.restFul.enable || this.mqtt.enable) await this.externalIntegrations();
2318
2307
 
2319
- //check state
2320
- await this.melCloudAtw.checkState();
2321
-
2322
2308
  //prepare accessory
2309
+ await new Promise(r => setTimeout(r, 1000));
2323
2310
  const accessory = await this.prepareAccessory();
2324
2311
  return accessory;
2325
2312
  } catch (error) {
package/src/deviceerv.js CHANGED
@@ -7,7 +7,7 @@ import { TemperatureDisplayUnits, Ventilation } from './constants.js';
7
7
  let Accessory, Characteristic, Service, Categories, AccessoryUUID;
8
8
 
9
9
  class DeviceErv extends EventEmitter {
10
- constructor(api, account, device, devicesFile, defaultTempsFile, accountInfo, accountFile) {
10
+ constructor(api, account, device, defaultTempsFile, accountInfo, accountFile, melcloud) {
11
11
  super();
12
12
 
13
13
  Accessory = api.platformAccessory;
@@ -17,6 +17,7 @@ class DeviceErv extends EventEmitter {
17
17
  AccessoryUUID = api.hap.uuid;
18
18
 
19
19
  //account config
20
+ this.melcloud = melcloud;
20
21
  this.account = account;
21
22
  this.accountType = account.type;
22
23
  this.accountName = account.name;
@@ -44,7 +45,6 @@ class DeviceErv extends EventEmitter {
44
45
  this.buttons = (device.buttonsSensors || []).filter(button => (button.displayType ?? 0) > 0);
45
46
 
46
47
  //files
47
- this.devicesFile = devicesFile;
48
48
  this.defaultTempsFile = defaultTempsFile;
49
49
  this.accountInfo = accountInfo;
50
50
  this.accountFile = accountFile;
@@ -103,16 +103,6 @@ class DeviceErv extends EventEmitter {
103
103
  this.accessory = {};
104
104
  };
105
105
 
106
- async startStopImpulseGenerator(state, timers = []) {
107
- try {
108
- //start impulse generator
109
- await this.melCloudErv.impulseGenerator.state(state, timers)
110
- return true;
111
- } catch (error) {
112
- throw new Error(`Impulse generator start error: ${error}`);
113
- }
114
- }
115
-
116
106
  async externalIntegrations() {
117
107
  //RESTFul server
118
108
  const restFulEnabled = this.restFul.enable || false;
@@ -1130,7 +1120,7 @@ class DeviceErv extends EventEmitter {
1130
1120
  async start() {
1131
1121
  try {
1132
1122
  //melcloud device
1133
- this.melCloudErv = new MelCloudErv(this.account, this.device, this.devicesFile, this.defaultTempsFile, this.accountFile)
1123
+ this.melCloudErv = new MelCloudErv(this.account, this.device, this.defaultTempsFile, this.accountFile, this.melcloud)
1134
1124
  .on('deviceInfo', (modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
1135
1125
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1136
1126
  this.emit('devInfo', `---- ${this.deviceTypeString}: ${this.deviceName} ----`);
@@ -1589,10 +1579,8 @@ class DeviceErv extends EventEmitter {
1589
1579
  //start external integrations
1590
1580
  if (this.restFul.enable || this.mqtt.enable) await this.externalIntegrations();
1591
1581
 
1592
- //check state
1593
- await this.melCloudErv.checkState();
1594
-
1595
1582
  //prepare accessory
1583
+ await new Promise(r => setTimeout(r, 1000));
1596
1584
  const accessory = await this.prepareAccessory();
1597
1585
  return accessory;
1598
1586
  } catch (error) {
package/src/melcloud.js CHANGED
@@ -5,7 +5,7 @@ import Functions from './functions.js';
5
5
  import { ApiUrls } from './constants.js';
6
6
 
7
7
  class MelCloud extends EventEmitter {
8
- constructor(account, accountFile, buildingsFile, devicesFile, pluginStart = false) {
8
+ constructor(account, accountFile, buildingsFile, pluginStart = false) {
9
9
  super();
10
10
  this.accountType = account.type;
11
11
  this.user = account.user;
@@ -14,9 +14,9 @@ class MelCloud extends EventEmitter {
14
14
  this.logWarn = account.log?.warn;
15
15
  this.logError = account.log?.error;
16
16
  this.logDebug = account.log?.debug;
17
+
17
18
  this.accountFile = accountFile;
18
19
  this.buildingsFile = buildingsFile;
19
- this.devicesFile = devicesFile;
20
20
  this.headers = {};
21
21
 
22
22
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
@@ -110,9 +110,7 @@ class MelCloud extends EventEmitter {
110
110
  devicesList.Info = `Found ${devicesCount} devices`;
111
111
  devicesList.Devices = devices;
112
112
  devicesList.Headers = this.headers;
113
-
114
- await this.functions.saveData(this.devicesFile, devicesList);
115
- if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
113
+ this.emit('devicesList', devicesList);
116
114
 
117
115
  return devicesList;
118
116
  } catch (error) {
@@ -173,7 +171,7 @@ class MelCloud extends EventEmitter {
173
171
  });
174
172
 
175
173
  accountInfo.State = true;
176
- accountInfo.Info = 'Connect to MELCloud Success';
174
+ accountInfo.Info = 'Connect Success';
177
175
  accountInfo.UseFahrenheit = loginData.UseFahrenheit;
178
176
  accountInfo.Account = account;
179
177
  await this.functions.saveData(this.accountFile, accountInfo);
@@ -1,12 +1,10 @@
1
- import WebSocket from 'ws';
2
1
  import axios from 'axios';
3
2
  import EventEmitter from 'events';
4
- import ImpulseGenerator from './impulsegenerator.js';
5
3
  import Functions from './functions.js';
6
4
  import { ApiUrls, ApiUrlsHome, AirConditioner } from './constants.js';
7
5
 
8
6
  class MelCloudAta extends EventEmitter {
9
- constructor(account, device, devicesFile, defaultTempsFile, accountFile, webSocket) {
7
+ constructor(account, device, defaultTempsFile, accountFile, melcloud, melcloudDevicesList) {
10
8
  super();
11
9
  this.accountType = account.type;
12
10
  this.logSuccess = account.log?.success;
@@ -16,10 +14,8 @@ class MelCloudAta extends EventEmitter {
16
14
  this.restFulEnabled = account.restFul?.enable;
17
15
  this.mqttEnabled = account.mqtt?.enable;
18
16
  this.deviceId = device.id;
19
- this.devicesFile = devicesFile;
20
17
  this.defaultTempsFile = defaultTempsFile;
21
18
  this.accountFile = accountFile;
22
- this.webSocket = webSocket;
23
19
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
24
20
  .on('warn', warn => this.emit('warn', warn))
25
21
  .on('error', error => this.emit('error', error))
@@ -28,47 +24,75 @@ class MelCloudAta extends EventEmitter {
28
24
  //set default values
29
25
  this.deviceData = {};
30
26
  this.headers = {};
31
- this.socket = null;
32
- this.connecting = false;
33
- this.socketConnected = false;
34
- this.heartbeat = null;
35
27
 
36
- //lock flag
37
- this.locks = false;
38
- this.impulseGenerator = new ImpulseGenerator()
39
- .on('checkState', () => this.handleWithLock(async () => {
40
- await this.checkState();
41
- }))
42
- .on('state', (state) => {
43
- this.emit(state ? 'success' : 'warn', `Impulse generator ${state ? 'started' : 'stopped'}`);
44
- });
45
- }
46
-
47
- async handleWithLock(fn) {
48
- if (this.locks) return;
49
-
50
- this.locks = true;
51
- try {
52
- await fn();
53
- } catch (error) {
54
- this.emit('error', `Inpulse generator error: ${error}`);
55
- } finally {
56
- this.locks = false;
57
- }
58
- }
28
+ let deviceData = null;
29
+ melcloud.on('devicesList', async (devicesData) => {
30
+ this.headers = devicesData.Headers;
31
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
32
+ if (!deviceData) return;
33
+ deviceData.Scenes = devicesData.Scenes ?? [];
59
34
 
60
- cleanupSocket() {
61
- if (this.heartbeat) {
62
- clearInterval(this.heartbeat);
63
- this.heartbeat = null;
64
- }
35
+ if (!this.logDebug) this.emit('debug', `MELCloud: ${melcloud}`);
65
36
 
66
- if (this.socket) {
67
- try { this.socket.close(); } catch { }
68
- this.socket = null;
69
- }
37
+ //update state
38
+ await this.updateState(deviceData);
39
+ }).on('webSocket', async (parsedMessage) => {
40
+ try {
41
+ const messageData = parsedMessage?.[0]?.Data;
42
+ if (!messageData || !deviceData) return;
43
+
44
+ let updateState = false;
45
+ const unitId = messageData?.id;
46
+ switch (unitId) {
47
+ case this.deviceId:
48
+ const messageType = parsedMessage[0].messageType;
49
+ const settings = this.functions.parseArrayNameValue(messageData.settings);
50
+ switch (messageType) {
51
+ case 'unitStateChanged':
52
+
53
+ //update values
54
+ for (const [key, value] of Object.entries(settings)) {
55
+ if (!this.functions.isValidValue(value)) continue;
56
+
57
+ //update holiday mode
58
+ if (key === 'HolidayMode') {
59
+ deviceData.HolidayMode.Enabled = value;
60
+ continue;
61
+ }
62
+
63
+ //update device settings
64
+ if (key in deviceData.Device) {
65
+ deviceData.Device[key] = value;
66
+ }
67
+ }
68
+ updateState = true;
69
+ break;
70
+ case 'unitHolidayModeTriggered':
71
+ deviceData.Device.Power = settings.Power;
72
+ deviceData.HolidayMode.Enabled = settings.HolidayMode;
73
+ deviceData.HolidayMode.Active = messageData.active;
74
+ updateState = true;
75
+ break;
76
+ case 'unitWifiSignalChanged':
77
+ deviceData.Rssi = messageData.rssi;
78
+ updateState = true;
79
+ break;
80
+ default:
81
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
82
+ return;
83
+ }
84
+ break;
85
+ default:
86
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
87
+ return;
88
+ }
70
89
 
71
- this.socketConnected = false;
90
+ //update state
91
+ if (updateState) await this.updateState(deviceData);
92
+ } catch (error) {
93
+ if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
94
+ }
95
+ });
72
96
  }
73
97
 
74
98
  async updateState(deviceData) {
@@ -144,95 +168,6 @@ class MelCloudAta extends EventEmitter {
144
168
  };
145
169
  };
146
170
 
147
- async checkState() {
148
- try {
149
- //read device info from file
150
- const devicesData = await this.functions.readData(this.devicesFile, true);
151
- if (!devicesData) return;
152
-
153
- this.headers = devicesData.Headers;
154
- const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
155
- if (!deviceData) return;
156
- deviceData.Scenes = devicesData.Scenes ?? [];
157
-
158
- //web cocket connection
159
- if (this.accountType === 'melcloudhome' && !this.connecting) {
160
- this.connecting = true;
161
-
162
- try {
163
- this.webSocket.on('message', async (message) => {
164
- const parsedMessage = JSON.parse(message);
165
- const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
166
- if (parsedMessage.message === 'Forbidden') return;
167
-
168
- const messageData = parsedMessage?.[0]?.Data;
169
- if (!messageData) return;
170
-
171
- let updateState = false;
172
- const unitId = messageData?.id;
173
- switch (unitId) {
174
- case this.deviceId:
175
- if (!this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
176
- const messageType = parsedMessage[0].messageType;
177
- const settings = this.functions.parseArrayNameValue(messageData.settings);
178
- switch (messageType) {
179
- case 'unitStateChanged':
180
-
181
- //update values
182
- for (const [key, value] of Object.entries(settings)) {
183
- if (!this.functions.isValidValue(value)) continue;
184
-
185
- //update holiday mode
186
- if (key === 'HolidayMode') {
187
- deviceData.HolidayMode.Enabled = value;
188
- continue;
189
- }
190
-
191
- //update device settings
192
- if (key in deviceData.Device) {
193
- deviceData.Device[key] = value;
194
- }
195
- }
196
- updateState = true;
197
- break;
198
- case 'unitHolidayModeTriggered':
199
- deviceData.Device.Power = settings.Power;
200
- deviceData.HolidayMode.Enabled = settings.HolidayMode;
201
- deviceData.HolidayMode.Active = messageData.active;
202
- updateState = true;
203
- break;
204
- case 'unitWifiSignalChanged':
205
- deviceData.Rssi = messageData.rssi;
206
- updateState = true;
207
- break;
208
- default:
209
- if (!this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
210
- return;
211
- }
212
- break;
213
- default:
214
- if (!this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
215
- return;
216
- }
217
-
218
- //update state
219
- if (updateState) await this.updateState(deviceData);
220
- });
221
- } catch (error) {
222
- if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
223
- this.cleanupSocket();
224
- }
225
- }
226
-
227
- //update state
228
- await this.updateState(deviceData);
229
-
230
- return true;
231
- } catch (error) {
232
- throw new Error(`Check state error: ${error.message}`);
233
- };
234
- };
235
-
236
171
  async send(accountType, displayType, deviceData, flag, flagData) {
237
172
  try {
238
173
  let method = null
@@ -284,7 +219,8 @@ class MelCloudAta extends EventEmitter {
284
219
  headers: headers,
285
220
  data: payload
286
221
  });
287
- this.updateData(deviceData, updateState);
222
+
223
+ this.emit('deviceState', deviceData);
288
224
  return true;
289
225
  case "melcloudhome":
290
226
  switch (flag) {
@@ -375,7 +311,6 @@ class MelCloudAta extends EventEmitter {
375
311
  data: payload
376
312
  });
377
313
 
378
- this.updateData(deviceData, updateState);
379
314
  return true;
380
315
  default:
381
316
  return;
@@ -385,14 +320,5 @@ class MelCloudAta extends EventEmitter {
385
320
  throw new Error(`Send data error: ${error.message}`);
386
321
  }
387
322
  }
388
-
389
- updateData(deviceData, updateState = true) {
390
- this.locks = true;
391
- if (updateState) this.emit('deviceState', deviceData);
392
-
393
- setTimeout(() => {
394
- this.locks = false
395
- }, 5000);
396
- }
397
323
  };
398
324
  export default MelCloudAta;
@@ -1,12 +1,10 @@
1
- import WebSocket from 'ws';
2
1
  import axios from 'axios';
3
2
  import EventEmitter from 'events';
4
- import ImpulseGenerator from './impulsegenerator.js';
5
3
  import Functions from './functions.js';
6
4
  import { ApiUrls, ApiUrlsHome, HeatPump } from './constants.js';
7
5
 
8
6
  class MelCloudAtw extends EventEmitter {
9
- constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
7
+ constructor(account, device, defaultTempsFile, accountFile, melcloud, melcloudDevicesList) {
10
8
  super();
11
9
  this.accountType = account.type;
12
10
  this.logSuccess = account.log?.success;
@@ -16,7 +14,6 @@ class MelCloudAtw extends EventEmitter {
16
14
  this.restFulEnabled = account.restFul?.enable;
17
15
  this.mqttEnabled = account.mqtt?.enable;
18
16
  this.deviceId = device.id;
19
- this.devicesFile = devicesFile;
20
17
  this.defaultTempsFile = defaultTempsFile;
21
18
  this.accountFile = accountFile;
22
19
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
@@ -27,47 +24,69 @@ class MelCloudAtw extends EventEmitter {
27
24
  //set default values
28
25
  this.deviceData = {};
29
26
  this.headers = {};
30
- this.socket = null;
31
- this.connecting = false;
32
- this.socketConnected = false;
33
- this.heartbeat = null;
34
27
 
35
- //lock flag
36
- this.locks = false;
37
- this.impulseGenerator = new ImpulseGenerator()
38
- .on('checkState', () => this.handleWithLock(async () => {
39
- await this.checkState();
40
- }))
41
- .on('state', (state) => {
42
- this.emit(state ? 'success' : 'warn', `Impulse generator ${state ? 'started' : 'stopped'}`);
43
- });
44
- }
45
-
46
- async handleWithLock(fn) {
47
- if (this.locks) return;
48
-
49
- this.locks = true;
50
- try {
51
- await fn();
52
- } catch (error) {
53
- this.emit('error', `Inpulse generator error: ${error}`);
54
- } finally {
55
- this.locks = false;
56
- }
57
- }
28
+ let deviceData = null;
29
+ melcloud.on('devicesList', async (devicesData) => {
30
+ this.headers = devicesData.Headers;
31
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
32
+ if (!deviceData) return;
33
+ deviceData.Scenes = devicesData.Scenes ?? [];
58
34
 
59
- cleanupSocket() {
60
- if (this.heartbeat) {
61
- clearInterval(this.heartbeat);
62
- this.heartbeat = null;
63
- }
35
+ //update state
36
+ await this.updateState(deviceData);
37
+ }).on('message', async (message) => {
38
+ try {
39
+ const parsedMessage = JSON.parse(message);
40
+ const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
41
+ if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
42
+ if (parsedMessage.message === 'Forbidden') return;
43
+
44
+ const messageData = parsedMessage?.[0]?.Data;
45
+ if (!messageData || !deviceData) return;
46
+
47
+ let updateState = false;
48
+ const unitId = messageData?.id;
49
+ switch (unitId) {
50
+ case this.deviceId:
51
+ const messageType = parsedMessage[0].messageType;
52
+ switch (messageType) {
53
+ case 'unitStateChanged':
54
+ const settings = Object.fromEntries(
55
+ messageData.settings.map(({ name, value }) => {
56
+ let parsedValue = this.functions.convertValue(value);
57
+ return [name, parsedValue];
58
+ })
59
+ );
60
+ Object.assign(deviceData.Device, settings);
61
+ updateState = true;
62
+ break;
63
+ case 'unitHolidayModeTriggered':
64
+ deviceData.Device.Power = settings.Power;
65
+ deviceData.HolidayMode.Enabled = settings.HolidayMode;
66
+ deviceData.HolidayMode.Active = messageData.active;
67
+ updateState = true;
68
+ break;
69
+ case 'unitWifiSignalChanged':
70
+ deviceData.Rssi = messageData.rssi;
71
+ updateState = true;
72
+ break;
73
+ default:
74
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
75
+ return;
76
+ }
77
+ break;
78
+ default:
79
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
80
+ return;
81
+ }
64
82
 
65
- if (this.socket) {
66
- try { this.socket.close(); } catch { }
67
- this.socket = null;
68
- }
83
+ //update state
84
+ if (updateState) await this.updateState(deviceData);
85
+ } catch (error) {
86
+ if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
87
+ }
88
+ });
69
89
 
70
- this.socketConnected = false;
71
90
  }
72
91
 
73
92
  async updateState(deviceData) {
@@ -137,106 +156,6 @@ class MelCloudAtw extends EventEmitter {
137
156
  };
138
157
  };
139
158
 
140
- async checkState() {
141
- try {
142
- //read device info from file
143
- const devicesData = await this.functions.readData(this.devicesFile, true);
144
- if (!devicesData) return;
145
-
146
- this.headers = devicesData.Headers;
147
- const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
148
- if (!deviceData) return;
149
- deviceData.Scenes = devicesData.Scenes ?? [];
150
-
151
- //web cocket connection
152
- if (this.accountType === 'melcloudhome' && !this.connecting && !this.socketConnected) {
153
- this.connecting = true;
154
-
155
- const url = `${ApiUrlsHome.WebSocketURL}${devicesData.WebSocketOptions.Hash}`;
156
- try {
157
- const socket = new WebSocket(url, { headers: devicesData.WebSocketOptions.Headers })
158
- .on('error', (error) => {
159
- if (this.logError) this.emit('error', `Socket error: ${error}`);
160
- socket.close();
161
- })
162
- .on('close', () => {
163
- if (this.logDebug) this.emit('debug', `Socket closed`);
164
- this.cleanupSocket();
165
- })
166
- .on('open', () => {
167
- this.socket = socket;
168
- this.socketConnected = true;
169
- this.connecting = false;
170
- if (this.logSuccess) this.emit('success', `Socket Connect Success`);
171
-
172
- // heartbeat
173
- this.heartbeat = setInterval(() => {
174
- if (socket.readyState === socket.OPEN) {
175
- if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
176
- socket.ping();
177
- }
178
- }, 30000);
179
- })
180
- .on('pong', () => {
181
- if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
182
- })
183
- .on('message', async (message) => {
184
- const parsedMessage = JSON.parse(message);
185
- const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
186
- if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
187
- if (parsedMessage.message === 'Forbidden') return;
188
-
189
- const messageData = parsedMessage?.[0]?.Data;
190
- if (!messageData) return;
191
-
192
- let updateState = false;
193
- const unitId = messageData?.id;
194
- switch (unitId) {
195
- case this.deviceId:
196
- const messageType = parsedMessage[0].messageType;
197
- switch (messageType) {
198
- case 'unitStateChanged':
199
- const settings = Object.fromEntries(
200
- messageData.settings.map(({ name, value }) => {
201
- let parsedValue = this.functions.convertValue(value);
202
- return [name, parsedValue];
203
- })
204
- );
205
- Object.assign(deviceData.Device, settings);
206
- updateState = true;
207
- break;
208
- case 'unitWifiSignalChanged':
209
- deviceData.Rssi = messageData.rssi;
210
- updateState = true;
211
- break;
212
- default:
213
- if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
214
- return;
215
- }
216
- break;
217
- default:
218
- if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
219
- return;
220
- }
221
-
222
- //update state
223
- if (updateState) await this.updateState(deviceData);
224
- });
225
- } catch (error) {
226
- if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
227
- this.cleanupSocket();
228
- }
229
- }
230
-
231
- //update state
232
- await this.updateState(deviceData);
233
-
234
- return true;
235
- } catch (error) {
236
- throw new Error(`Check state error: ${error.message}`);
237
- };
238
- };
239
-
240
159
  async send(accountType, displayType, deviceData, flag, flagData) {
241
160
  try {
242
161
 
@@ -303,7 +222,8 @@ class MelCloudAtw extends EventEmitter {
303
222
  headers: headers,
304
223
  data: payload
305
224
  });
306
- this.updateData(deviceData);
225
+
226
+ this.emit('deviceState', deviceData);
307
227
  return true;
308
228
  case "melcloudhome":
309
229
  switch (flag) {
@@ -363,7 +283,6 @@ class MelCloudAtw extends EventEmitter {
363
283
  data: payload
364
284
  });
365
285
 
366
- this.updateData(deviceData, updateState);
367
286
  return true;
368
287
  default:
369
288
  return;
@@ -373,14 +292,5 @@ class MelCloudAtw extends EventEmitter {
373
292
  throw new Error(`Send data error: ${error.message}`);
374
293
  }
375
294
  }
376
-
377
- updateData(deviceData, updateState = true) {
378
- this.locks = true;
379
- if (updateState) this.emit('deviceState', deviceData);
380
-
381
- setTimeout(() => {
382
- this.locks = false
383
- }, 5000);
384
- }
385
295
  };
386
296
  export default MelCloudAtw;
@@ -1,12 +1,10 @@
1
- import WebSocket from 'ws';
2
1
  import axios from 'axios';
3
2
  import EventEmitter from 'events';
4
- import ImpulseGenerator from './impulsegenerator.js';
5
3
  import Functions from './functions.js';
6
4
  import { ApiUrls, ApiUrlsHome, Ventilation } from './constants.js';
7
5
 
8
6
  class MelCloudErv extends EventEmitter {
9
- constructor(account, device, devicesFile, defaultTempsFile, accountFile) {
7
+ constructor(account, device, defaultTempsFile, accountFile, melcloud, melcloudDevicesList) {
10
8
  super();
11
9
  this.accountType = account.type;
12
10
  this.logSuccess = account.log?.success;
@@ -16,7 +14,6 @@ class MelCloudErv extends EventEmitter {
16
14
  this.restFulEnabled = account.restFul?.enable;
17
15
  this.mqttEnabled = account.mqtt?.enable;
18
16
  this.deviceId = device.id;
19
- this.devicesFile = devicesFile;
20
17
  this.defaultTempsFile = defaultTempsFile;
21
18
  this.accountFile = accountFile;
22
19
  this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
@@ -28,42 +25,77 @@ class MelCloudErv extends EventEmitter {
28
25
  this.deviceData = {};
29
26
  this.headers = {};
30
27
 
31
- //lock flags
32
- this.locks = false;
33
- this.impulseGenerator = new ImpulseGenerator()
34
- .on('checkState', () => this.handleWithLock(async () => {
35
- await this.checkState();
36
- }))
37
- .on('state', (state) => {
38
- this.emit(state ? 'success' : 'warn', `Impulse generator ${state ? 'started' : 'stopped'}`);
39
- });
40
- }
28
+ let deviceData = null;
29
+ melcloud.on('devicesList', async (devicesData) => {
30
+ this.headers = devicesData.Headers;
31
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
32
+ if (!deviceData) return;
33
+ deviceData.Scenes = devicesData.Scenes ?? [];
41
34
 
42
- async handleWithLock(fn) {
43
- if (this.locks) return;
35
+ //update state
36
+ await this.updateState(deviceData);
37
+ }).on('webSocket', async (parsedMessage) => {
38
+ try {
39
+ const messageData = parsedMessage?.[0]?.Data;
40
+ if (!messageData || !deviceData) return;
44
41
 
45
- this.locks = true;
46
- try {
47
- await fn();
48
- } catch (error) {
49
- this.emit('error', `Inpulse generator error: ${error}`);
50
- } finally {
51
- this.locks = false;
52
- }
42
+ let updateState = false;
43
+ const unitId = messageData?.id;
44
+ switch (unitId) {
45
+ case this.deviceId:
46
+ const messageType = parsedMessage[0].messageType;
47
+ const settings = this.functions.parseArrayNameValue(messageData.settings);
48
+ switch (messageType) {
49
+ case 'unitStateChanged':
50
+
51
+ //update values
52
+ for (const [key, value] of Object.entries(settings)) {
53
+ if (!this.functions.isValidValue(value)) continue;
54
+
55
+ //update holiday mode
56
+ if (key === 'HolidayMode') {
57
+ deviceData.HolidayMode.Enabled = value;
58
+ continue;
59
+ }
60
+
61
+ //update device settings
62
+ if (key in deviceData.Device) {
63
+ deviceData.Device[key] = value;
64
+ }
65
+ }
66
+ updateState = true;
67
+ break;
68
+ case 'unitHolidayModeTriggered':
69
+ deviceData.Device.Power = settings.Power;
70
+ deviceData.HolidayMode.Enabled = settings.HolidayMode;
71
+ deviceData.HolidayMode.Active = messageData.active;
72
+ updateState = true;
73
+ break;
74
+ case 'unitWifiSignalChanged':
75
+ deviceData.Rssi = messageData.rssi;
76
+ updateState = true;
77
+ break;
78
+ default:
79
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
80
+ return;
81
+ }
82
+ break;
83
+ default:
84
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
85
+ return;
86
+ }
87
+
88
+ //update state
89
+ if (updateState) await this.updateState(deviceData);
90
+ } catch (error) {
91
+ if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
92
+ }
93
+ });
53
94
  }
54
95
 
55
- async checkState() {
96
+ async updateState(deviceData) {
56
97
  try {
57
- //read device info from file
58
- const devicesData = await this.functions.readData(this.devicesFile, true);
59
- if (!devicesData) return;
60
-
61
- this.headers = devicesData.Headers;
62
- const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
63
- if (!deviceData) return;
64
98
  if (this.accountType === 'melcloudhome') {
65
- deviceData.Scenes = devicesData.Scenes ?? [];
66
-
67
99
  //read default temps
68
100
  const temps = await this.functions.readData(this.defaultTempsFile, true);
69
101
  deviceData.Device.DefaultHeatingSetTemperature = temps?.defaultHeatingSetTemperature ?? 20;
@@ -194,7 +226,8 @@ class MelCloudErv extends EventEmitter {
194
226
  headers: headers,
195
227
  data: payload
196
228
  });
197
- this.updateData(deviceData);
229
+
230
+ this.emit('deviceState', deviceData);
198
231
  return true;
199
232
  case "melcloudhome":
200
233
  switch (flag) {
@@ -257,7 +290,7 @@ class MelCloudErv extends EventEmitter {
257
290
  headers: headers,
258
291
  data: payload
259
292
  });
260
- this.updateData(deviceData, updateState);
293
+
261
294
  return true;
262
295
  default:
263
296
  return;
@@ -267,14 +300,5 @@ class MelCloudErv extends EventEmitter {
267
300
  throw new Error(`Send data error: ${error.message}`);
268
301
  }
269
302
  }
270
-
271
- updateData(deviceData, updateState = true) {
272
- this.locks = true;
273
- if (updateState) this.emit('deviceState', deviceData);
274
-
275
- setTimeout(() => {
276
- this.locks = false
277
- }, 5000);
278
- }
279
303
  };
280
304
  export default MelCloudErv;
@@ -11,7 +11,7 @@ import { ApiUrlsHome, LanguageLocaleMap } from './constants.js';
11
11
  const execPromise = promisify(exec);
12
12
 
13
13
  class MelCloudHome extends EventEmitter {
14
- constructor(account, accountFile, buildingsFile, devicesFile, pluginStart = false) {
14
+ constructor(account, accountFile, buildingsFile, pluginStart = false) {
15
15
  super();
16
16
  this.accountType = account.type;
17
17
  this.user = account.user;
@@ -23,7 +23,7 @@ class MelCloudHome extends EventEmitter {
23
23
  this.logDebug = account.log?.debug;
24
24
  this.accountFile = accountFile;
25
25
  this.buildingsFile = buildingsFile;
26
- this.devicesFile = devicesFile;
26
+
27
27
  this.headers = {};
28
28
  this.webSocketOptions = {};
29
29
  this.socket = null;
@@ -228,7 +228,7 @@ class MelCloudHome extends EventEmitter {
228
228
  this.socket = new WebSocket(url, { headers: this.webSocketOptions.Headers })
229
229
  .on('error', (error) => {
230
230
  if (this.logError) this.emit('error', `Socket error: ${error}`);
231
- socket.close();
231
+ this.socket.close();
232
232
  })
233
233
  .on('close', () => {
234
234
  if (this.logDebug) this.emit('debug', `Socket closed`);
@@ -241,14 +241,22 @@ class MelCloudHome extends EventEmitter {
241
241
 
242
242
  // heartbeat
243
243
  this.heartbeat = setInterval(() => {
244
- if (socket.readyState === socket.OPEN) {
245
- if (!this.logDebug) this.emit('debug', `Socket send heartbeat`);
246
- socket.ping();
244
+ if (this.socket.readyState === this.socket.OPEN) {
245
+ if (this.logDebug) this.emit('debug', `Socket send heartbeat`);
246
+ this.socket.ping();
247
247
  }
248
248
  }, 30000);
249
249
  })
250
250
  .on('pong', () => {
251
- if (!this.logDebug) this.emit('debug', `Socket received heartbeat`);
251
+ if (this.logDebug) this.emit('debug', `Socket received heartbeat`);
252
+ })
253
+ .on('message', (message) => {
254
+ const parsedMessage = JSON.parse(message);
255
+ const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
256
+ if (!this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
257
+ if (parsedMessage.message === 'Forbidden') return;
258
+
259
+ this.emit('webSocket', parsedMessage);
252
260
  });
253
261
  } catch (error) {
254
262
  if (this.logError) this.emit('error', `Socket connection failed: ${error}`);
@@ -261,10 +269,7 @@ class MelCloudHome extends EventEmitter {
261
269
  devicesList.Devices = devices;
262
270
  devicesList.Scenes = scenes;
263
271
  devicesList.Headers = this.headers;
264
- devicesList.WebSocket = this.socket;
265
-
266
- await this.functions.saveData(this.devicesFile, devicesList);
267
- if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
272
+ this.emit('devicesList', devicesList);;
268
273
 
269
274
  return devicesList;
270
275
  } catch (error) {
@@ -436,7 +441,7 @@ class MelCloudHome extends EventEmitter {
436
441
  };
437
442
 
438
443
  accountInfo.State = true;
439
- accountInfo.Info = 'Connect to MELCloud Home Success';
444
+ accountInfo.Info = 'Connect Success';
440
445
  await this.functions.saveData(this.accountFile, accountInfo);
441
446
 
442
447
  return accountInfo;