This project shows how I instrumented my oil boiler and what I did with the readings.
First off, I wanted to know what the temperatures of the Out and Return flows were and secondly, by logging when it was actually fired up, how much oil I was using. I have a new boiler that is set to give 21.5kW at an oil flow rate of 2.28l/h. But I have no power near the boiler other than when it is being demanded!
I decided to use an ESP 8266, namely a Wemos D1 with integral 18650 battery and charging circuit.
Sensors
Temperature - 3 x DS18B20 One Wire sensors to measure Output, Return and Cabinet temperatures. The one wire fed into D4. (I should have added a fourth to measure the room temperature!)
Boiler Demand (ie from the CH/HW controller) - I found a point on the board that went from 1V0 to 4V7 when the demand was present, so I fed this through a 47k/100k resistor chain to get near 3V3 as an input to D6.
Boiler Thermostat Demand (ie when the boiler is actually burning as demanded by its own internal thermostat) - The logic within the boiler is all at mains voltage so I bought a mains sensor which opto isolates the digital output. This I fed into D7.
Battery voltage - I put the battery voltage through a 22k/100k resistor chain to drop it to 3V3 max for feeding into the A2D converter.
During the summer when Boiler demand isn’t that great I found the Battery would go flat so I put the ESP into sleep mode after an hour of no demand. The code includes some redundant features, namely to sense the difference between the boiler’s thermostat demanding and the oil flow solenoid kicking in after the safety checks. In the end I just added a delay of 8 seconds within the templates in Home Assistant. Also lots of Serial.prints to aid debugging but not in live use. Here’s the code:
/* This will be the definitive Sketch for the Boiler sensor running on the
* WeMos8266 board with integrated battery. It also runs on the generic ESP8266
* development board in the breadboard.
*/
#include <SPI.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2 // Signal from Temperature sensors D4
#define LED_BUILTIN 16
#define BURN 14 // Mains sensor circuit on solenoid cable. D5
#define THERM 13 // Mains sensor circuit on boiler thermostat output cable. D7
#define POWER 12 // pick up from board sensing power is on. D6
#define ADIN A0 // Analogue to Digital converter pin for reading the temperature. AD
IPAddress server(192, 168, x, x);
const char* mqtt_user = "user";
const char* mqtt_pwd = "pwd";
boolean charging; //HIGH if the unit is being charged (Boiler is being demanded)
boolean burning; // HIGH if the boiler is fired up
boolean lastBurning = false; // Previous state
boolean therming; // HIGH if boiler thermostat is demanding heating
boolean lastTherming; // Previous state
boolean noChange = true; // Flag for no state change
float tempOut; // Temperature of boiler water outflow
float tempIn; // Temperature of returning water
float tempCab; // Temperature inside the boiler cabinet
float batteryV; // Battery voltage
int upTime = 0; // Up Time in Seconds
int secsSincePower; // Seconds since Power was on
int modeStartTime; // Time at which it enters either of the while loops in loop()
byte mac[6];
char* topic;
String payload;
char output[180];
int numberOfDevices; // Number of temperature devices found - Should be 3
long rssi; // WiFi RSSI Strength
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// We'll use this variable to store a found device address
DeviceAddress tempDeviceAddress; // Device address is a 16 character Hex number or 8 bytes
void callback(char* topic, byte* payload, unsigned int length) {
} // Not used
WiFiClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
void initWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin("SSID", "PASSWORD");
while ((WiFi.status() != WL_CONNECTED)) {
digitalWrite(LED_BUILTIN, LOW); // Turn on
delay(60);
digitalWrite(LED_BUILTIN, HIGH); // Turn off
delay(340);
Serial.print(".");
}
digitalWrite(LED_BUILTIN, HIGH); // Turn off
Serial.println("");
Serial.print("WiFi connected, IP address: "); Serial.println(WiFi.localIP());
WiFi.macAddress(mac);
Serial.print("MAC: ");
for(int i=0; i<5; i++){ // Print the first 5 with :
if (mac[i] < 16) Serial.print("0");
Serial.print(mac[i],HEX);
Serial.print(":");
}
if (mac[5] < 16) Serial.print("0"); // Print the 6th
Serial.println(mac[5],HEX);
}
void light_sleep(uint32_t sleep_time_in_ms){ // If sleep time is 0 then an indefinate interrupt wakeup is set
wifi_station_disconnect();
wifi_set_opmode_current(NULL_MODE);
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); // set sleep type, the above posters wifi_set_sleep_type() didnt seem to work for me although it did let me compile and upload with no errors
wifi_fpm_open(); // Enables force sleep
gpio_pin_wakeup_enable(GPIO_ID_PIN(12), GPIO_PIN_INTR_HILEVEL); // Pin D6
// GPIO_ID_PIN(2) corresponds to GPIO2 on ESP8266-01 , GPIO_PIN_INTR_LOLEVEL for a logic low, can also do other interrupts, see gpio.h above
if (sleep_time_in_ms == 0){
Serial.println("Going to sleep to be woken by interupt on GPIO 2 or D4");
delay(200); // Give time to print before sleeping
wifi_fpm_do_sleep(0xFFFFFFF); // Sleep for longest possible time
delay(100); // This is necessary to ensure it goes to sleep!
Serial.println("Wake up by interupt");
}
else{
Serial.print("Going to sleep now for "); Serial.print(sleep_time_in_ms/1000); Serial.println(" seconds.");
delay(200);
wifi_fpm_do_sleep(sleep_time_in_ms * 1000); // Sleep for set time (us)
delay(sleep_time_in_ms + 1);
Serial.println("Woken up after 10s");
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266Boiler", mqtt_user, mqtt_pwd)) {
Serial.println("connected");
client.setBufferSize(512); // Default is 256 which was too short for the config messages.
// Subscribe
}
else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void publishData(){
// Create a JSON string containing the 2 temoeratures, the POWER and BURN states and maybe the WiFi status
topic = "tele/boiler"; // Delete Breadboard for live system
payload = "{\"Outflow Temperature\":";
payload += String(tempOut);
payload += ",\"Inflow Temperature\":";
payload += String(tempIn);
payload += ",\"Cabinet Temperature\":";
payload += String(tempCab);
payload += ",\"Battery Voltage\":";
payload += String(batteryV);
payload += ",\"RSSI\":";
payload += String(rssi);
payload += ",\"Demand\":";
payload += charging ? "\"On\",\"Therm\":" : "\"Off\",\"Therm\":";
payload += therming ? "\"On\",\"Burn\":" : "\"Off\",\"Burn\":";
payload += burning ? "\"On\"}" : "\"Off\"}";
if (!client.connected()) reconnect(); // Reconnect to to the MQTT Broker
for (int i = 0; i <= payload.length(); i++) output[i] = payload[i];
client.publish(topic, output);
Serial.println(payload);
}
void getTemps(){
// Read the temperature voltages **********************************************************************************
sensors.requestTemperatures(); // Send the command to get temperatures
// Get temperature for each device - May need to adjust these indexes
if(sensors.getAddress(tempDeviceAddress, 0)){ // Cabinet
tempOut = sensors.getTempC(tempDeviceAddress);
}
if(sensors.getAddress(tempDeviceAddress, 1)){ // Output
tempCab = sensors.getTempC(tempDeviceAddress);
}
if(sensors.getAddress(tempDeviceAddress, 2)){ // Input (or Return)
tempIn = sensors.getTempCByIndex(2); //(tempDeviceAddress);
}
Serial.print("Boiler Output: "); Serial.print(tempOut); Serial.print("°C Input: "); Serial.print(tempIn);
Serial.print("°C Cabinet: "); Serial.print(tempCab); Serial.println("°C");
}
void flash(int noOfTimes){
for(int i=0; i<noOfTimes; i++){
digitalWrite(LED_BUILTIN, LOW); //On
delay(10);
digitalWrite(LED_BUILTIN, HIGH); //Off
if(i+1 < noOfTimes)delay(290); // No delay after last flash
}
}
void getBatteryV(){
// Reads the Battery Voltage using the A2D converter.
int adcV = analogRead(ADIN);
batteryV = adcV * 4.1 / 820; // with 47k/100k voltage divider
Serial.print("Battery Voltage: "); Serial.print(batteryV); Serial.print(" (Reading: "); Serial.print(adcV); Serial.println(")");
rssi = WiFi.RSSI();
Serial.print("WiFi RSSI: "); Serial.println(rssi);
}
// function to print a DS18B20 device address
void printAddress(DeviceAddress deviceAddress) {
for (uint8_t i = 0; i < 8; i++){
if (deviceAddress[i] < 16) Serial.print("0");
Serial.print(deviceAddress[i], HEX);
}
}
boolean isBurning(){ // Is the safety mechanism demanding the oil solenoid
return(!digitalRead(BURN)); // Only one place to forget the !
}
boolean isTherming(){ // Is the boiler's thermostat demanding the burner
return(!digitalRead(THERM)); // Only one place to forget the !
}
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.println("BoilerDS18B20LightSleepMQTT");
Serial.println();
gpio_init(); // Initilise GPIO pins
WiFi.mode( WIFI_OFF ); // Why?
WiFi.forceSleepBegin(); // Why?
sensors.begin(); // Start up the Dallas and OneWire stuff
// locate devices on the bus
numberOfDevices = sensors.getDeviceCount(); // Grab a count of devices on the wire - should be 3
Serial.print("Locating devices...");
Serial.print("Found ");
Serial.print(numberOfDevices, DEC);
Serial.println(" devices.");
// Loop through each device, print out address
for(int i=0;i<numberOfDevices; i++){
// Search the wire for address
if(sensors.getAddress(tempDeviceAddress, i)){
Serial.print("Found device ");
Serial.print(i, DEC);
Serial.print(" with address: ");
printAddress(tempDeviceAddress);
Serial.println();
} else {
Serial.print("Found ghost device at ");
Serial.print(i, DEC);
Serial.print(" but could not detect address. Check power and cabling");
}
}
pinMode(LED_BUILTIN, OUTPUT); // LED to give visual indication of what is going on
digitalWrite(LED_BUILTIN, HIGH); // Turn LED off
pinMode(POWER, INPUT); // HIGH if Power is on
pinMode(BURN, INPUT_PULLUP); // LOW if burning
pinMode(THERM, INPUT_PULLUP); // LOW if Boiler Thermostat is demanding heating
delay(1000); // Let things settle ?
upTime = millis()/1000;
Serial.print("Up Time (S):- ");
Serial.println(upTime);
charging = digitalRead(POWER); // Read ahead just once
noChange = true;
burning = false; // Sensor not implemented so set false
modeStartTime = millis()/1000; // Time at which loop() starts or repeats
}
void loop() {
// This loop will run over and over again. Initially on module startup and then after being woken from light sleep by
// the power coming back on, a change in state of charging or therming or every 30 seconds unless in light sleep
// ******************** Charging Loop *********************
while (charging){ // Stay in this loop until charging stops (ie boiler is no longer being demanded)
// Don't bother going to sleep as running the unit does not reduce the charging current
// The boiler will only be Therming in this loop
// The boiler will only be burning if it is Therming
// Therming is when the boiler thermostat is demanding the burner
Serial.println("In Charging While loop");
flash(3); // Give visual indication of bein in charging loop and about to publish
if ((WiFi.status() != WL_CONNECTED)) initWifi();
therming = isTherming();
lastTherming = therming;
getTemps();
getBatteryV(); // Gets rssi too
publishData(); // Publish Power, Therm, Burn and Temps
noChange = true;
// Wait 30 seconds reading the burn etc. Breakout if burn changes
while (((millis()/1000 - modeStartTime) < 30) && noChange){
Serial.println(millis()/1000 - modeStartTime);
delay(1000); // Loop once every second
charging = digitalRead(POWER);
if (!charging){ // charging/demand has stopped!
noChange = false;
getTemps(); // This bit is to force a write of the data in Home Assistant
getBatteryV(); // to give more of a square wave on the graphs by
charging = true; // Not true so will need to reset to false after publishing
// Note therming should be as was
Serial.println("Charging change publish.");
publishData(); // writing the old data 1 second before the new.
charging = false;
delay(1000);
}
else { // This bit only runs whilst charging/demand is on
therming = isTherming();
if (therming != lastTherming){
noChange = false;
getTemps(); // This bit is to force a write of the data in Home Assistant
getBatteryV(); // to give more of a square wave on the graphs by
therming = lastTherming; // Not true so will need to reset to false after publishing
// Note charging should not have changed
Serial.println("Therming change publish.");
publishData(); // writing the old data 1 second before the new.
therming = isTherming();
delay(1000);
}
}
} // ***************** End of 30 seconds or change loop ******
modeStartTime = millis()/1000; // Time the while(charging) loop repeats
} // ******************* End of Charging loop *************
// ********************* Not Charging Loop ****************
int chargeOffTime = millis()/1000; // Time we enter the Not Charging loop. To time the hour before sleep
while(!charging && (millis()/1000 - chargeOffTime) < 3600){ // Stay in this loop for up to an hour while NOT charging
Serial.println("In the Not Charging while loop");
flash(1);
// Publish every 30 seconds for 1 hour then go to light sleep to be woken by the POWER coming on
// No need to test for THERM or BURN only POWER
if ((WiFi.status() != WL_CONNECTED)) initWifi();
getTemps();
getBatteryV(); // Gets rssi too
burning = 0;
therming = 0;
publishData();
noChange = true;
while((millis()/1000) - modeStartTime < 30 && noChange){
Serial.println(millis()/1000 - modeStartTime);
delay(1000); // Loop once per second
charging = digitalRead(POWER);
if (charging){ // ie Demand has come back on
noChange = false;
getTemps(); // This bit is to force a write of the data in Home Assistant
getBatteryV(); // to give more of a square wave on the graphs by
charging = false; // Not true so will need to reset to true after publishing
Serial.println("Power back on publish");
publishData(); // writing the old data 1 second before the new.
charging = true;
delay(1000);
}
} // ******************* End of 30 seconds or change loop ***************
modeStartTime = millis()/1000; // Time the while(charging) loop repeats
} //******************* End of Not Charging or 60 minutes loop **********
// We get here if an hour has passed or the Power has come back on
if((millis()/1000 - chargeOffTime) >= 3600 && !charging) light_sleep(0); // Hour up. Go into light_sleep mode until charging starts again
// Woken up
// Need to publish a point here too with charging off to get a square edge
delay(100);
if ((WiFi.status() != WL_CONNECTED)) initWifi();
getTemps();
getBatteryV();
// charging and therming should still be false
publishData();
delay(1000);
charging = digitalRead(POWER); // Should be charging having just woken up or charging has started within the hour
} // ******** ************ End of loop() loop *****************
Here’s the YAML that pick up the readings within Home Assistant
mqtt:
sensor:
- name: "Boiler outflow water temperature"
unique_id: mqtt_0002
state_topic: "tele/boiler"
unit_of_measurement: '°C'
value_template: "{{value_json['Outflow Temperature']}}"
- name: "Boiler return water temperature"
unique_id: mqtt_0003
state_topic: "tele/boiler"
unit_of_measurement: '°C'
value_template: "{{value_json['Inflow Temperature']}}"
- name: "Boiler cabinet temperature"
unique_id: mqtt_0004
state_topic: "tele/boiler"
unit_of_measurement: '°C'
value_template: "{{value_json['Cabinet Temperature']}}"
- name: "Boiler Battery Voltage"
unique_id: mqtt_0005
state_topic: "tele/boiler"
unit_of_measurement: 'V'
value_template: "{{value_json['Battery Voltage']}}"
- name: "Boiler Sensor RSSI"
unique_id: mqtt_0006
state_topic: "tele/boiler"
unit_of_measurement: "dB"
value_template: "{{value_json['RSSI']}}"
force_update: true
binary_sensor:
- name: "Boiler demand"
state_topic: "tele/boiler"
value_template: "{{value_json['Demand']}}"
payload_on: "On"
payload_off: "Off"
force_update: true
- name: "Boiler thermostat demand"
state_topic: "tele/boiler"
value_template: "{{value_json['Therm']}}"
payload_on: "On"
payload_off: "Off"
force_update: true
Here’s 1 days worth of History on a cold day in December…
Other than monitoring and graphing the temperatures, I don’t do much with that info. I was thinking I could have a routine that checks that the boiler_outflow_water_temperature is going up when the boiler_thermostat_demand is on and sends me an email if it thinks there’s a problem. I haven’t got it right yet as I get emails daily!
The main sensor I use for further processing is the boiler_thermostat_demand. I use this to calculate such things like the oil remaining in the tank, oil usage by day, litres used and costs. It also gets fed into the Energy Integration as kWh of Gas (Oil isn’t an option yet?!). So here’s how I do all that.
The first thing I do is adjust the “on” time to get a truer log of when the boiler is actually burning. From the time the boiler_thermostat_demand turns on, there is about an 8 second delay before it actually fires up. I think this is why it’s doing its safety checks. So I have an automation that turns a boolean helper on after an 8 second delay
alias: Turn boiler State On
trigger:
- platform: state
entity_id: binary_sensor.boiler_thermostat_demand
from: "off"
to: "on"
for:
hours: 0
minutes: 0
seconds: 8
condition: []
action:
- service: input_boolean.turn_on
data: {}
target:
entity_id: input_boolean.boiler_state
mode: single
and another that turns is off immediately.
alias: Turn Boiler State Off
description: ""
trigger:
- platform: state
entity_id: binary_sensor.boiler_thermostat_demand
from: "on"
to: "off"
condition: []
action:
- service: input_boolean.turn_off
data: {}
target:
entity_id: input_boolean.boiler_state
mode: single
So helper input_boolean.boiler_state tells me if it’s burning or not.
Next step was to determine today’s (and yesterday’s (don’t ask me why)) burn time so I used the history_stats platform
sensor:
- platform: history_stats
name: Correct Burn Time Today
entity_id: input_boolean.boiler_state
state: "on"
type: time
start: "{{ now().replace(hour=0, minute=0, second=0) }}"
end: "{{ now() }}"
- platform: history_stats
name: Correct Burn Time Yesterday
entity_id: input_boolean.boiler_state
state: "on"
type: time
end: "{{ now().replace(hour=0, minute=0, second=0) }}"
duration:
hours: 24
I wanted to know the burn time since the new boiler was fitted so created the template entity sensor.boiler_hours_since_new. (I cannot remember why I needed the if statement)
sensor:
- platform: template
sensors:
boiler_hours_since_new:
friendly_name: "Boiler Hours Burning Since New"
unit_of_measurement: "h"
value_template: >-
{% if states('sensor.correct_burn_time_today') | float == 0 %}
{{ states('sensor.boiler_hours_since_new') }}
{% else %}
{{states('input_number.total_boiler_burn_time_cop_yesterday') | float + states('sensor.correct_burn_time_today') | float }}
{% endif %}
The helper input_number.total_boiler_burn_time_cop_yesterday gets updated each day just after midnight (when the boiler should be off) by an automation
alias: Update Total Boiler Burn Time COP Yesterday
description: Runs just after midnight
trigger:
- platform: time
at: "00:01"
condition: []
action:
- service: input_number.set_value
target:
entity_id: input_number.total_boiler_burn_time_cop_yesterday
data_template:
value: "{{states(\"sensor.boiler_hours_since_new\")}}"
mode: single
This is a Grafana panel showing the last 8 months’ worth of data. It took me a while to sort out the logic and I didn’t set it up straight away!
Now, moving on to oil usage, it can be expressed in Litres, kWh and cost. I have helpers to use in these conversions, namely input_number.oil_burn_rate set to 2.28l/m as per the boiler manual; input_number.boiler_rating set to 21.5kW/h and input_number.last_oil_price which changes from one delivery to the next and is currently 0.83ÂŁ/L.
Template sensors get me todays values…
sensor:
- platform: template
sensors:
oil_litres_today:
friendly_name: "Oil Used Today"
unit_of_measurement: "L"
value_template: "{{states('sensor.correct_burn_time_today') | float * states('input_number.oil_burn_rate') | float}}"
- platform: template
sensors:
oil_cost_today:
friendly_name: "Oil Cost Today"
unit_of_measurement: "ÂŁ"
value_template: "{{states('sensor.oil_litres_today') | float * states('input_number.last_oil_price') | float}}"
- platform: template
sensors:
oil_kwh_today:
friendly_name: "Oil kWh Today"
unit_of_measurement: "kWh"
value_template: "{{states('sensor.correct_burn_time_today') | float * states('input_number.boiler_rating') | float}}"
Next I have another helper with the current Oil Tank level - input_number.test_oil_remaining which I update through the developer tools when I have a delivery and gets updated after each burn through an Automation…
alias: Test Oil Remaining
description: Updates oil remaining as the History sensor.oil_litres_today increases
trigger:
- platform: state
entity_id: sensor.oil_litres_today
condition:
- condition: numeric_state
entity_id: sensor.oil_litres_today
above: "0"
action:
- delay:
hours: 0
minutes: 0
seconds: 1
milliseconds: 0
- service: input_number.set_value
data_template:
value: >-
{{ states("input_number.oil_remaining_yesterday") | float -
states("sensor.oil_litres_today") | float }}
target:
entity_id: input_number.test_oil_tank_remaining
mode: single
Note that this relies on another helper input_number.oil_remaining_yesterday that gets updated 1 minute after midnight in the same automation that updates the burn time cop yesterday above. It is another Action
- service: input_number.set_value
target:
entity_id: input_number.oil_remaining_yesterday
data_template:
value: "{{ (states(\"input_number.test_oil_tank_remaining\")) }}"
So I can now have Guages and Graphs on my dashboards. I could also have notifications of when I need to reorder, but I’ve not done that yet. Having burnt through 1000L of oil since running this system, the manufacturer’s claim of 2.28L/h seems pretty spot on.
The last thing I’m going to add here is how I get the data into the Energy Integration so that it can do the serious stuff of logging usage and presenting it by day, week, month etc. First off, there is no mention of Oil in the Energy Integration, but it does have Gas. I don’t use gas so thought this could be used instead. The only meaningful unit to provide the Integration is kWh, so that’s what i tried. However, despite having the template entity oil_kwh_today, it did not come up as an acceptable entity in the Energy Integration. I do not know why but if anyone else does then please tell me.
Knowing that an MQTT entity was acceptable for electricity (I’d already set that up) I gave that a go and it worked! To do it I used Node Red to send an MQTT message on a change in sensor.oil_kwh_today although now, I’m guessing, I could have done it through an automation and an MQTT service call. The Node Red flow is here…
[{"id":"b9ab70d0a923d773","type":"server-state-changed","z":"4d3d0c02.dba494","name":"","server":"1ebc9f01.db1151","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.oil_kwh_today","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":230,"y":120,"wires":[["e1e0e89e9e6f2959"]]},{"id":"e1e0e89e9e6f2959","type":"mqtt out","z":"4d3d0c02.dba494","name":"","topic":"tele/boiler/energy","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"91b9efe8.4a598","x":530,"y":120,"wires":[]},{"id":"1ebc9f01.db1151","type":"server","name":"Home Assistant","addon":true},{"id":"91b9efe8.4a598","type":"mqtt-broker","name":"MQTT","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":4,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]
And the YAML code to receive it being also a valid input to the Energy Integration is here…
mqtt:
sensor:
- name: "Oil Energy"
unique_id: sunny_mqtt_0001
state_topic: tele/boiler/energy
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
And here is December’s usage. Where it say Gas read Oil - I hope one day the good people writing Home Assistant recognize that oil is out there as a fuel too.
That’s it. I do do more with the data putting it into Grafana but in truth that just duplicates what is going on in the Energy Integration.