homebridge-melcloud-control 4.3.9-beta.9 → 4.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -22,6 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
22
 
23
23
  - Do not use Homebridge UI > v5.5.0 because of break config.json
24
24
 
25
+ # [4.3.9] - (01.12.2025)
26
+
27
+ ## Changes
28
+
29
+ - stability and performance improvements
30
+ - fix vane H/V display wrong state
31
+ - cleanup
32
+
25
33
  # [4.3.8] - (29.11.2025)
26
34
 
27
35
  ## Changes
package/index.js CHANGED
@@ -140,7 +140,7 @@ class MelCloudPlatform {
140
140
  //chack device is not disabled in config
141
141
  const displayType = device.displayType > 0;
142
142
  if (!displayType) {
143
- if (logLevel.warn) log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, disabled in config, will not be published in Home app.`);
143
+ if (logLevel.warn) log.warn(`${accountName}, ${deviceTypeString}, ${deviceName}, disabled in configuration, will not be published in the Home app.`);
144
144
  continue;
145
145
  }
146
146
 
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.9-beta.9",
4
+ "version": "4.3.9",
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/constants.js CHANGED
@@ -59,18 +59,20 @@ export const TemperatureDisplayUnits = ["°C", "°F"];
59
59
  export const AirConditioner = {
60
60
  SystemMapEnumToString: { 0: "Air Conditioner Off", 1: "Air Conditioner On", 2: "Air Conditioner Offline" },
61
61
  OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Automatic": 8, "Isee Heat": 9, "Isee Dry": 10, "Isee Cool": 11 },
62
- OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11 },
63
62
  OperationModeMapEnumToString: { 0: "0", 1: "Heat", 2: "Dry", 3: "Cool", 4: "4", 5: "5", 6: "6", 7: "Fan", 8: "Automatic", 9: "Isee Heat", 10: "Isee Dry", 11: "Isee Cool" },
64
- FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 },
63
+ OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11 },
64
+ FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
65
65
  FanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
66
- AktualFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 },
66
+ SetFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
67
+ SetFanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
68
+ AktualFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
67
69
  AktualFanSpeedMapEnumToString: { 0: "Quiet", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
68
70
  VaneVerticalDirectionMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "Six": 6, "Swing": 7 },
69
- VaneVerticalDirectionMapEnumToEnumWs: { 6: 7 },
70
71
  VaneVerticalDirectionMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Swing" },
72
+ VaneVerticalDirectionMapEnumToEnumWs: { 6: 7 },
71
73
  VaneHorizontalDirectionMapStringToEnum: { "Auto": 0, "Left": 1, "LeftCentre": 2, "Centre": 3, "RightCentre": 4, "Right": 5, "Six": 6, "Seven": 7, "Split": 8, "Nine": 9, "Ten": 10, "Eleven": 11, "Swing": 12 },
72
- VaneHorizontalDirectionMapEnumToEnumWs: { 7: 12 },
73
74
  VaneHorizontalDirectionMapEnumToString: { 0: "Auto", 1: "Left", 2: "LeftCentre", 3: "Centre", 4: "RightCentre", 5: "Right", 6: "Six", 7: "Seven", 8: "Split", 9: "Nine", 10: "Ten", 11: "Eleven", 12: "Swing" },
75
+ VaneHorizontalDirectionMapEnumToEnumWs: { 7: 12 },
74
76
  AirDirectionMapEnumToString: { 0: "Auto", 1: "Swing" },
75
77
  CurrentOperationModeMapEnumToStringHeatherCooler: { 0: "Inactive", 1: "Idle", 2: "Heating", 3: "Cooling" },
76
78
  CurrentOperationModeMapEnumToStringThermostat: { 0: "Inactive", 1: "Heating", 2: "Cooling" },
package/src/deviceata.js CHANGED
@@ -1397,11 +1397,11 @@ class DeviceAta extends EventEmitter {
1397
1397
  const supportsDry = deviceData.Device[supportDryKey];
1398
1398
  const supportsCool1 = deviceData.Device[supportCoolKey];
1399
1399
  const supportsCool = this.coolDryFanMode >= 1 && supportsCool1;
1400
- const numberOfFanSpeeds = supportsFanSpeed ? deviceData.Device.NumberOfFanSpeeds : 0;
1401
- const minTempHeat = 10;
1402
- const maxTempHeat = 31;
1403
- const minTempCoolDryAuto = 16;
1404
- const maxTempCoolDryAuto = 31;
1400
+ const numberOfFanSpeeds = deviceData.Device.NumberOfFanSpeeds;
1401
+ const minTempHeat = deviceData.Device.MinTempHeat ?? 10;
1402
+ const maxTempHeat = deviceData.Device.MaxTempHeat ?? 31;
1403
+ const minTempCoolDryAuto = deviceData.Device.MinTempAutomatic ?? 16;
1404
+ const maxTempCoolDryAuto = deviceData.Device.MaxTempAutomatic ?? 31;
1405
1405
 
1406
1406
  //device state
1407
1407
  const power = deviceData.Device.Power ?? false;
@@ -24,18 +24,23 @@ class MelCloudAta extends EventEmitter {
24
24
 
25
25
  //set default values
26
26
  this.deviceData = {};
27
- this.headers = {}
27
+ this.headers = {};
28
28
 
29
29
  //handle melcloud events
30
30
  let deviceData = null;
31
31
  melcloud.on('devicesList', async (devicesData) => {
32
- this.headers = devicesData.Headers;
33
- deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
34
- if (!deviceData) return;
35
- deviceData.Scenes = devicesData.Scenes ?? [];
32
+ try {
33
+ this.headers = devicesData.Headers;
34
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
35
+ if (!deviceData) return;
36
+ deviceData.Scenes = devicesData.Scenes ?? [];
36
37
 
37
- //update state
38
- await this.updateState(deviceData);
38
+ //update state
39
+ if (this.logDebug) this.emit('debug', `Request update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
40
+ await this.updateState(deviceData);
41
+ } catch (error) {
42
+ if (this.logError) this.emit('error', `Request process message error: ${error}`);
43
+ }
39
44
  }).on('webSocket', async (parsedMessage) => {
40
45
  try {
41
46
  const messageData = parsedMessage?.[0]?.Data;
@@ -62,12 +67,11 @@ class MelCloudAta extends EventEmitter {
62
67
 
63
68
  //update device settings
64
69
  if (key in deviceData.Device) {
65
- let parsedValue = this.functions.convertValue(value);
66
- deviceData.Device[key] = parsedValue;
70
+ deviceData.Device[key] = value;
67
71
  }
68
72
  }
69
73
 
70
- if (!this.logDebug) this.emit('debug', `Update device settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
74
+ if (this.logDebug) this.emit('debug', `WS update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
71
75
  updateState = true;
72
76
  break;
73
77
  case 'unitHolidayModeTriggered':
@@ -81,12 +85,12 @@ class MelCloudAta extends EventEmitter {
81
85
  updateState = true;
82
86
  break;
83
87
  default:
84
- if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
88
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${parsedMessage}`);
85
89
  return;
86
90
  }
87
91
  break;
88
92
  default:
89
- if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
93
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${parsedMessage}`);
90
94
  return;
91
95
  }
92
96
 
@@ -104,14 +108,12 @@ class MelCloudAta extends EventEmitter {
104
108
 
105
109
  if (type === 'ws') {
106
110
  deviceData.Device.OperationMode = AirConditioner.OperationModeMapEnumToEnumWs[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
107
- deviceData.Device.ActualFanSpeed = AirConditioner.AktualFanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
108
- deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
109
111
  deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapEnumToEnumWs[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
110
112
  deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapEnumToEnumWs[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
111
113
  } else {
112
114
  deviceData.Device.OperationMode = AirConditioner.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
113
115
  deviceData.Device.ActualFanSpeed = AirConditioner.AktualFanSpeedMapStringToEnum[deviceData.Device.ActualFanSpeed] ?? deviceData.Device.ActualFanSpeed;
114
- deviceData.Device.SetFanSpeed = AirConditioner.FanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
116
+ deviceData.Device.SetFanSpeed = AirConditioner.SetFanSpeedMapStringToEnum[deviceData.Device.SetFanSpeed] ?? deviceData.Device.SetFanSpeed;
115
117
  deviceData.Device.VaneHorizontalDirection = AirConditioner.VaneHorizontalDirectionMapStringToEnum[deviceData.Device.VaneHorizontalDirection] ?? deviceData.Device.VaneHorizontalDirection;
116
118
  deviceData.Device.VaneVerticalDirection = AirConditioner.VaneVerticalDirectionMapStringToEnum[deviceData.Device.VaneVerticalDirection] ?? deviceData.Device.VaneVerticalDirection;
117
119
  }
@@ -325,7 +327,7 @@ class MelCloudAta extends EventEmitter {
325
327
  //sens payload
326
328
  headers['Content-Type'] = 'application/json; charset=utf-8';
327
329
  headers.Origin = ApiUrlsHome.Origin;
328
- if (!this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
330
+ if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
329
331
 
330
332
  await axios(path, {
331
333
  method: method,
@@ -29,20 +29,20 @@ class MelCloudAtw extends EventEmitter {
29
29
  //handle melcloud events
30
30
  let deviceData = null;
31
31
  melcloud.on('devicesList', async (devicesData) => {
32
- this.headers = devicesData.Headers;
33
- deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
34
- if (!deviceData) return;
35
- deviceData.Scenes = devicesData.Scenes ?? [];
36
-
37
- //update state
38
- await this.updateState(deviceData);
39
- }).on('message', async (message) => {
40
32
  try {
41
- const parsedMessage = JSON.parse(message);
42
- const stringifyMessage = JSON.stringify(parsedMessage, null, 2);
43
- if (this.logDebug) this.emit('debug', `Incoming message: ${stringifyMessage}`);
44
- if (parsedMessage.message === 'Forbidden') return;
33
+ this.headers = devicesData.Headers;
34
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
35
+ if (!deviceData) return;
36
+ deviceData.Scenes = devicesData.Scenes ?? [];
45
37
 
38
+ //update state
39
+ if (this.logDebug) this.emit('debug', `Request update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
40
+ await this.updateState(deviceData);
41
+ } catch (error) {
42
+ if (this.logError) this.emit('error', `Request process message error: ${error}`);
43
+ }
44
+ }).on('webSocket', async (parsedMessage) => {
45
+ try {
46
46
  const messageData = parsedMessage?.[0]?.Data;
47
47
  if (!messageData || !deviceData) return;
48
48
 
@@ -51,15 +51,27 @@ class MelCloudAtw extends EventEmitter {
51
51
  switch (unitId) {
52
52
  case this.deviceId:
53
53
  const messageType = parsedMessage[0].messageType;
54
+ const settings = this.functions.parseArrayNameValue(messageData.settings);
54
55
  switch (messageType) {
55
56
  case 'unitStateChanged':
56
- const settings = Object.fromEntries(
57
- messageData.settings.map(({ name, value }) => {
58
- let parsedValue = this.functions.convertValue(value);
59
- return [name, parsedValue];
60
- })
61
- );
62
- Object.assign(deviceData.Device, settings);
57
+
58
+ //update values
59
+ for (const [key, value] of Object.entries(settings)) {
60
+ if (!this.functions.isValidValue(value)) continue;
61
+
62
+ //update holiday mode
63
+ if (key === 'HolidayMode') {
64
+ deviceData.HolidayMode.Enabled = value;
65
+ continue;
66
+ }
67
+
68
+ //update device settings
69
+ if (key in deviceData.Device) {
70
+ deviceData.Device[key] = value;
71
+ }
72
+ }
73
+
74
+ if (this.logDebug) this.emit('debug', `WS update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
63
75
  updateState = true;
64
76
  break;
65
77
  case 'unitHolidayModeTriggered':
@@ -73,24 +85,24 @@ class MelCloudAtw extends EventEmitter {
73
85
  updateState = true;
74
86
  break;
75
87
  default:
76
- if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
88
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${parsedMessage}`);
77
89
  return;
78
90
  }
79
91
  break;
80
92
  default:
81
- if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
93
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${parsedMessage}`);
82
94
  return;
83
95
  }
84
96
 
85
97
  //update state
86
- if (updateState) await this.updateState(deviceData);
98
+ if (updateState) await this.updateState(deviceData, 'ws');
87
99
  } catch (error) {
88
100
  if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
89
101
  }
90
102
  });
91
103
  }
92
104
 
93
- async updateState(deviceData) {
105
+ async updateState(deviceData, type) {
94
106
  try {
95
107
  if (this.accountType === 'melcloudhome') {
96
108
  deviceData.Device.OperationMode = HeatPump.OperationModeMapStringToEnum[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
@@ -228,7 +240,7 @@ class MelCloudAtw extends EventEmitter {
228
240
  }
229
241
 
230
242
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
231
-
243
+
232
244
  await axios(path, {
233
245
  method: 'POST',
234
246
  baseURL: ApiUrls.BaseURL,
@@ -29,13 +29,18 @@ class MelCloudErv extends EventEmitter {
29
29
  //handle melcloud events
30
30
  let deviceData = null;
31
31
  melcloud.on('devicesList', async (devicesData) => {
32
- this.headers = devicesData.Headers;
33
- deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
34
- if (!deviceData) return;
35
- deviceData.Scenes = devicesData.Scenes ?? [];
32
+ try {
33
+ this.headers = devicesData.Headers;
34
+ deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
35
+ if (!deviceData) return;
36
+ deviceData.Scenes = devicesData.Scenes ?? [];
36
37
 
37
- //update state
38
- await this.updateState(deviceData);
38
+ //update state
39
+ if (this.logDebug) this.emit('debug', `Request update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
40
+ await this.updateState(deviceData);
41
+ } catch (error) {
42
+ if (this.logError) this.emit('error', `Request process message error: ${error}`);
43
+ }
39
44
  }).on('webSocket', async (parsedMessage) => {
40
45
  try {
41
46
  const messageData = parsedMessage?.[0]?.Data;
@@ -65,6 +70,8 @@ class MelCloudErv extends EventEmitter {
65
70
  deviceData.Device[key] = value;
66
71
  }
67
72
  }
73
+
74
+ if (this.logDebug) this.emit('debug', `WS update settings: ${JSON.stringify(deviceData.Device, null, 2)}`);
68
75
  updateState = true;
69
76
  break;
70
77
  case 'unitHolidayModeTriggered':
@@ -78,24 +85,24 @@ class MelCloudErv extends EventEmitter {
78
85
  updateState = true;
79
86
  break;
80
87
  default:
81
- if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${stringifyMessage}`);
88
+ if (this.logDebug) this.emit('debug', `Unit ${unitId}, received unknown message type: ${parsedMessage}`);
82
89
  return;
83
90
  }
84
91
  break;
85
92
  default:
86
- if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${stringifyMessage}`);
93
+ if (this.logDebug) this.emit('debug', `Incoming unknown unit id: ${parsedMessage}`);
87
94
  return;
88
95
  }
89
96
 
90
97
  //update state
91
- if (updateState) await this.updateState(deviceData);
98
+ if (updateState) await this.updateState(deviceData, 'ws');
92
99
  } catch (error) {
93
100
  if (this.logError) this.emit('error', `Web socket process message error: ${error}`);
94
101
  }
95
102
  });
96
103
  }
97
104
 
98
- async updateState(deviceData) {
105
+ async updateState(deviceData, type) {
99
106
  try {
100
107
  if (this.accountType === 'melcloudhome') {
101
108
  //read default temps
@@ -233,7 +240,7 @@ class MelCloudErv extends EventEmitter {
233
240
  }
234
241
 
235
242
  if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
236
-
243
+
237
244
  await axios(path, {
238
245
  method: 'POST',
239
246
  baseURL: ApiUrls.BaseURL,
@@ -135,24 +135,13 @@ class MelCloudHome extends EventEmitter {
135
135
 
136
136
  const devices = buildingsList.flatMap(building => {
137
137
  // Funkcja kapitalizująca klucze obiektu
138
- const capitalizeKeys = obj =>
139
- Object.fromEntries(
140
- Object.entries(obj).map(([key, value]) => [
141
- key.charAt(0).toUpperCase() + key.slice(1),
142
- value
143
- ])
144
- );
138
+ const capitalizeKeys = obj => Object.fromEntries(Object.entries(obj).map(([key, value]) => [key.charAt(0).toUpperCase() + key.slice(1), value]));
145
139
 
146
140
  // Rekurencyjna kapitalizacja kluczy w obiekcie lub tablicy
147
141
  const capitalizeKeysDeep = obj => {
148
142
  if (Array.isArray(obj)) return obj.map(capitalizeKeysDeep);
149
143
  if (obj && typeof obj === 'object') {
150
- return Object.fromEntries(
151
- Object.entries(obj).map(([key, value]) => [
152
- key.charAt(0).toUpperCase() + key.slice(1),
153
- capitalizeKeysDeep(value)
154
- ])
155
- );
144
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key.charAt(0).toUpperCase() + key.slice(1), capitalizeKeysDeep(value)]));
156
145
  }
157
146
  return obj;
158
147
  };