Calculate volume of horizontal cylinder (oil tank)


I’m monitoring my oil-tank with a Watchman Sonic. I convert the cm of air into cm of oil which gives me an indication of how much gasoil I still have. I would like to convert that into liters.

The formula is quite complex:

I’ve been able to convert that (easily) using ksh (I’m still old-school…) so I can convert the cm of gasoil in liters. Is it possible to do this calculation in HA/Jinja? If not, is it possible to call my script with the cm of gasoil and get the liters?

It’s not possible with the native jinja because cos isn’t a math operation that is available in the HA jinja. You’d be better off creating a python script to handle this. But it may be simpler than that. When you talk about

What is the cm of air and the cm of gas refering to in reference to the equation? Is that the height? If so you could write a python script assuming that you are getting the information from a height sensor:

script: named calc_gas

import math

h = data.get('height') # units should be cm

   h = float(h) # convert h to a float.
except ValueError:
   h = None

# hard coding this, but you could make it a variable like above.
L = 500 # cm
R = 100 # cm

if h != None:
    # In cm
    volume = L * (R**2 * math.acos((R - h) / R) - (R - h) * math.sqrt(2 * R * h - h**2))
    # in liters, dividing by 1000.
    volume /= 1000
    volume = None # volume will be invalid if we don't have a good entity id or height.

if volume != None:
    hass.states.set('sensor.gas_tank', volume, {
        'unit_of_measurement': 'Liters',
        'friendly_name': 'Gas Left',
        'icon': 'mdi:thermometer'})

Then call the script using an automation when your height sensor changes:

  - alias: 'update gas amount'
      - platform: state
        entity_id: sensor.gas_height
      - service: python_script.calc_gas
          height: "{{ trigger.to_state.state }}"

EDIT: had to fix the EQ. FYI you’d need to follow the script installation process:

1 Like

Thanks for the quick response. Never played with Python-scripts so that’s another challenge.

Small type on the formula, a ( is missing: L * ((R**2 …

When I try to run the script from HASS I get following error:

2018-07-11 15:58:22 ERROR (Thread-2) [] Error executing script: __import__ not found
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/components/", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "", line 1, in <module>
ImportError: __import__ not found

Any idea?

Yes. If you read the docs, Python Scripts (i.e., the HA component, not Python in general) are run in a sandboxed environment, and that environment does not let the script (among many other things) import modules. I think you’ll need to run the script from a Shell Command instead, which will make it slightly more complex.

I think math might be imported by default, so it may not be needed. I’d have to look at the components code. I just added it anyways.

EDIT: It’s not imported. Not sure why, its a pretty sound library. Looks like you’ll have to go a different route. Possibly your own component.

Setup: rtl-sdr which sends a json to mosquitto_pub. just created an intermediary script that modifies the json, calculates the volume and adds it to the json. Now need to check if HASS picks that up. So I have both depth and volume sent so I should be set.


1 Like

drop it back in mosquitto, hass has an MQTT component:

To do this without relying on external scripts/mqtt, you could create a piecewise-linear function and retrieve values from that function. The function looks slowly varying enough that even three linear periods would be enough to semi-accurately capture the curve. This would probably get you to a similar level of accuracy considering assumptions made, accuracy of the sensor itself, etc.

Another approach would be to use series approximations of the non-polynomial functions in question:

Having said that, I would personally take this as an opportunity to learn how to get python scripts to interface with HA :slight_smile:

That’s how I receive the messages from mosquitto… That’s been working for quite some time now but I want to know the volume in stead of the level of oil. Test-script is running and now I have to wait for the sensor to send a signal (about 2-3 times an hour).

I know this is quite old now, but did you get a solution, I have the exact same issue and would like to resolve it.

what sensors do you have and what values do they provide?

I have an ultra sonic distance sensor providing the distance from the top of the tank to the surface of the oil. That then converts it into a rudimentary %. Exactly like this…

Oil Tank Level Sensor but without the flashy LED’s.

But I would like a more accurate volume calculation from that.

What’s the entity_id of the sensor in HA and what are the units? What is the radius and length of the tank?


The tanks radius is 55cm and the length is 150cm.

It currently returns the diameter of the tank minus the distance measured, and I just use that as a rudimentary percentage full. So currently the distance measured is 62cm and the gauge is showing as 48% full (110-62).

Thank you in advance.

So this formula cannot be done in HA within templates or a python script because we don’t have access to acos. We can estimate it and that is the best we can do.

So i found some forumla online that gives a rough estimation and that should be good enough:

cos-1(v) = (-0.69813170079773212 * v * v - 0.87266462599716477) * v + 1.5707963267948966

The estimation isn’t close at max or empty so you’ll have a ton of error near those values. Pretty much if you are within 10 cm of the top or bottom of your tank, the value will be wrong this is going to truncate that.

  - platform: template
        name: Estimated Oil
        unit_of_measurement: L
        value_template: >
          {%- set r = 55 %}
          {%- set l = 150 %}
          {%- set h = r*2 - states('sensor.oil_tank_level') | float %}
          {%- macro acos(v) %}
          {{ (-0.69813170079773212 * v * v - 0.87266462599716477) * v + 1.5707963267948966 }}
          {%- endmacro %}
          {%- set total = pi * r ** 2 * l %}
          {%- set volume = l * ( r ** 2 * acos((r-h)/r) | float - (r - h)*sqrt(2 * r * h - h**2)) %}
          {{ total }} {{ volume }}
          {%- if volume > total %}
            {{ total / 1000 }}
          {%- elif volume < 0 %}
          {%- else %}
            {{ volume / 1000 }}
          {%- endif %}
        name: Estimated Oil Level
        unit_of_measurement: %
        value_template: >
          {%- set r = 55 %}
          {%- set l = 150 %}
          {%- set total = (pi * r ** 2 * l) / 1000 %}
          {%- set volume = states('sensor.estimated_oil_volume') | float %}
          {{ volume / total }}

That’s brilliant. TY. I’ll implement this morning and let you know how it goes.