homebridge-melcloud-control 4.13.1 → 4.14.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.
- package/CHANGELOG.md +15 -0
- package/homebridge-ui/public/index.html +1 -1
- package/package.json +1 -1
- package/src/melcloudata.js +29 -0
- package/src/melcloudhome.js +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -25,6 +25,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
25
25
|
- For plugin < v4.6.0 use Homebridge UI <= v5.5.0
|
|
26
26
|
- For plugin >= v4.6.0 use Homebridge UI >= v5.13.0
|
|
27
27
|
|
|
28
|
+
## [4.14.0-beta.0] - (17.10.2026)
|
|
29
|
+
|
|
30
|
+
### Changes
|
|
31
|
+
|
|
32
|
+
- added: MELCloud Home, outdoor temperature sensor for Air Conditioner (ATA) from the trend summary report, because the status of the unit does not contain the outdoor temperature
|
|
33
|
+
- the sensor is created when `Outdoor Temperature Sensor` is enabled and the unit has an outdoor reading in the report, the report is read every 30 minutes
|
|
34
|
+
- the report is updated only while the unit is running, so the sensor keeps the last known value when the unit is off
|
|
35
|
+
- units without an outdoor reading, for example multi split systems, stay without the sensor
|
|
36
|
+
|
|
37
|
+
## [4.13.2] - (17.10.2026)
|
|
38
|
+
|
|
39
|
+
### Changes
|
|
40
|
+
|
|
41
|
+
- fixed: Config UI, the error shown after a failed Login and Sync was an empty object `{}`, now the real error message is shown
|
|
42
|
+
|
|
28
43
|
## [4.13.1] - (29.09.2026)
|
|
29
44
|
|
|
30
45
|
### Changes
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
await homebridge.savePluginConfig();
|
|
478
478
|
} catch (error) {
|
|
479
479
|
updateInfo('info', `Prepare config error`, "red");
|
|
480
|
-
updateInfo('info1', `Error: ${JSON.stringify(error)}`, "red");
|
|
480
|
+
updateInfo('info1', `Error: ${error?.message ?? JSON.stringify(error)}`, "red");
|
|
481
481
|
} finally {
|
|
482
482
|
homebridge.hideSpinner();
|
|
483
483
|
$('logIn').disabled = false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.14.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",
|
package/src/melcloudata.js
CHANGED
|
@@ -12,6 +12,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
12
12
|
this.restFulEnabled = account.restFul?.enable;
|
|
13
13
|
this.mqttEnabled = account.mqtt?.enable;
|
|
14
14
|
this.deviceId = device.id;
|
|
15
|
+
this.temperatureOutdoorSensor = device.temperatureOutdoorSensor || false;
|
|
16
|
+
this.melCloudClass = melCloudClass;
|
|
15
17
|
this.defaultTempsFile = defaultTempsFile;
|
|
16
18
|
|
|
17
19
|
this.functions = new Functions(this.logWarn, this.logError, this.logDebug)
|
|
@@ -22,6 +24,8 @@ class MelCloudAta extends EventEmitter {
|
|
|
22
24
|
//set default values
|
|
23
25
|
this.deviceData = {};
|
|
24
26
|
this.client = melCloudClass.client;
|
|
27
|
+
this.outdoorReading = null;
|
|
28
|
+
this.outdoorCheckedAt = 0;
|
|
25
29
|
|
|
26
30
|
//handle melcloud events
|
|
27
31
|
melCloudClass.on('client', (client) => {
|
|
@@ -120,8 +124,33 @@ class MelCloudAta extends EventEmitter {
|
|
|
120
124
|
});
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
// MELCloud Home does not send the outdoor temperature in the status of the unit, read it from the trend summary report
|
|
128
|
+
// every 30 minutes, the report is updated only while the unit is running
|
|
129
|
+
async checkOutdoorTemperature(deviceData) {
|
|
130
|
+
if (this.accountTypeMelcloud || !this.temperatureOutdoorSensor || (deviceData.Device.HasOutdoorTemperature && !this.outdoorReading)) return;
|
|
131
|
+
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (now - this.outdoorCheckedAt >= 1_800_000) {
|
|
134
|
+
this.outdoorCheckedAt = now;
|
|
135
|
+
try {
|
|
136
|
+
this.outdoorReading = await this.melCloudClass.getOutdoorTemperature(this.deviceId) ?? this.outdoorReading;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (this.logWarn) this.emit('warn', `Unit ${this.deviceId}, ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!this.outdoorReading) return;
|
|
143
|
+
const { value, timestamp } = this.outdoorReading;
|
|
144
|
+
if (this.logDebug && timestamp && now - timestamp > 10_800_000) this.emit('debug', `Unit ${this.deviceId}, outdoor temperature ${value} is older than 3 hours, last measurement ${new Date(timestamp).toLocaleString()}`);
|
|
145
|
+
|
|
146
|
+
deviceData.Device.HasOutdoorTemperature = true;
|
|
147
|
+
deviceData.Device.OutdoorTemperature = value;
|
|
148
|
+
}
|
|
149
|
+
|
|
123
150
|
async updateState(type, deviceData) {
|
|
124
151
|
try {
|
|
152
|
+
await this.checkOutdoorTemperature(deviceData);
|
|
153
|
+
|
|
125
154
|
if (!this.accountTypeMelcloud) {
|
|
126
155
|
if (type === 'ws') {
|
|
127
156
|
deviceData.Device.OperationMode = AirConditioner.OperationModeMapEnumToEnumWs[deviceData.Device.OperationMode] ?? deviceData.Device.OperationMode;
|
package/src/melcloudhome.js
CHANGED
|
@@ -726,6 +726,36 @@ class MelCloudHome extends EventEmitter {
|
|
|
726
726
|
}
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
+
// Latest outdoor temperature of a unit from the trend summary report, the status of the unit does not contain it.
|
|
730
|
+
// The last point of every dataset is stamped with the generation time of the report and only repeats the last known
|
|
731
|
+
// value, so the time of the real measurement is the point before it. Returns null when the unit has no outdoor reading.
|
|
732
|
+
async getOutdoorTemperature(unitId) {
|
|
733
|
+
try {
|
|
734
|
+
const day = (offset) => new Date(Date.now() + offset * 86400000).toISOString().slice(0, 10);
|
|
735
|
+
const params = { unitId, period: 'Daily', from: `${day(-1)}T00:00:00.0000000`, to: `${day(1)}T00:00:00.0000000` };
|
|
736
|
+
const resp = await this.client.get(ApiUrls.Home.Get.ReportTrendSummary, { params });
|
|
737
|
+
if (this.logDebug) this.emit('debug', `Trend summary unit ${unitId}: ${JSON.stringify(resp.data)}`);
|
|
738
|
+
|
|
739
|
+
const reports = Array.isArray(resp.data) ? resp.data : [];
|
|
740
|
+
const datasets = reports.flatMap(report => report.datasets ?? []);
|
|
741
|
+
const outdoor = datasets.find(dataset => dataset.id === 'outside_temperature');
|
|
742
|
+
const points = (outdoor?.data ?? []).filter(point => this.functions.isValidValue(point?.y));
|
|
743
|
+
|
|
744
|
+
if (points.length > 0) {
|
|
745
|
+
const last = points.at(-1);
|
|
746
|
+
const otherStamps = datasets.filter(dataset => dataset !== outdoor).map(dataset => dataset.data?.at(-1)?.x);
|
|
747
|
+
const carried = points.length > 1 && otherStamps.includes(last.x);
|
|
748
|
+
return { value: last.y, timestamp: Date.parse((carried ? points.at(-2) : last).x) || null };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// no point in the requested range, use the last value before it
|
|
752
|
+
const trigger = reports.flatMap(report => report.previousTriggers ?? []).find(item => item.measure === 'outside_temperature' && this.functions.isValidValue(item.value));
|
|
753
|
+
return trigger ? { value: trigger.value, timestamp: Date.parse(trigger.trigger) || null } : null;
|
|
754
|
+
} catch (error) {
|
|
755
|
+
throw new Error(`Get outdoor temperature error: ${error.message}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
729
759
|
// ── Devices ───────────────────────────────────────────────────────────────
|
|
730
760
|
|
|
731
761
|
// Wraps checkDevicesList with a single retry on timeout or network error.
|