project: oil tank level sensor

A heating oil tank level sensor that’s more useful than anything you can buy.

  • Measures oil level in any unit – litres, gallons or days until I need to refill the tank
  • Displays readings on a gauge or time graph (uses Home Assistant gauge & history graph).
  • Receive readings or alerts as an email or phone notification or flashing smart bulb in the house.
  • Uses an ultrasound sensor JSN-SR04T Ultrasonic distance measuring sensor, waterproof (~£6 ebay)
  • Uses an ESP32 wifi chip (~ £6 ebay).
  • With a waterproof housing could work outdoors and on battery power.
  • The project with little modification would monitor the salt level in a water softener or the water in a water barrel. (Ref Dr Zzz – youtube)

Some homes use oil as a fuel for keeping warm in winter. The oil is burned in a central heating boiler in much the same way that many homes in the UK burn methane. Although we call the liquid ‘heating oil’ it’s more runny than the oil you know: it’s more like diesel or white spirits or turps or paraffin or kerosine. Over the course of a winter I might need to order two or three deliveries of oil but till now I’ve no way to predict the day when I’ll need to top it up. And I really don’t ever want to run out of oil.

I asked local firm Shelford Heating to quote for an oil level sensor. Their solution involved drilling and shimming the steel tank to fit an Apollo wireless kit – but the £220 quote seemed excessive for this need. I could also have bought the kit, which sends an oil level to a display unit but I really wanted something that recorded oil levels and could alert me better.

How it connects up

A waterproof ultrasonic sensor, looking like a car bumper sensor, arrives with a long lead and a blue board. Female-female Dupont jumper wires will connect the four pins on the board to an ESP32. Two of the pins connect to ~ 5v power and ground. The other two, labelled TRIG and ECHO go to any suitable GPIO pin on the ESP32. I connected them to GPIO 15 and GPIO 16. Thus far we’ve four wires joining an ESP32 and a sensor board. We now need a lead (from a USB charger) to bring 5v power and GND to the ESP32.

(I used an ESP32-CAM instead of an ESP32 with a USB socket. This meant that I needed to hack a USB cable and make the wiring less elegant. I just happened to have a spare ESP-CAM that had lost its camera).

Underside of the home-made oil cap where the ultrasonic sensor sits, pointing at the oil in the tank.
Download the STL model file here

The ultrasound sensor needs to be fixed so that it points down at the surface of the oil in the tank. I used a tank hole that housed the dipstick used for the last 40 years. A suitable cap, as shown below, was made on a 3D printer. You might instead drill a hole for it but I’ve no advice on this.

The sensor installed on an oil tank – it’s messy around here! I lined the sensor holder with sticky felt to ensure it couldn’t slip or shift easily

Program the ESP32

For this step you need an already working Home Assistant installation on a Raspberry Pi as shown in an earlier project. In Home Assistant go to the Esphome plugin and add a new ‘node’. A wizard asks you a few questions after which you can edit your answers. Add the code below but change the items in bold to suit your board, your wifi, your network and the GPIO pins you want to connect to.


   name: oil
   platform: ESP32
   board: esp-wrover-kit
Enable logging
Enable Home Assistant API
   password: "Your HA API password"

   password: "Any password here"

   ssid: "YOUR WIFI SSID"
   password: "YOUR WIFI PASSWORD"
     # Set the IP of the ESP
     # Set this to the IP address of the router. 
     # The subnet of the network

 port: 80
 platform: ultrasonic
 trigger_pin: GPIO15
 echo_pin: GPIO16
 name: "oil level"
 accuracy_decimals: 0
 update_interval: 10s 

The sensor section at the bottom of the code tells the sensor to say how far the sensor is from an object (in cm). The sensor doesn’t work well at distances ~30 cm but at distances up to a couple of metres it’s surprisingly good. A reflective surface nearby might affect your result.

Here is the final code. I call the sensor “oil used” because the number it returns is the amount of oil I need to buy to fill the tank. Next I really don’t need an oil level reading every 10 seconds so I increased the update interval. Lastly, I needed to somehow calibrate the reading so that it gave me a litre volume. My tank has a perfectly regular shape from top to bottom – so by measuring its dipstick, I found that 100 mm of tank depth contains 105.3 litres. The last two lines (filter and lamda) do this calibration. The two lines can be edited or entirely omitted.

UPDATE (January 2020) On the oil level graph below, you’ll notice that the red trace is noisy. On my tank there’s noise when the tank is full (sensor is too close) and when it’s nearly empty (sound bounces inside the tank). The code below now delivers a ‘sliding-average’ of the oil level readings taken over 20 minutes. What the hec, the readings are free.

 platform: ultrasonic
 trigger_pin: GPIO15
 echo_pin: GPIO16
 name: "oil used"
 accuracy_decimals: 0
 update_interval: 60s 
 unit_of_measurement: "litre" 
  - lambda: return x * 1053;
  - sliding_window_moving_average: 
    window_size: 20 
    send_every: 10

An electrician’s junction box like this fits my ESP32-CAM perfectly. A slightly longer box is needed for an ESP32 Dev kit. (HomeDepot /B&Q / Toolstation)

  • In the ESPhome section of Home Assistant VALIDATE your code and then COMPILE it. The compile operation takes a few minutes as ESPhome assembles the code libraries it needs and creates a firmware ‘.bin’ file.
  • Download this bin file. Flash the bin file using the ESPHome-flasher tool (see here).
  • When the flashing is done, power-up the ESP32 and go to Home Assistant > Integrations > + > ESPHome > add node. You might have to enter the IP address you chose in the code. You’ll then see the oil level entity that has been added to Home Assistant.
  • Finally, go to the Home Assistant overview – the page that displays your stuff – and choose Configure UI in the top corner. You might use a history graph and a gauge to display your oil level.

The code for the items above is shown below. You can edit the bold items to suit.

entity: sensor.oil_level
 max: 1100
 min: 0
 name: heating oil used
   green: 100
   red: 800
   yellow: 600
 theme: default
 type: gauge
entity: sensor.oil_level
 graph: line
 hours_to_show: 96
 name: of 1000 L oil used
 type: sensor
 unit: litres
 entity: sensor.oil_level
 hours_to_show: 24
 refresh_interval: 0
 type: history-graph 

So when do I need to order some more oil?

The first nice consequence is that I now know how much oil I need to order and how much I use. And I can see I would need to order an extra 80 litres as it will take 9 days to arrive. As I write I’m thinking about a way to predict the date when I’ll need to order more oil. For the present I’ve put the data in a spreadsheet and it looks like I’ll order in mid January – unless I start to use oil faster.

Create a notification that alerts when oil needs to be ordered

I order 900 litres of oil and I use 80 litres of oil as I wait for it to be delivered. Therefore I need an alert when the tank has 900 – 80 or 820 litres of oil. Home Assistant offers numerous alerts including send an email, show a message in the browser or pop-up a ‘toast’ message on a phone. You can set a notification to persist so that you can’t easily swipe it away. You can set an urgent notification to wake up a phone to tell you, or one that waits quietly until you next pick up the phone. Here is the automation code – if you’re starting out, the easiest method to get working is the email notification:


 alias: heating oil level alert
 above: '820'
 entity_id: sensor.oil_level
 platform: numeric_state
 after: 07:00
 condition: time
   message: at {{ states ('sensor.time') }}
   title: '{{ states (''sensor.oil_level'') }} L now. 900L oil to be ordered'
 service: notify.notify_html5
   message: at {{ states ('sensor.time') }}
   title: '{{ states (''sensor.oil_level'') }} L now. 900L oil to be ordered'

Automatically record your daily readings in a Google spreadsheet using IFTTT to mediate

Update March 2020

For a while it was OK to manually record the daily oil level in a spreadsheet. I soon saw that I was using 80 – 100 litres of oil a week during December. I therefore set up a connection between Home Assistant and IFTTT so that the daily level could be recorded in a spreadsheet. This has been happening daily all through 2020 and hasn’t broken yet.

The code for the automation routine that takes the reading is below. The procedure to link Home Assistant and IFTTT is here: send sensor data from Home Assistant to a spreadsheet

# that is /config/automations/oil_level.yaml
id: '12399999'
alias: oil_level_spreadsheet
description: daily readings in oil_level.yaml
at: "23:55:00"
platform: time
condition: []
data_template: { "event": "oil_level", "value1": "{{ states('sensor.oil_level')}}", "value2": "{{ states('sensor.temperature_weather')}}", "value3": "{{ states('sensor.power_today')}}" }
service: ifttt.trigger

10 Responses

  1. Ash says:

    Excellent write up. Thank you for taking the time to do this. It has helped with my own, very similar, project.

    One thing I am experiencing is timeouts. I see this in the log:

    [09:08:13][D][ultrasonic.sensor:025]: ‘Oil level in cm’ – Distance measurement timed out!
    [09:08:13][D][sensor:092]: ‘Oil level in cm’: Sending state nan cm with 0 decimals of accuracy

    This results in a ‘non-numeric’ error in Home Assistant. Have you experienced this? Any idea of the cause or a solution?

    • roger says:

      I have seen this error. At times I thought I had two many sensors on board so I slimmed down the code; at others I changed the ESP pins used or the ESP itself. Ultimately it seemed to be related to the strained connection of the sensor plug and the board. It went away so I took care not to touch the thing again.

      Generally errors happen everywhere – eg when trying to upload the firmware – so it’s easy to be discouraged. My tips would be a) experiment a lot b) take notes in a book c) buy two of everything if that’s affordable.

  2. Graham says:

    As an avid user of Home Assistant, this is a fantastic help guide for monitoring an oil tank here in the UK, thank you. To save hacking up a USB cable, would something like this board work for the ESP module ? Can you recommend a housing for the ESP and the sensor board?

    • roger says:

      Cheers Graham. Thank you for reminding that some ESP32’s have a USB socket.

      For a housing on this occasion I used a £4 (electronics) project box from ebay. There are dozens in all sizes, with see-through lids and waterproofness. Since then I’ve bought (from Screwfix or Toolstation) electrician’s junction / connection boxes also called chocboxes. This one fits an ESP32-CAM at £1; others are larger and waterproof

  3. Dan says:

    This is excellent. Do you have the plans for the oil cap that you custom printed? I’d love to order one like yours.

  4. Dan says:

    Regarding the non-numeric issue that Ash inquired about; the fix is to add a filter to your code that ignores non-numeric readings. I added it after the lambda, so the code in your demo would be:
    – lambda: return x * 1053;
    – filter_out: nan

    • roger says:

      Oh thank you so much! I wish I’d known about that (– filter_out: nan) as a way to remove non-zero readings should they occur.

      There’s another need for a filter in my TRAIN TIMES* project. The Home Assistant overview shows (something like “Non-numeric” or “Not available”) after midnight when there are no local times. A filter is probably the answer.

      I’m currently posting projects on using unmodified Sonoff devices; using RFlink and 433MHz sensors. Pop back should these be of value. Thank you again Dan.


  5. sooty says:

    Hi Roger – Sincere thanks for posting this!

    I’ve now got a cost effective remote Oil Monitoring system for my overseas vacation home. In the past I have always been somewhat concerned not being able to see how much oil remains in the tank as we go through the cold winter months…

    • roger says:

      Thank you too for your feedback Lance and best wishes from here. Wow … measuring oil levels remotely offers a big gain. Thinking about the oil tank level as you’ll realise is an unnecessary worry. Even ordering oil is easier when you know you use say, 100 litres in a week. I will soon be posting a very easy project to constantly display the oil level on a TTGO T-Display.

Leave a Reply

Your email address will not be published. Required fields are marked *