Project: add sensor to heating oil tank + integration in energy dashboard

Hello,
In another thread I was asked about my solution to add the heating oil use to the energy dashboard. Instead of going off topic in that thread, I’ll try to describe here how I did it.

Using a sensor to determine the remaining amount of oil:
I’m using an ESPHome powered ESP8266 in addition with a (waterproof) Ultrasonic distance sensor. I had a few extra fuelcaps, so decided to build the sensor in a fuelcap rather than drill a hole in the tank itself. Be sure to keep all electronics outside of the tank and only have the sensor itself in the fuelcap.

I wanted to have all the code condensed on the ESP, so no additional code in the config or any template sensors are necessary. The code measures the distance from the top of the tank to the level of the oil regularly and does some calculations to convert it to liters. The value which is needed for the energy dashboard, however, is only updated once a day (at 2 o clock in the night). This makes it possible to input any new oil delivery before an erroneus result is uploaded to the energy dashboard.
The yaml creates a sensor in liters, an input number for an oil delivery and a button to send the oil delivery to the esp.
The last send oil level is saved in the eeprom, so even after a powercut no data is lost.


Sadly, the energy dashboard doesn’t allow liters so we have to convert it to kwh (1liter = 10kwh). The conversion is easy and is allready included in the esp code. The sensor is allready configured with all necessary classes, so it should pop up in the setup of the energy dashboard. Having oil in the energy dashboard in kwh is a bit less intuitive than having it in liters, but you get used to it pretty fast. Just remember; deviding by 10 gives you the value in liters.

One thing to be aware of, is to input the amount of oil at any new delivery, as well with first setup at the same day it gets delivered (before 2 o clock of the next day). Otherwise you will get an erroneous entry in the ernergy dashboard!

As i was typing this, i realised there is quite a lot to it. If there are any questions left, or if i forgot to explain certain things, feel free to ask :grinning:.

This setup has been working for me super reliable for almost 2 years now.

If you wish to do something like this youself please keep in mind you do this at your own risk. I take no responsibility for anything going wrong😉.

esphome:
  name: oelstand
  platform: ESP8266
  board: nodemcu
  esp8266_restore_from_flash: true

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "xxxxxxxxx"

wifi:
  ssid: "xxxxxxxxxxx"
  password: "xxxxxxxxxxxx"
  
  manual_ip:
    static_ip: 192.168.178.52
    gateway: 192.168.178.1
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Oelstand Fallback Hotspot"
    password: "xxxxxxxxxx"

captive_portal:

preferences:
  flash_write_interval: 2h

globals:
  - id: previous_oelstand
    type: float
    initial_value: '4000.0'
    restore_value: yes
  - id: previous_oelverbrauch
    type: float
    initial_value: '0.0'
    restore_value: yes

number:
  - platform: template
    id: oellieferung_liter
    name: Öllieferung in Liter
    icon: "mdi:tanker-truck"
    step: 0.1
    min_value: -4000.0
    max_value: 4000.0
    optimistic: true
    initial_value: 0.0
    
button:
  - platform: template
    name: Öllieferung
    on_press:
      then:
        - lambda: |-
            id(previous_oelstand) += id(oellieferung_liter).state;
            id(oellieferung_liter).state = 0.0;
            
  - platform: template
    name: Reset total oelverbrauch
    on_press:
      then:
        - lambda: |-
            id(previous_oelverbrauch) = 0.0;

sensor:
  - platform: ultrasonic
    trigger_pin: D1
    echo_pin: D2
    name: "oillevel measured distance"
    id: oillevel_measured_distance
    update_interval: 10min
    accuracy_decimals: 3
    internal: true
    filters:
      - filter_out: nan
      - max:
          window_size: 7
          send_every: 4
          send_first_at: 3

  - platform: template
    name: "Test_previous_oelstand"
    lambda: |-
      return id(previous_oelstand);
    update_interval: 1s
    unit_of_measurement: "liters"

  - platform: template
    name: "Test_previous_oelverbrauch"
    lambda: |-
      return id(previous_oelverbrauch);
    update_interval: 1s

  - platform: template
    name: "Ölstand"
    id: oelstand
    unit_of_measurement: "liters"
    icon: "mdi:hydraulic-oil-level"
    update_interval: 10min
    accuracy_decimals: 0
    lambda: |-
      return 1.63 - id(oillevel_measured_distance).state;
    filters:
      - calibrate_polynomial:
         degree: 1
         datapoints:
          - 0.01 -> 22.0
          - 0.02 -> 44.0
          - 0.03 -> 66.0
          - 0.04 -> 88.0
          - 0.05 -> 112.0
          - 0.06 -> 134.0
          - 0.07 -> 156.0
          - 0.08 -> 178.0
          - 0.09 -> 200.0
          - 0.10 -> 226.0
          - 0.11 -> 250.0
          - 0.12 -> 276.0
          - 0.13 -> 300.0
          - 0.14 -> 326.0
          - 0.15 -> 350.0
          - 0.16 -> 376.0
          - 0.17 -> 400.0
          - 0.18 -> 426.0
          - 0.19 -> 450.0
          - 0.20 -> 476.0
          - 0.21 -> 500.0
          - 0.22 -> 526.0
          - 0.23 -> 550.0
          - 0.24 -> 576.0
          - 0.25 -> 600.0
          - 0.26 -> 628.0
          - 0.27 -> 658.0
          - 0.28 -> 686.0
          - 0.29 -> 714.0
          - 0.30 -> 742.0
          - 0.31 -> 772.0
          - 0.32 -> 800.0
          - 0.33 -> 828.0
          - 0.34 -> 858.0
          - 0.35 -> 886.0
          - 0.36 -> 914.0
          - 0.37 -> 942.0
          - 0.38 -> 972.0
          - 0.39 -> 1000.0
          - 0.40 -> 1022.0
          - 0.41 -> 1044.0
          - 0.42 -> 1066.0
          - 0.43 -> 1088.0
          - 0.44 -> 1112.0
          - 0.45 -> 1134.0
          - 0.46 -> 1156.0
          - 0.47 -> 1178.0
          - 0.48 -> 1200.0
          - 0.49 -> 1222.0
          - 0.50 -> 1244.0
          - 0.51 -> 1266.0
          - 0.52 -> 1288.0
          - 0.53 -> 1312.0
          - 0.54 -> 1334.0
          - 0.55 -> 1356.0
          - 0.56 -> 1378.0
          - 0.57 -> 1400.0
          - 0.58 -> 1420.0
          - 0.59 -> 1440.0
          - 0.60 -> 1460.0
          - 0.61 -> 1480.0
          - 0.62 -> 1500.0
          - 0.63 -> 1520.0
          - 0.64 -> 1540.0
          - 0.65 -> 1560.0
          - 0.66 -> 1580.0
          - 0.67 -> 1600.0
          - 0.68 -> 1628.0
          - 0.69 -> 1658.0
          - 0.70 -> 1686.0
          - 0.71 -> 1714.0
          - 0.72 -> 1742.0
          - 0.73 -> 1772.0
          - 0.74 -> 1800.0
          - 0.75 -> 1826.0
          - 0.76 -> 1850.0
          - 0.77 -> 1876.0
          - 0.78 -> 1900.0
          - 0.79 -> 1926.0
          - 0.80 -> 1950.0
          - 0.81 -> 1976.0
          - 0.82 -> 2000.0
          - 0.83 -> 2026.0
          - 0.84 -> 2050.0
          - 0.85 -> 2076.0
          - 0.86 -> 2100.0
          - 0.87 -> 2126.0
          - 0.88 -> 2150.0
          - 0.89 -> 2176.0
          - 0.90 -> 2200.0
          - 0.91 -> 2222.0
          - 0.92 -> 2244.0
          - 0.93 -> 2266.0
          - 0.94 -> 2288.0
          - 0.95 -> 2312.0
          - 0.96 -> 2334.0
          - 0.97 -> 2356.0
          - 0.98 -> 2378.0
          - 0.99 -> 2400.0
          - 1.00 -> 2422.0
          - 1.01 -> 2444.0
          - 1.02 -> 2466.0
          - 1.03 -> 2488.0
          - 1.04 -> 2512.0
          - 1.05 -> 2534.0
          - 1.06 -> 2556.0
          - 1.07 -> 2578.0
          - 1.08 -> 2600.0
          - 1.09 -> 2626.0
          - 1.10 -> 2650.0
          - 1.11 -> 2676.0
          - 1.12 -> 2700.0
          - 1.13 -> 2726.0
          - 1.14 -> 2750.0
          - 1.15 -> 2776.0
          - 1.16 -> 2800.0
          - 1.17 -> 2828.0
          - 1.18 -> 2858.0
          - 1.19 -> 2886.0
          - 1.20 -> 2914.0
          - 1.21 -> 2942.0
          - 1.22 -> 2972.0
          - 1.23 -> 3000.0
          - 1.24 -> 3028.0
          - 1.25 -> 3058.0
          - 1.26 -> 3086.0
          - 1.27 -> 3114.0
          - 1.28 -> 3142.0
          - 1.29 -> 3172.0
          - 1.30 -> 3200.0
          - 1.31 -> 3226.0
          - 1.32 -> 3250.0
          - 1.33 -> 3276.0
          - 1.34 -> 3300.0
          - 1.35 -> 3326.0
          - 1.36 -> 3350.0
          - 1.37 -> 3376.0
          - 1.38 -> 3400.0
          - 1.39 -> 3422.0
          - 1.40 -> 3444.0
          - 1.41 -> 3466.0
          - 1.42 -> 3488.0
          - 1.43 -> 3512.0
          - 1.44 -> 3534.0
          - 1.45 -> 3556.0
          - 1.46 -> 3578.0
          - 1.47 -> 3600.0
          - 1.48 -> 3626.0
          - 1.49 -> 3650.0
          - 1.50 -> 3676.0
          - 1.51 -> 3700.0
          - 1.52 -> 3726.0
          - 1.53 -> 3750.0
          - 1.54 -> 3776.0
          - 1.55 -> 3800.0
          - 1.56 -> 3826.0
          - 1.57 -> 3850.0
          - 1.58 -> 3876.0
          - 1.59 -> 3900.0
          - 1.60 -> 3926.0
          - 1.61 -> 3950.0
          - 1.62 -> 3976.0
          - 1.63 -> 4000.0

  - platform: template
    id: oelverbrauch
    name: Ölverbrauch
    unit_of_measurement: "kWh"
    accuracy_decimals: 3
    device_class: energy
    state_class: total_increasing

time:
  - platform: homeassistant
    on_time:
      - seconds: 0
        minutes: 0
        hours: 2
        then:
          - lambda: |-
              if id(oelstand).state < id(previous_oelstand){
                float calc = ((id(previous_oelstand) - id(oelstand).state)*10.0) + id(previous_oelverbrauch);
                id(oelverbrauch).publish_state(calc);
                id(previous_oelstand) = id(oelstand).state;
                id(previous_oelverbrauch) = id(oelverbrauch).state;
              }
              else{
                
              }


2 Likes

Some remarks to questions asked here:

Seems like you got very far allready! Some more pointers:

  • for the entity to show up in the energy dashboard it should be set up in a certain way and the state has to be total increasing (so the value is always getting bigger and bigger)

Example:

  - platform: template
    id: oelverbrauch
    name: Ölverbrauch
    unit_of_measurement: "kWh"
    accuracy_decimals: 3
    device_class: energy
    state_class: total_increasing
  • since the energy dashboard doesn’t support liters (yet), the above sensor must be conversed to kwh. Luckily the conversion is very simple, so calculating back to liters in your head isn’t that bad. 1L of oil equals approx. 9.5 to 10.5kwh (depending on a lot of other factors). So to make the calculation easy on yourself, I would suggest sticking to 1L equals 10kwh.

  • About your question why you can’t have it display in liters, that’s just because it isn’t supported by the energy dashboard. This doesn’t mean you can’t have a sensor displaying your daily oil consumption or your remaining oil in the tank in liters though. Have a look at my screenshot in my first post, it shows how much oil is remaining in my fueltank and it shows it in liters. Just keep in mind, this is a different template sensor than the one used for the energy dashboard!

Dear assembly,

Thank you for answering my questions.
I got already further with influx and grafana.
However, I believe my brain needs a rest (have been fiddling around this morning again already for 4 hours). I believe all information I read needs to settle a bit.
Can you maybe show me a code example for your other sensor where you do the liters (edit: ah seems to be in the code at the top if I am not mistaken) and which visualization are you using - grafana and influx as well?

Is it still true that HA only keeps only 7 days of history?
Since I read somewhere (something with furnace burner) where someone made sensors for daily, weekly, monthly), so from all I learned so far I would expect him to use influx as well to keep long term data stored.

I also thought if I just do not display kwH in the visualization and leave the values * 2.4 (oil consumption per hour) I would see the bars just as if they were liters. Or do you think that is a bad idea?

I go so much side-tracked with all the other things I need to put in place that I am a bit off the goal I wanted to achieve. Still struggling with getting values from 18:00 to 18:00 daily. In influx I managed to get 0:00 to 0:00 which is already something (maybe I just go with that).

Thank you again for your help so far and have a nice week-end. Regards, Annette

edit: maybe slowly I am getting closer to the goal:

This is impressive! Bravo!

Am using the following SR04T Ultrasonic sensor with Tasmota.

Am pretty happy with it.
I don’t have problems and with the following template I have the litres of the tank with a deviation of ± 16 Lt.

  - platform: template
    sensors:
      tank_volume:
        friendly_name: "Tank Volume"
        unit_of_measurement: "Litres"
        value_template: "{% set x = states('sensor.oil_in_tank_height') | float(0) %} {{(-5.876302 + 6.803452*x + 0.1275233*x**2 - 0.0009407021*x**3 + 0.000004060763*x**4 - 3.565947e-8*x**5) | round(0)}}"

I was thinking of switching to ESPhome because I liked the filter calibrate _polynomial.
I had the manual of the tank but I didn’t know how to use it with Tasmota.

Would you please be kind and help me with the ESP code ???

Awaiting the pleasure of your reply

Since you are allready using the same hardware as i am, you should be able to just copy my yaml.

Just change out the wifi info and your tank parameters und maybe some of the calculations referencing tank dimensions.

it is very hard for me as I don’t understand the math calculations and am not very familiar with esphome

At what hight in your tank is your sensor mounted?

Hi, great post, thanks for all the info, I’ve been following a few of these threads, finally bit the bullet and got the parts…

I’m curious about your cap setup, I have an old tank, the cap just sits on top, no threads, and it will be removed by the guys filling it, I’m concerned about this.

Is the cap you’re using the cap for the fill hole? Have you asked the guys to be careful when removing to fill?

Is the part you sit the sensor in 3d printed? I don’t have access to a 3d printer and I’m trying to come up with a solution to attach the sensor.

My preference would be to not have the sensor in the cap at all, and drill into the tank, and somehow attach the sensor that way, but I haven’t really been able to come up with a good approach for that yet.

Would love to hear your take?

Thanks :disguised_face:

Excellent project :slight_smile:

Can you share any information on the specific sensor you used and your wiring to the ESP

Would it be possible to ‘upgrade’ your code so that if it sees a sudden large increase, it marks it as a fuel delivery automaticaly? Then there is no need to take any manual action.

The sensor appears to be: JSN-SR04T

I’ve made progress on this project,

I’ve drilled the hole, and have the sensor returning data to home assistant, I’ve modified the code data points (and some names)

I’ve a few things I need to understand to set this up properly.

Any support would be appreciated:

Tank is 1200 liters capacity
Distance from the bottom to the fill line is about 100cm
Currently the distance returned is: 0.69235
The tank is filled up to 34cm ~ ( I estimate this to be about 250 liters ~)
The distance returned at the fill line is: 0.21000

I’m trying to figure out what the distance returned will be at empty based on the information I have, so I can begin to plot data points.

I need to translate the 34cm it’s currently filled up to, into a values I can add onto: 0.69235 to get the empty value, does anyone know the formula for that, what is the measurement that is returned?

I’ve disabled some of the persistence settings while testing(i think they’re persistence settings, I’ve also added brackets to the if statement, OP’s version was not compiling for me. )

I’m also confused by the data points in OP’s code, with

  • 0.01 → 22.0 plotting empty
  • 1.63 → 4000.0 plotting full

But the date returned in my setup:

  • 0.69 → current distance
  • 0.21 → full distance

So I’m expecting to plot the other way around to OP, what am I missing?

esphome:
  name: oil-sensor
  friendly_name: oil-sensor

esp8266:
  board: esp01_1m

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:


ota:
  - platform: esphome


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Oil-Sensor Fallback Hotspot"


captive_portal:

preferences:
  flash_write_interval: 2h

globals:
  - id: previous_oil_level
    type: float
    initial_value: '250.0'
    restore_value: no
  - id: previous_oil_consumption
    type: float
    initial_value: '0.0'
    restore_value: no

number:
  - platform: template
    id: oil_supply_liter
    name: Oil Supply in Liter
    icon: "mdi:tanker-truck"
    step: 0.1
    min_value: -1200.0
    max_value: 1200.0
    optimistic: true
    initial_value: 0.0

button:
  - platform: template
    name: Oil Supply
    on_press:
      then:
        - lambda: |-
            id(previous_oil_level) += id(oil_supply_liter).state;
            id(oil_supply_liter).state = 0.0;

  - platform: template
    name: Reset total consumption
    on_press:
      then:
        - lambda: |-
            id(previous_oil_consumption) = 0.0;

sensor:
  - platform: ultrasonic
    trigger_pin: GPIO5
    echo_pin: GPIO4
    name: "oil level measured distance"
    id: oil_level_measured_distance
    unit_of_measurement: 'l'
    update_interval: 5000ms
    accuracy_decimals: 3
    pulse_time: 200ms
    timeout: 4.0m
    # internal: true
    filters:
      - filter_out: nan
      - max:
          window_size: 7
          send_every: 4
          send_first_at: 3

  - platform: template
    name: "Test_previous_oil_level"
    lambda: |-
      return id(previous_oil_level);
    update_interval: 1s
    unit_of_measurement: "liters"

  - platform: template
    name: "Test_previous_oil_consumption"
    lambda: |-
      return id(previous_oil_consumption);
    update_interval: 1s

  - platform: template
    name: "Test_oil_level_measured_distance"
    lambda: |-
      return id(oil_level_measured_distance).state;
    update_interval: 1s

  - platform: template
    name: "Oil level"
    id: oil_level
    unit_of_measurement: "liters"
    icon: "mdi:hydraulic-oil-level"
    update_interval: 5000ms
    accuracy_decimals: 0
    lambda: |-
      return 0.90 - id(oil_level_measured_distance).state;
    filters:
      - calibrate_polynomial:
          degree: 1
          datapoints:
            - 0.90 -> 1200.0
            - 0.89 -> 1100.0
            - 0.88 -> 1000.0
            - 0.87 -> 900.0
            - 0.86 -> 850.0
            - 0.85 -> 800.0
            - 0.84 -> 750.0
            - 0.83 -> 700.0
            - 0.82 -> 650.0
            - 0.81 -> 600.0
            - 0.80 -> 550.0
            - 0.79 -> 500.0
            - 0.78 -> 475.0
            - 0.77 -> 450.0
            - 0.76 -> 425.0
            - 0.75 -> 400.0
            - 0.74 -> 375.0
            - 0.73 -> 350.0
            - 0.72 -> 325.0
            - 0.71 -> 300.0
            - 0.70 -> 275.0
            - 0.69 -> 250.0
            - 0.68 -> 870.0
            - 0.67 -> 855.0
            - 0.66 -> 840.0
            - 0.65 -> 825.0
            - 0.64 -> 810.0
            - 0.63 -> 795.0
            - 0.62 -> 780.0
            - 0.61 -> 765.0
            - 0.60 -> 750.0
            - 0.59 -> 735.0
            - 0.58 -> 720.0
            - 0.57 -> 705.0
            - 0.56 -> 690.0
            - 0.55 -> 675.0
            - 0.54 -> 660.0
            - 0.53 -> 645.0
            - 0.52 -> 630.0
            - 0.51 -> 615.0
            - 0.50 -> 600.0
            - 0.49 -> 585.0
            - 0.48 -> 570.0
            - 0.47 -> 555.0
            - 0.46 -> 540.0
            - 0.45 -> 525.0
            - 0.44 -> 510.0
            - 0.43 -> 495.0
            - 0.42 -> 480.0
            - 0.41 -> 465.0
            - 0.40 -> 450.0
            - 0.39 -> 435.0
            - 0.38 -> 420.0
            - 0.37 -> 405.0
            - 0.36 -> 390.0
            - 0.35 -> 375.0
            - 0.34 -> 360.0
            - 0.33 -> 345.0
            - 0.32 -> 330.0
            - 0.31 -> 315.0
            - 0.30 -> 300.0
            - 0.29 -> 285.0
            - 0.28 -> 270.0
            - 0.27 -> 255.0
            - 0.26 -> 240.0
            - 0.25 -> 225.0
            - 0.24 -> 210.0
            - 0.23 -> 195.0
            - 0.22 -> 180.0
            - 0.21 -> 165.0


  - platform: template
    id: consumption
    name: Consumption
    unit_of_measurement: "kWh"
    accuracy_decimals: 3
    device_class: energy
    state_class: total_increasing

time:
  - platform: homeassistant
    on_time:
      - seconds: 10
        minutes: 0
        hours: 0
        then:
          - lambda: |-
              if(id(oil_level).state < id(previous_oil_level)){
                float calc = ((id(previous_oil_level) - id(oil_level).state)*10.0) + id(previous_oil_consumption);
                id(consumption).publish_state(calc);
                id(previous_oil_level) = id(oil_level).state;
                id(previous_oil_consumption) = id(consumption).state;
              }
              else{
              
              }