homebridge-melcloud-control 4.0.0-beta.43 → 4.0.0-beta.430

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/src/deviceatw.js CHANGED
@@ -6,7 +6,7 @@ import { TemperatureDisplayUnits, HeatPump } from './constants.js';
6
6
  let Accessory, Characteristic, Service, Categories, AccessoryUUID;
7
7
 
8
8
  class DeviceAtw extends EventEmitter {
9
- constructor(api, account, device, contextKey, devicesFile, useFahrenheit, restFul, mqtt) {
9
+ constructor(api, account, device, contextKey, devicesFile, defaultTempsFile, useFahrenheit, restFul, mqtt) {
10
10
  super();
11
11
 
12
12
  Accessory = api.platformAccessory;
@@ -16,8 +16,9 @@ class DeviceAtw extends EventEmitter {
16
16
  AccessoryUUID = api.hap.uuid;
17
17
 
18
18
  //account config
19
+ this.accountMelcloud = account.displayType === 'melcloud' ? true : false;
19
20
  this.device = device;
20
- this.displayMode = device.displayMode;
21
+ this.displayType = device.displayType;
21
22
  this.hideZone = device.hideZone;
22
23
  this.temperatureSensor = device.temperatureSensor || false;
23
24
  this.temperatureSensorFlow = device.temperatureSensorFlow || false;
@@ -40,6 +41,7 @@ class DeviceAtw extends EventEmitter {
40
41
  this.deviceName = device.name;
41
42
  this.deviceTypeText = device.typeString;
42
43
  this.devicesFile = devicesFile;
44
+ this.defaultTempsFile = defaultTempsFile;
43
45
  this.displayDeviceInfo = true;
44
46
 
45
47
  //external integrations
@@ -305,7 +307,7 @@ class DeviceAtw extends EventEmitter {
305
307
  this.accessory.zones.forEach((zone, i) => {
306
308
  const zoneName = zone.name
307
309
  const serviceName = `${deviceTypeText} ${accessoryName}: ${zoneName}`;
308
- switch (this.displayMode) {
310
+ switch (this.displayType) {
309
311
  case 1: //Heater Cooler
310
312
  if (this.logDebug) this.emit('debug', `Prepare heather/cooler ${zoneName} service`);
311
313
  const melCloudService = new Service.HeaterCooler(serviceName, `HeaterCooler ${deviceId} ${i}`);
@@ -1284,7 +1286,7 @@ class DeviceAtw extends EventEmitter {
1284
1286
  async start() {
1285
1287
  try {
1286
1288
  //melcloud device
1287
- this.melCloudAtw = new MelCloudAtw(this.device, this.contextKey, this.devicesFile)
1289
+ this.melCloudAtw = new MelCloudAtw(this.device, this.contextKey, this.devicesFile, this.defaultTempsFile)
1288
1290
  .on('deviceInfo', (manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion, hasHotWaterTank, hasZone2) => {
1289
1291
  if (this.logDeviceInfo && this.displayDeviceInfo) {
1290
1292
  this.emit('devInfo', `---- ${this.deviceTypeText}: ${this.deviceName} ----`);
@@ -1435,7 +1437,7 @@ class DeviceAtw extends EventEmitter {
1435
1437
  let temperatureSetPropsMaxValue = 100;
1436
1438
 
1437
1439
  for (let i = 0; i < zonesCount; i++) {
1438
- switch (this.displayMode) {
1440
+ switch (this.displayType) {
1439
1441
  case 1: //Heater Cooler
1440
1442
  switch (i) {
1441
1443
  case caseHeatPump: //Heat Pump Operation Mode - IDLE, HOT WATER, HEATING ZONES, COOLING, HOT WATER STORAGE, FREEZE STAT, LEGIONELLA, HEATING ECO, MODE 1, MODE 2, MODE 3, HEATING UP /// Unit Status - HEAT, COOL
package/src/deviceerv.js CHANGED
@@ -6,7 +6,7 @@ import { TemperatureDisplayUnits, Ventilation } from './constants.js';
6
6
  let Accessory, Characteristic, Service, Categories, AccessoryUUID;
7
7
 
8
8
  class DeviceErv extends EventEmitter {
9
- constructor(api, account, device, contextKey, devicesFile, useFahrenheit, restFul, mqtt) {
9
+ constructor(api, account, device, contextKey, devicesFile, defaultTempsFile, useFahrenheit, restFul, mqtt) {
10
10
  super();
11
11
 
12
12
  Accessory = api.platformAccessory;
@@ -16,8 +16,9 @@ class DeviceErv extends EventEmitter {
16
16
  AccessoryUUID = api.hap.uuid;
17
17
 
18
18
  //account config
19
+ this.accountMelcloud = account.displayType === 'melcloud' ? true : false;
19
20
  this.device = device;
20
- this.displayMode = device.displayMode;
21
+ this.displayType = device.displayType;
21
22
  this.temperatureSensor = device.temperatureSensor || false;
22
23
  this.temperatureSensorOutdoor = device.temperatureSensorOutdoor || false;
23
24
  this.temperatureSensorSupply = device.temperatureSensorSupply || false;
@@ -33,6 +34,7 @@ class DeviceErv extends EventEmitter {
33
34
  this.deviceName = device.name;
34
35
  this.deviceTypeText = device.typeString;
35
36
  this.devicesFile = devicesFile;
37
+ this.defaultTempsFile = defaultTempsFile;
36
38
  this.displayDeviceInfo = true;
37
39
 
38
40
  //external integrations
@@ -152,57 +154,57 @@ class DeviceErv extends EventEmitter {
152
154
  case 'Power':
153
155
  deviceData.Device[key] = value;
154
156
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.Power;
155
- set = await this.melCloudErv.send(deviceData, this.displayMode);
157
+ set = await this.melCloudErv.send(deviceData, this.displayType);
156
158
  break;
157
159
  case 'OperationMode':
158
160
  deviceData.Device[key] = value;
159
161
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.OperationMode;
160
- set = await this.melCloudErv.send(deviceData, this.displayMode);
162
+ set = await this.melCloudErv.send(deviceData, this.displayType);
161
163
  break;
162
164
  case 'VentilationMode':
163
165
  deviceData.Device[key] = value;
164
166
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.VentilationMode;
165
- set = await this.melCloudErv.send(deviceData, this.displayMode);
167
+ set = await this.melCloudErv.send(deviceData, this.displayType);
166
168
  break;
167
169
  case 'SetTemperature':
168
170
  deviceData.Device[key] = value;
169
171
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
170
- set = await this.melCloudErv.send(deviceData, this.displayMode);
172
+ set = await this.melCloudErv.send(deviceData, this.displayType);
171
173
  break;
172
174
  case 'DefaultCoolingSetTemperature':
173
175
  deviceData.Device[key] = value;
174
176
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
175
- set = await this.melCloudErv.send(deviceData, this.displayMode);
177
+ set = await this.melCloudErv.send(deviceData, this.displayType);
176
178
  break;
177
179
  case 'DefaultHeatingSetTemperature':
178
180
  deviceData.Device[key] = value;
179
181
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
180
- set = await this.melCloudErv.send(deviceData, this.displayMode);
182
+ set = await this.melCloudErv.send(deviceData, this.displayType);
181
183
  break;
182
184
  case 'NightPurgeMode':
183
185
  deviceData.Device[key] = value;
184
186
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.NightPurgeMode;
185
- set = await this.melCloudErv.send(deviceData, this.displayMode);
187
+ set = await this.melCloudErv.send(deviceData, this.displayType);
186
188
  break;
187
189
  case 'SetFanSpeed':
188
190
  deviceData.Device[key] = value;
189
191
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetFanSpeed;
190
- set = await this.melCloudErv.send(deviceData, this.displayMode);
192
+ set = await this.melCloudErv.send(deviceData, this.displayType);
191
193
  break;
192
194
  case 'HideRoomTemperature':
193
195
  deviceData[key] = value;
194
196
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.Prohibit;
195
- set = await this.melCloudErv.send(deviceData, this.displayMode);
197
+ set = await this.melCloudErv.send(deviceData, this.displayType);
196
198
  break;
197
199
  case 'HideSupplyTemperature':
198
200
  deviceData[key] = value;
199
201
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.Prohibit;
200
- set = await this.melCloudErv.send(deviceData, this.displayMode);
202
+ set = await this.melCloudErv.send(deviceData, this.displayType);
201
203
  break;
202
204
  case 'HideOutdoorTemperature':
203
205
  deviceData[key] = value;
204
206
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.Prohibit;
205
- set = await this.melCloudErv.send(deviceData, this.displayMode);
207
+ set = await this.melCloudErv.send(deviceData, this.displayType);
206
208
  break;
207
209
  default:
208
210
  this.emit('warn', `${integration}, received key: ${key}, value: ${value}`);
@@ -263,7 +265,7 @@ class DeviceErv extends EventEmitter {
263
265
 
264
266
  //services
265
267
  const serviceName = `${deviceTypeText} ${accessoryName}`;
266
- switch (this.displayMode) {
268
+ switch (this.displayType) {
267
269
  case 1: //Heater Cooler
268
270
  if (this.logDebug) this.emit('debug', `Prepare heather/cooler service`);
269
271
  this.melCloudService = new Service.HeaterCooler(serviceName, `HeaterCooler ${deviceId}`);
@@ -277,7 +279,7 @@ class DeviceErv extends EventEmitter {
277
279
  try {
278
280
  deviceData.Device.Power = [false, true][state];
279
281
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.Power;
280
- await this.melCloudErv.send(deviceData, this.displayMode);
282
+ await this.melCloudErv.send(deviceData, this.displayType);
281
283
  if (this.logInfo) this.emit('info', `Set power: ${state ? 'ON' : 'OFF'}`);
282
284
  } catch (error) {
283
285
  if (this.logWarn) this.emit('warn', `Set power error: ${error}`);
@@ -313,7 +315,7 @@ class DeviceErv extends EventEmitter {
313
315
  };
314
316
 
315
317
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.VentilationMode;
316
- await this.melCloudErv.send(deviceData, this.displayMode);
318
+ await this.melCloudErv.send(deviceData, this.displayType);
317
319
  const operationModeText = Ventilation.VentilationMode[deviceData.Device.VentilationMode];
318
320
  if (this.logInfo) this.emit('info', `Set operation mode: ${operationModeText}`);
319
321
  } catch (error) {
@@ -357,7 +359,7 @@ class DeviceErv extends EventEmitter {
357
359
  break;;
358
360
  };
359
361
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetFanSpeed;
360
- await this.melCloudErv.send(deviceData, this.displayMode);
362
+ await this.melCloudErv.send(deviceData, this.displayType);
361
363
  if (this.logInfo) this.emit('info', `Set fan speed mode: ${Ventilation.FanSpeed[fanSpeedModeText]}`);
362
364
  } catch (error) {
363
365
  if (this.logWarn) this.emit('warn', `Set fan speed mode error: ${error}`);
@@ -379,7 +381,7 @@ class DeviceErv extends EventEmitter {
379
381
  try {
380
382
  deviceData.Device.DefaultCoolingSetTemperature = value;
381
383
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
382
- await this.melCloudErv.send(deviceData, this.displayMode);
384
+ await this.melCloudErv.send(deviceData, this.displayType);
383
385
  if (this.logInfo) this.emit('info', `Set cooling threshold temperature: ${value}${this.accessory.temperatureUnit}`);
384
386
  } catch (error) {
385
387
  if (this.logWarn) this.emit('warn', `Set cooling threshold temperature error: ${error}`);
@@ -402,7 +404,7 @@ class DeviceErv extends EventEmitter {
402
404
  try {
403
405
  deviceData.Device.DefaultHeatingSetTemperature = value;
404
406
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
405
- await this.melCloudErv.send(deviceData, this.displayMode);
407
+ await this.melCloudErv.send(deviceData, this.displayType);
406
408
  if (this.logInfo) this.emit('info', `Set heating threshold temperature: ${value}${this.accessory.temperatureUnit}`);
407
409
  } catch (error) {
408
410
  if (this.logWarn) this.emit('warn', `Set heating threshold temperature error: ${error}`);
@@ -420,7 +422,7 @@ class DeviceErv extends EventEmitter {
420
422
  // value = value ? true : false;
421
423
  // deviceData.Device = deviceData.Device;
422
424
  // deviceData.Device.EffectiveFlags = CONSTANTS.Ventilation.EffectiveFlags.Prohibit;
423
- // await this.melCloudErv.send(deviceData, this.displayMode);
425
+ // await this.melCloudErv.send(deviceData, this.displayType);
424
426
  // if (this.logInfo) this.emit('info', `Set local physical controls: ${value ? 'LOCK' : 'UNLOCK'}`);
425
427
  // } catch (error) {
426
428
  // if (this.logWarn) this.emit('warn', `Set lock physical controls error: ${error}`);
@@ -486,7 +488,7 @@ class DeviceErv extends EventEmitter {
486
488
  break;
487
489
  };
488
490
 
489
- await this.melCloudErv.send(deviceData, this.displayMode);
491
+ await this.melCloudErv.send(deviceData, this.displayType);
490
492
  const operationModeText = Ventilation.VentilationMode[deviceData.Device.VentilationMode];
491
493
  if (this.logInfo) this.emit('info', `Set operation mode: ${operationModeText}`);
492
494
  } catch (error) {
@@ -512,7 +514,7 @@ class DeviceErv extends EventEmitter {
512
514
  try {
513
515
  deviceData.Device.SetTemperature = value;
514
516
  deviceData.Device.EffectiveFlags = Ventilation.EffectiveFlags.SetTemperature;
515
- await this.melCloudErv.send(deviceData, this.displayMode);
517
+ await this.melCloudErv.send(deviceData, this.displayType);
516
518
  if (this.logInfo) this.emit('info', `Set temperature: ${value}${this.accessory.temperatureUnit}`);
517
519
  } catch (error) {
518
520
  if (this.logWarn) this.emit('warn', `Set temperature error: ${error}`);
@@ -704,7 +706,7 @@ class DeviceErv extends EventEmitter {
704
706
  break;
705
707
  };
706
708
 
707
- await this.melCloudErv.send(deviceData, this.displayMode);
709
+ await this.melCloudErv.send(deviceData, this.displayType);
708
710
  if (this.logInfo) this.emit('info', `${state ? 'Set:' : 'Unset:'} ${name}`);
709
711
  } catch (error) {
710
712
  if (this.logWarn) this.emit('warn', `Set preset error: ${error}`);
@@ -818,7 +820,7 @@ class DeviceErv extends EventEmitter {
818
820
  break;
819
821
  };
820
822
 
821
- await this.melCloudErv.send(deviceData, this.displayMode);
823
+ await this.melCloudErv.send(deviceData, this.displayType);
822
824
  if (this.logInfo) this.emit('info', `${state ? `Set: ${buttonName}` : `Unset: ${buttonName}, Set: ${button.previousValue}`}`);
823
825
  } catch (error) {
824
826
  if (this.logWarn) this.emit('warn', `Set button error: ${error}`);
@@ -839,7 +841,7 @@ class DeviceErv extends EventEmitter {
839
841
  async start() {
840
842
  try {
841
843
  //melcloud device
842
- this.melCloudErv = new MelCloudErv(this.device, this.contextKey, this.devicesFile)
844
+ this.melCloudErv = new MelCloudErv(this.device, this.contextKey, this.devicesFile, this.defaultTempsFile)
843
845
  .on('deviceInfo', (manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion) => {
844
846
  if (this.logDeviceInfo && this.displayDeviceInfo) {
845
847
  this.emit('devInfo', `---- ${this.deviceTypeText}: ${this.deviceName} ----`);
@@ -956,7 +958,7 @@ class DeviceErv extends EventEmitter {
956
958
  };
957
959
 
958
960
  //ventilation mode - 0, HEAT, 2, COOL, 4, 5, 6, FAN, AUTO
959
- switch (this.displayMode) {
961
+ switch (this.displayType) {
960
962
  case 1: //Heater Cooler
961
963
  switch (ventilationMode) {
962
964
  case 0: //LOSSNAY
package/src/functions.js CHANGED
@@ -1,4 +1,9 @@
1
+ import fs from 'fs';
2
+ import { exec } from 'child_process';
1
3
  import { promises as fsPromises } from 'fs';
4
+ import { promisify } from 'util';
5
+ import puppeteer from 'puppeteer-core';
6
+ const execAsync = promisify(exec);
2
7
 
3
8
  class Functions {
4
9
  constructor() {
@@ -14,12 +19,128 @@ class Functions {
14
19
  }
15
20
  }
16
21
 
17
- async readData(path) {
22
+ async readData(path, parseJson = false) {
18
23
  try {
19
- const data = await fsPromises.readFile(path);
24
+ const data = await fsPromises.readFile(path, 'utf8');
25
+
26
+ if (parseJson) {
27
+ if (!data.trim()) {
28
+ // Empty file when expecting JSON
29
+ return null;
30
+ }
31
+ try {
32
+ return JSON.parse(data);
33
+ } catch (jsonError) {
34
+ throw new Error(`JSON parse error in file "${path}": ${jsonError.message}`);
35
+ }
36
+ }
37
+
38
+ // For non-JSON, just return file content (can be empty string)
20
39
  return data;
21
40
  } catch (error) {
22
- throw new Error(`Read data error: ${error}`);
41
+ if (error.code === 'ENOENT') {
42
+ // File does not exist
43
+ return null;
44
+ }
45
+ // Preserve original error details
46
+ const wrappedError = new Error(`Read data error for "${path}": ${error.message}`);
47
+ wrappedError.original = error;
48
+ throw wrappedError;
49
+ }
50
+ }
51
+
52
+
53
+ async ensureChromiumInstalled(logInfo, logWarn, logError) {
54
+ let chromiumPath = '/usr/bin/chromium-browser'; // default fallback Linux
55
+
56
+ try {
57
+ // --- Detect platform ---
58
+ const { stdout: archStdout } = await execAsync('uname -m');
59
+ const arch = archStdout.trim();
60
+ logInfo(`Detected architecture: ${arch}`);
61
+
62
+ const { stdout: osNameStdout } = await execAsync('uname -s');
63
+ const osName = osNameStdout.trim();
64
+ logInfo(`Detected OS: ${osName}`);
65
+
66
+ // --- macOS handling ---
67
+ if (osName === 'Darwin') {
68
+ logInfo('Running on macOS — using Puppeteer bundled Chromium');
69
+ chromiumPath = puppeteer.executablePath();
70
+ logInfo(`Chromium path: ${chromiumPath}`);
71
+ return chromiumPath;
72
+ }
73
+
74
+ // --- Linux handling ---
75
+ // Detect OS release if available
76
+ let linuxDistro = 'unknown';
77
+ try {
78
+ const { stdout: osRelease } = await execAsync('cat /etc/os-release');
79
+ linuxDistro = osRelease.split('\n')[0] || 'unknown';
80
+ } catch {
81
+ logWarn('/etc/os-release not found, skipping OS detection.');
82
+ }
83
+ logInfo(`Linux distro: ${linuxDistro}`);
84
+
85
+ // --- Check existing Chromium ---
86
+ const { stdout: chromiumCheck } = await execAsync('which chromium || which chromium-browser || true');
87
+ if (chromiumCheck.trim()) {
88
+ chromiumPath = chromiumCheck.trim();
89
+ logInfo(`Found system Chromium: ${chromiumPath}`);
90
+ return chromiumPath;
91
+ }
92
+
93
+ logWarn('Chromium not found. Attempting installation...');
94
+
95
+ // --- Try apt-get first ---
96
+ try {
97
+ await execAsync('sudo apt-get update -y && sudo apt-get install -y chromium-browser chromium-codecs-ffmpeg');
98
+ chromiumPath = '/usr/bin/chromium-browser';
99
+ logInfo('Chromium installed successfully via apt-get.');
100
+ return chromiumPath;
101
+ } catch (aptErr) {
102
+ logWarn('apt-get install failed. Trying apk (Alpine) or yum (RHEL/CentOS)...');
103
+ }
104
+
105
+ // --- Try Alpine APK ---
106
+ try {
107
+ await execAsync('sudo apk add --no-cache chromium ffmpeg');
108
+ chromiumPath = '/usr/bin/chromium-browser';
109
+ logInfo('Chromium installed successfully via apk.');
110
+ return chromiumPath;
111
+ } catch (apkErr) {
112
+ logWarn('apk install failed. Trying yum (RHEL/CentOS)...');
113
+ }
114
+
115
+ // --- Try YUM ---
116
+ try {
117
+ await execAsync('sudo yum install -y chromium chromium-codecs-ffmpeg');
118
+ chromiumPath = '/usr/bin/chromium-browser';
119
+ logInfo('Chromium installed successfully via yum.');
120
+ return chromiumPath;
121
+ } catch (yumErr) {
122
+ logWarn('yum install failed. Falling back to Puppeteer bundled Chromium.');
123
+ }
124
+
125
+ // --- Fallback: Puppeteer Chromium ---
126
+ chromiumPath = puppeteer.executablePath();
127
+ logWarn(`Using bundled Puppeteer Chromium at ${chromiumPath}`);
128
+ return chromiumPath;
129
+
130
+ } catch (err) {
131
+ logError(`Chromium detection/install error: ${err.message}`);
132
+ throw err;
133
+ }
134
+ }
135
+
136
+
137
+ async isRunningInDocker() {
138
+ try {
139
+ if (fs.existsSync('/.dockerenv')) return true;
140
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
141
+ return cgroup.includes('docker') || cgroup.includes('kubepods');
142
+ } catch {
143
+ return false;
23
144
  }
24
145
  }
25
146
  }