Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eTRV Monitoring, Maintenance Dropdown, and Boiler Automation Support #98

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# mqtt-energenie-ener314rt
[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg)](https://github.com/Achronite/mqtt-energenie-ener314/graphs/commit-activity)
[![MAINTENANCE](https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg)](https://github.com/Achronite/mqtt-energenie-ener314/graphs/commit-activity)
[![Downloads](https://img.shields.io/npm/dm/mqtt-energenie-ener314rt.svg)](https://www.npmjs.com/package/mqtt-energenie-ener314rt)
![node](https://img.shields.io/node/v/mqtt-energenie-ener314rt)
[![Release](https://img.shields.io/github/release-pre/achronite/mqtt-energenie-ener314rt.svg)](https://github.com/Achronite/mqtt-energenie-ener314rt/releases)
Expand Down Expand Up @@ -325,7 +325,7 @@ Both MiHome heating devices are now supported (as of v0.7.x). Specifically the
These devices are battery operated, so energenie in order to save power, have implemented periods of sleep where the devices do not listen for commands. This can lead to a delay from when a command is sent to it being processed by the device. See **Command Caching** below.

### Command Caching
Battery powered energenie devices, such as the eTRV or Thermostat do not constantly listen for commands. For example, the eTRV reports its temperature at the *SET_REPORTING_INTERVAL* (default 5 minutes) after which the receiver is then activated to listen for commands. The receiver only remains active for 200ms or until a message is received.
Battery powered energenie devices, such as the eTRV or Thermostat do not constantly listen for commands. For example, the eTRV reports its temperature at the *SET_REPORT_PERIOD* (default 5 minutes) after which the receiver is then activated to listen for commands. The receiver only remains active for 200ms or until a message is received.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to REPORT_PERIOD (remove SET)


To cater for these hardware limitations a command will be held (cached) until a report is received by the monitor thread from the device; at this point the most recent cached message (only 1 is supported) will be sent to the device. Messages will continue to be resent until we know they have been succesfully received or until the number of retries has reached 0. When a command is known to have been processed (e.g DIAGNOSTICS) the 'command' and 'retries' topics are reset to 0.

Expand All @@ -334,20 +334,20 @@ The reason that a command may be resent multiple times is due to reporting issue
> **NOTE:** The performance of node may decrease when a command is cached due to dynamic polling. The frequency that the radio device is polled by the monitor thread automatically increases by a factor of 200 when a command is cached (it goes from checking every 5 seconds to every 25 milliseconds) this dramatically increases the chance of a message being correctly received sooner.

### eTRV Commands
The MiHome Thermostatic Radiator valve (eTRV) can accept commands to perform operations, provide diagnostics or perform self tests. The documented commands are provided in the table below. For this MQTT implementation most of the commands have been simplified under a single 'Maintenance' topic. If you are using MQTT Discovery in Home Assistant you should see a 'select' for this on your dashboard.
The MiHome Thermostatic Radiator valve (eTRV) can accept commands to perform operations, provide diagnostics or perform self tests. The documented commands are provided in the table below. For this MQTT implementation most of the commands have been simplified under a single 'MAINTENANCE' topic. If you are using MQTT Discovery in Home Assistant you should see a 'select' for this on your dashboard.

Where .data shows an entry in "", this is the string that should be sent as the 'Command' for the MQTT Maintenance topic. This can be used if you want to send a request without using the select dropdown set-up by MQTT discovery.
Where .data shows an entry in "", this is the string that should be sent as the 'Command' for the MQTT MAINTENANCE topic. This can be used if you want to send a request without using the select dropdown set-up by MQTT discovery.

| Command | MQTT Command Topic(s) | # | Description | .data | Response Msg |
|---|:---:|---|---|:---:|---|
|Clear|Maintenance|0|Cancel current outstanding cached command for the device (set command & retries to 0)| "Cancel Command"|All Msgs|
|Exercise Valve|Maintenance EXERCISE_VALVE|163|Send exercise valve command, recommended once a week to calibrate eTRV|"Exercise Valve"|DIAGNOSTICS|
|Low power mode|Maintenance LOW_POWER_MODE|164|This is used to enhance battery life by limiting the hunting of the actuator, ie it limits small adjustments to degree of opening, when the room temperature is close to the *TEMP_SET* point. A consequence of the Low Power mode is that it may cause larger errors in controlling room temperature to the set temperature.|0=Off<br>1=On OR "Low Power Mode ON" "Low Power Mode OFF"|No*|
|Valve state^|Maintenance<br>VALVE_STATE|165|Set valve state|"Valve Auto"<br>"Valve Open"<br>"Valve Closed"<br> OR 0=Open<br>1=Closed<br>2=Auto (default)|No|
|Diagnostics|Maintenance<br>DIAGNOSTICS|166|Request diagnostic data from device, if all is OK it will return 0. Otherwise see additional monitored values for status messages|"Request Diagnostics"|DIAGNOSTICS|
|Identify|Maintenance<br>IDENTIFY|191|Identify the device by making the green light flash on the selected eTRV for 60 seconds|"Identify"|No|
|Reporting Interval|Maintenance REPORTING_INTERVAL|210|Update reporting interval to requested value|300-3600 seconds|No|
|Voltage|Maintenance<br>VOLTAGE|226|Report current voltage of the batteries||VOLTAGE|
|Clear|MAINTENANCE|0|Cancel current outstanding cached command for the device (set command & retries to 0)| "Cancel Command"|All Msgs|
|Exercise Valve|MAINTENANCE EXERCISE_VALVE|163|Send exercise valve command, recommended once a week to calibrate eTRV|"Exercise Valve"|DIAGNOSTICS|
|Low power mode|MAINTENANCE LOW_POWER_MODE|164|This is used to enhance battery life by limiting the hunting of the actuator, ie it limits small adjustments to degree of opening, when the room temperature is close to the *TEMP_SET* point. A consequence of the Low Power mode is that it may cause larger errors in controlling room temperature to the set temperature.|0=Off<br>1=On OR "Low Power Mode ON" "Low Power Mode OFF"|No*|
|Valve state^|MAINTENANCE<br>VALVE_STATE|165|Set valve state|"Valve Auto"<br>"Valve Open"<br>"Valve Closed"<br> OR 0=Open<br>1=Closed<br>2=Auto (default)|No|
|Diagnostics|MAINTENANCE<br>DIAGNOSTICS|166|Request diagnostic data from device, if all is OK it will return 0. Otherwise see additional monitored values for status messages|"Request Diagnostics"|DIAGNOSTICS|
|Identify|MAINTENANCE<br>IDENTIFY|191|Identify the device by making the green light flash on the selected eTRV for 60 seconds|"Identify"|No|
|Reporting Interval|MAINTENANCE REPORT_PERIOD|210|Update reporting interval to requested value|300-3600 seconds|No|
|Voltage|MAINTENANCE<br>VOLTAGE|226|Report current voltage of the batteries||VOLTAGE|
|Target temperature|TARGET_TEMP|244|Send new target temperature for eTRV.<br>NOTE: The VALVE_STATE must be set to 'Auto' for this to work.|5-40<br>(Integer)|No|

> \* Although this will not auto-report, a subsequent call to *REQUEST_DIAGNOTICS* will confirm the *LOW_POWER_MODE* setting
Expand All @@ -360,7 +360,7 @@ To support the MiHome Radiator Valve (MIHO013) aka **'eTRV'**, additional code h

|Parameter|Description|Topics|Data|Discovery Type|
|---|---|:---:|:---:|:---:|
|Maintenance|For sending maintenance commands|state,command|None, Cancel Command, Request Diagnostics, Exercise Valve, Identify, Low Power Mode ON, Low Power Mode OFF, Valve Auto, Valve Open,Valve Closed|select|
|MAINTENANCE|For sending MAINTENANCE commands|state,command|None, Cancel Command, Request Diagnostics, Exercise Valve, Identify, Low Power Mode ON, Low Power Mode OFF, Valve Auto, Valve Open,Valve Closed|select|
|command|Current cached command being sent to device|state,command|None,...|sensor|
|retries|The number of remaining retries for 'command' to be sent to the device|state,*soon*|0-10|sensor|
|DIAGNOSTICS|Numeric diagnostic code|state|Numeric||
Expand All @@ -369,7 +369,7 @@ To support the MiHome Radiator Valve (MIHO013) aka **'eTRV'**, additional code h
|ERROR_TEXT|error information|state|text|sensor|
|EXERCISE_VALVE|The result of the *EXERCISE_VALVE* command|state|success, fail|binary_sensor|
|LOW_POWER_MODE|eTRV is in low power mode state>|state|ON, OFF|binary_sensor|
|REPORTING_INTERVAL|Frequency the eTRV will work up and report (in seconds)|command|300-3600|Number|
|REPORT_PERIOD|Frequency the eTRV will work up and report (in seconds)|command|300-3600|Number|
|TARGET_TEMP|Target temperature in celcius|state,command|5.0 to 40.0<br>0.5 increments|Number|
|TEMPERATURE|The current temperature in celcius|state|float|sensor|
|VALVE_STATE|Current valve mode/state|state|0=Open<br>1=Closed<br>2=Auto|sensor|
Expand Down
104 changes: 90 additions & 14 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const VALVE_STATE = 165;
const DIAGNOSTICS = 166;
const THERMOSTAT_MODE = 170; // Thermostat
const IDENTIFY = 191;
const REPORTING_INTERVAL= 210;
const REPORT_PERIOD = 210;
const TARGET_TEMP = 244;
const VOLTAGE = 226;
const SWITCH_STATE = 243;
Expand Down Expand Up @@ -289,7 +289,7 @@ client.on('message', function (topic, msg, packet) {
165 SET_VALVE_STATE Set valve state 0=Open, 1=Closed, 2=Auto
166 DIAGNOSTICS (DIAGNOSTICS)
191 IDENTIFY
210 REPORTING_INTERVAL 300-3600 seconds
210 REPORT_PERIOD 300-3600 seconds
226 REQUEST_VOLTAGE Report current voltage of the batteries (VOLTAGE)
TEMP_SET
*/
Expand All @@ -299,9 +299,9 @@ client.on('message', function (topic, msg, packet) {

// Convert OpenThings Cmd String to Numeric
switch (cmd_array[MQTTM_OT_CMD]) {
case 'Maintenance':
case 'MAINTENANCE':
// Special select processing from Home Assistant built for the eTRV
// The idea here is to translate the maintenance commands into OpenThings Commands
// The idea here is to translate the MAINTENANCE commands into OpenThings Commands
msg_data = 0;
switch (String(msg)) {
case 'None':
Expand Down Expand Up @@ -343,7 +343,7 @@ client.on('message', function (topic, msg, packet) {
otCommand = VOLTAGE;
break;
default:
log.warn('cmd', "Unsupported Maintenance command: %s type:%j for eTRV", msg, typeof(msg));
log.warn('cmd', "Unsupported MAINTENANCE command: %s type:%j for eTRV", msg, typeof(msg));
} // msg
break;

Expand Down Expand Up @@ -373,8 +373,8 @@ client.on('message', function (topic, msg, packet) {
log.verbose('<', "%s: null (retained)", stateTopic);
client.publish(stateTopic, undefined, { retain: true });
break;
case 'REPORTING_INTERVAL':
otCommand = REPORTING_INTERVAL;
case 'REPORT_PERIOD':
otCommand = REPORT_PERIOD;
break;
default:
// unsupported command
Expand Down Expand Up @@ -449,8 +449,8 @@ client.on('message', function (topic, msg, packet) {
case 'HUMID_OFFSET':
otCommand = HUMID_OFFSET;
break;
case 'REPORTING_INTERVAL': // DO NOT USE
otCommand = REPORTING_INTERVAL;
case 'REPORT_PERIOD': // DO NOT USE
otCommand = REPORT_PERIOD;
break;
case 'CANCEL':
case 1:
Expand Down Expand Up @@ -677,12 +677,88 @@ forked.on("message", msg => {
topic_key = null;
break;
case 'VALVE_STATE':
case 'REPORTING_INTERVAL':
case 'REPORT_PERIOD':
case 'TARGET_TEMP':
case 'ERROR_TEXT':
case 'THERMOSTAT_MODE':
case 'HYSTERESIS':
case 'TEMP_OFFSET':
case 'TEMPERATURE': {
// Validate message productId before proceeding to check its an eTRV
if (msg.productId == 3) {
log.info('eTRV TEMPERATURE monitor received: %j', msg);
const targetTempTopic = `${CONFIG.topic_stub}${msg.productId}/${msg.deviceId}/TARGET_TEMP/state`;

// Parse current temperature from eTRV and validate
const currentTemp = parseFloat(msg.TEMPERATURE);
if (isNaN(currentTemp)) {
log.warn(`eTRV TEMPERATURE Invalid current temperature data: currentTemp=${currentTemp}`);
} else {
// Set a timeout to handle cases where no response is received from MQTT, happens if no TARGET_TEMP has yet been set
let timeoutHandle = setTimeout(() => {
log.warn(`Timeout: No response received for TARGET_TEMP on topic: ${targetTempTopic}`);
// Unsubscribe from topic in timeout scenario
client.unsubscribe(targetTempTopic);
client.removeListener('message', onTargetTempMessage); // Ensure listener is removed on timeout
}, 5000);

// Subscribe to the TARGET_TEMP MQTT topic
log.info(`eTRV TEMPERATURE monitor requesting:`, `TARGET_TEMP from MQTT: ${targetTempTopic}`);
client.subscribe(targetTempTopic, { qos: 0 }, (err) => {
if (err) {
log.error(`Error subscribing to TARGET_TEMP topic: ${err}`);
clearTimeout(timeoutHandle);
} else {
log.info(`eTRV TEMPERATURE Subscribed to topic:`,`${targetTempTopic}`);

// Listen for messages on the subscribed topic
client.on('message', function onTargetTempMessage(topic, targetMsg) {
if (topic === targetTempTopic) {
// Clear the timeout as we received a message
clearTimeout(timeoutHandle);

if (!targetMsg) {
log.warn(`eTRV TEMPERATURE No value received in TARGET_TEMP topic:`,`${targetMsg}`);
} else {
log.info(`eTRV TEMPERATURE Received`,`TARGET_TEMP from MQTT: ${targetTempTopic}`);

// Parse target temperature and validate
const targetTemp = parseFloat(targetMsg);
if (isNaN(targetTemp)) {
log.warn(`eTRV TEMPERATURE Invalid target temperature data: targetTemp=${targetTemp}`);
} else {
log.info(`eTRV TEMPERATURE Data:`,`TARGET_TEMP: ${targetTemp}, currentTemp: ${currentTemp}`);

// Determine HVAC action based on temperature comparison
const hvacAction = currentTemp <= targetTemp ? 'ON' : 'OFF';
const deltaTemp = (currentTemp - targetTemp).toFixed(2);

log.info(`eTRV TEMPERATURE Calculated:`,`hvacAction:`,`${hvacAction}, deltaTemp: ${deltaTemp}`);

// Define topics for publishing HVAC_ACTION and delta_temp
const hvacTopic = `${CONFIG.topic_stub}${msg.productId}/${msg.deviceId}/HVAC_ACTION/state`;
const deltaTempTopic = `${CONFIG.topic_stub}${msg.productId}/${msg.deviceId}/DELTA_TEMP/state`;

// Publish the calculated values
try {
client.publish(hvacTopic, hvacAction, { qos: 0, retain: false });
client.publish(deltaTempTopic, deltaTemp.toString(), { qos: 0, retain: false });
log.info(`eTRV TEMPERATURE Published:`,`HVAC_ACTION and delta_temp successfully.`);
} catch (publishErr) {
log.error(`eTRV TEMPERATURE Error publishing to MQTT: ${publishErr}`);
}
}
}
// Unsubscribe from the topic and clean up the listener
client.unsubscribe(targetTempTopic);
client.removeListener('message', onTargetTempMessage);
}
});
}
});
}
}
}
case 'HUMID_OFFSET':
// These values need to be retained on MQTT as they are irregularly reported
retain = true;
Expand Down Expand Up @@ -767,10 +843,10 @@ forked.on("message", msg => {
client.publish(state_topic,state);
}

// Update Maintenance if retries=0 for trv
// Update MAINTENANCE if retries=0 for trv
if (msg.productId == MIHO013 && topic_key == "retries" && state == '0'){
// retries are now empty, also change the Maintenance Select back to None
state_topic = `${CONFIG.topic_stub}${msg.productId}/${msg.deviceId}/Maintenance/state`;
// retries are now empty, also change the MAINTENANCE Select back to None
state_topic = `${CONFIG.topic_stub}${msg.productId}/${msg.deviceId}/MAINTENANCE/state`;
log.verbose('<', "%s: None", state_topic);
client.publish(state_topic,"None");
}
Expand Down Expand Up @@ -978,7 +1054,7 @@ function lookupCommand( cmd, data ){
break;
case DIAGNOSTICS:
return 'Diagnostics';
case REPORTING_INTERVAL:
case REPORT_PERIOD:
command = 'Interval';
break;
case IDENTIFY:
Expand Down
4 changes: 4 additions & 0 deletions devices/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"main": true,
"config": {
"dev_cla": "power",
"stat_cla": "measurement",
"unit_of_meas": "W"
}
},
Expand All @@ -18,6 +19,7 @@
"stat_t": "~@/state",
"config": {
"dev_cla": "reactive_power",
"stat_cla": "measurement",
"unit_of_meas": "var"
}
},
Expand All @@ -28,6 +30,7 @@
"config": {
"val_tpl": "{{ value|round(2) }}",
"dev_cla": "frequency",
"stat_cla": "measurement",
"unit_of_meas": "Hz",
"en": "false"
}
Expand All @@ -38,6 +41,7 @@
"stat_t": "~@/state",
"config": {
"dev_cla": "voltage",
"stat_cla": "measurement",
"unit_of_meas": "V",
"en": "false"
}
Expand Down
4 changes: 4 additions & 0 deletions devices/2.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"stat_t": "~@/state",
"config": {
"dev_cla": "power",
"stat_cla": "measurement",
"unit_of_meas": "W"
}
},
Expand All @@ -29,6 +30,7 @@
"stat_t": "~@/state",
"config": {
"dev_cla": "reactive_power",
"stat_cla": "measurement",
"unit_of_meas": "var"
}
},
Expand All @@ -39,6 +41,7 @@
"config": {
"val_tpl": "{{ value|round(2) }}",
"dev_cla": "frequency",
"stat_cla": "measurement",
"unit_of_meas": "Hz",
"en": "false"
}
Expand All @@ -49,6 +52,7 @@
"stat_t": "~@/state",
"config": {
"dev_cla": "voltage",
"stat_cla": "measurement",
"unit_of_meas": "V",
"en": "false"
}
Expand Down
Loading