Oil Tank level monitoring

I am looking for a “simple” solution to integrate oil tank level monitoring. There are a number of solutions i have read about, but none are straight forward. I have come across this solution, which is based on an integration with Tuya, which means it is cloud based. Is it possible to run this as a local solution. Any thoughts and help are welcomed.

https://www.amazon.co.uk/EPTTECH-TLC2206-Ultrasonic-Contactless-Continuous/dp/B0CHFDHGSN

Your first question should be if you can get a wifi signal from your tank to your router? Your second should be can you get a power source to the tank safely?

I created esphome ultrasonic distance mesurement device for this almost two years ago. It works well and didn’t burn the house down yet :slightly_smiling_face:.

Are you able to share more info on this please, or give me a url to follow please?

Thanks

Phil.

I bought this sensor and interface card from AliExpress:
Waterproof Ultrasonic Module JSN-SR04T / AJ-SR04M Water Proof Integrated Distance Measuring Transducer Sensor for Arduino


With some plastic pipes for plumbing i created sensor ‘cork’:

and I replaced old mechanical meter on tank with it:


Created esphome device which measures distance from sensor to oil surface and from that is possible to calculate content of tank.
I also measuring temperature of tank using Dallas DS18B20 one wire sensor, but at this time temperature is not used in volume calculation yet.
I placed esphome device in plastic case on the wall near the tank and also added display to be able read data in place.

Schematics drawn by hand:

Program:

#
# Ultrasound distance measurement
#
# Wiring:
#   + 5V (positive power supply) -> 5V
#   Trig (control side) RX       -> D5
#   Echo (the receiver) TX       -> D6
#   GND (negative)               -> GND
#   Na D4 je status led (na pločici)
#
# https://markus-haack.com/ultrasonic-distance-sensors-esphome/
# https://www.aliexpress.com/item/32665460264.html?spm=a2g0o.order_list.order_list_main.69.608c1802MeRvmI
#
# 2023-12-02:
#   - Initial version
#
# 2023-12-03:
#   - Dodan I2C LCD display 16x2
#   -  Wiring:
#        + 5V (positive power supply) -> 5V
#        scl                          -> D1
#        sda                          -> D2
#        GND (negative)               -> GND
#   - Ispisuje sadržaj input_text.heat_fuel_display_l1 u liniji 1
#   - Ispisuje sadržaj input_text.heat_fuel_display_l2 u liniji 2
#
# 2023-12-04:
#   - Dodano zaboravljeno id(api_was_connected) = 1; u on_boot sekciju
#
# 2023-12-15:
#   - Dodan template sensor "${devname} Raw distance" koji šalje svaku
#     izmjerenu vrijednost distance prije svih filtera
#   - Povećano vrijeme između mjerenja na 10s
#   - Produžen uzv impuls na 80us što doprinosi pouzdanijem odzivu
#
# 2023-12-16:
#   - Isproban prototip na stvarnom tanku i radi
#   - Između D3 i GND dodan pushbutton koji se vidi kao senzor button
#   - te kao akcija: single, double, triple ili hold
#   - Dodan switch LCD Backlight te servisi backlight_on i backlight_off.
#     Svi oni preko scripta display_on i display_off kontroliraju LCD backlight.
#
# 2023-12-17:
#   - Dodana plava led dioda za status
#     S D4 ide 4k7 na anodu, a katoda ide na GND
#   - Dodan Dallas DS18B20 da mjeri temperaturu tanka
#     Spojen s 1,5m telefonske flat žice
#     VCC na 5V, GND na GND, data pin DQ na D7 na ESP8266
#     Pull up otpornik od 4k7 stavljen na stranu senzora
#
# 2023-12-31:
#   - Podešeni parametri ultrasonic senzora i stavljen filter: sliding_window_moving_average
#   - Uređaj je u upotrebi
#
#
# 2024-01-02:
#   - Nakon raznih pokušaja s sliding_window_moving_average vraćen median filter
#
# 2024-01-08:
#   - Za ultrasonic senzor primijenjen cijeli niz filtera koji napokon smiruju priču
#
# 2024-01-09:
#   - Isti set filtera upotrebljen za temperaturu
#

substitutions:
  devname: usdist01

globals:
  - id: api_was_connected
    type: int
    restore_value: no
    initial_value: '0'
  - id: reboot_counter
    type: int
    restore_value: no
    initial_value: '0'
  - id: display_backlight
    type: int
    restore_value: no
    initial_value: '1'
  - id: display_ln2_mode
    type: int
    restore_value: no
    initial_value: '0'

wifi:
  networks:
    - ssid:  "IShome08"
      password: !secret wifi_password
    - ssid:  "IShome04"
      password: !secret wifi_password
    - ssid:  "IShome06"
      password: !secret wifi_password
    - ssid:  "IShome07"
      password: !secret wifi_password
    - ssid:  "IShome05"
      password: !secret wifi_password
  domain: !secret domain

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${devname}
    password: !secret ap_password

captive_portal:

# Do not enable logging
logger:
  #level: DEBUG
  level: INFO
  baud_rate: 0

ota:
  password: !secret ota_password

esphome:
  name: $devname
  platform: ESP8266
  board: d1_mini
  on_boot:
    priority: -110
    then:
      - script.execute: display_off
      - wait_until:
          api.connected:
      - lambda: |-
          id(api_was_connected) = 1;

      - sensor.template.publish:
          id: "${devname}_display_ln2_mode"
          state: !lambda 'return id(display_ln2_mode);'

      - text_sensor.template.publish:
          id: "${devname}_button_action"
          state: !lambda 'return "none";'

      - script.execute: display_on
      - delay: 1000ms
      - homeassistant.service:
          service: script.usdist01_startup_script
          data: {}

api:
  password: !secret api_password
  services:
    - service: backlight_on
      then:
        - script.execute: display_on
    - service: backlight_off
      then:
        - script.execute: display_off
    - service: update_display
      then:
        - component.update: lcd_display
    - service: display_ln2_next
      then:
        - lambda: |-
            id(display_ln2_mode)++;
            if (id(display_ln2_mode) > 1) {
              id(display_ln2_mode) = 0;
            }
        - sensor.template.publish:
            id: "${devname}_display_ln2_mode"
            state: !lambda 'return id(display_ln2_mode);'
            

dallas:
  - pin: D7
    update_interval: 2s

sensor:
  - platform: uptime
    name: "${devname} uptime"
    id: "${devname}_uptime"
  - platform: wifi_signal
    name: "${devname} WiFi Signal"
    id: "${devname}_wifi_signal"
    update_interval: 60s

  - platform: ultrasonic
    trigger_pin: D5
    echo_pin: D6
    name: "${devname} Distance"
    id: "${devname}_distance"
    device_class: distance
    icon: mdi:arrow-expand-down
    unit_of_measurement: m
    accuracy_decimals: 6
    update_interval: 3s
    #pulse_time: 60us
    pulse_time: 80us
    #pulse_time: 100us
    timeout: 1.8m
    on_raw_value:
      then:
        - sensor.template.publish:
            id: "${devname}_raw_distance"
            state: !lambda 'return x;'
    filters:
      - skip_initial: 3
      - filter_out: NaN
      - median:
          window_size: 9
          send_every: 4
          send_first_at: 3
      - quantile:
          window_size: 7
          send_every: 4
          send_first_at: 3
          quantile: .9
      - sliding_window_moving_average:
          window_size: 7
          send_every: 4
          send_first_at: 3
      #- delta: 0.005
      - delta: 0.002

  - platform: template
    name: "${devname} Raw distance"
    id: "${devname}_raw_distance"
    unit_of_measurement: m
    device_class: distance
    accuracy_decimals: 6
    icon: mdi:arrow-expand-down
    
  - platform: template
    name: "${devname} Display ln2 mode"
    id: "${devname}_display_ln2_mode"

  - platform: dallas
    #index: 0
    address: 0x8b000000643cf428
    id: "${devname}_tank_temperature"
    name: "${devname} Tank Temperature"
    resolution: 12
    force_update: false
    filters:
      - skip_initial: 3
      - filter_out: NaN
      - filter_out: 85.0
      - filter_out: -127.0
      - median:
          window_size: 9
          send_every: 4
          send_first_at: 3
      - quantile:
          window_size: 7
          send_every: 4
          send_first_at: 3
          quantile: .9
      - sliding_window_moving_average:
          window_size: 7
          send_every: 4
          send_first_at: 3
      - delta: 0.1


text_sensor:
  - platform: homeassistant
    name: "${devname} Display L1"
    entity_id: input_text.heat_fuel_display_l1
    id: "${devname}_display_l1"

  - platform: homeassistant
    name: "${devname} Display L2"
    entity_id: input_text.heat_fuel_display_l2
    id: "${devname}_display_l2"

  # Expose ESPHome version as sensor.
  - platform: version
    name: $devname ESPHome Version
  # Expose WiFi information as sensors.
  - platform: wifi_info
    ip_address:
      name: $devname IP
    ssid:
      name: $devname SSID
    bssid:
      name: $devname BSSID

  - platform: template
    name: "${devname} button action"
    id: "${devname}_button_action"
    icon: "mdi:gesture-tap"

binary_sensor:

  - platform: status
    name: "Statussensor"
    id: statussensor

  - platform: gpio
    pin:
      number: D3
      mode:
        input: true
        pullup: true
    name: "${devname} button"
    id: "${devname}_button"
    filters:
      - invert:
      - delayed_on: 30ms
      - delayed_off: 30ms
    on_multi_click:
      - timing:
          - ON for 40ms to 400ms
          - OFF for at least 330ms
        then:
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "single";'
          - delay: 10ms
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "none";'
      - timing:
          - ON for 40ms to 400ms
          - OFF for 40ms to 300ms
          - ON for 40ms to 400ms
          - OFF for at least 330ms
        then:
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "double";'
          - delay: 10ms
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "none";'
      - timing:
          - ON for 40ms to 400ms
          - OFF for 40ms to 300ms
          - ON for 40ms to 400ms
          - OFF for 40ms to 300ms
          - ON for 40ms to 400ms
          - OFF for at least 50ms
        then:
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "triple";'
          - delay: 10ms
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "none";'
      - timing:
          - ON for at least 2s
        then:
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "hold";'
          - delay: 10ms
          - text_sensor.template.publish:
              id: "${devname}_button_action"
              state: !lambda 'return "none";'

# I want status LED too (red one)
# It will be on D4 pin of D1 mini
status_led:
  pin:
    number: D4
    inverted: true
    mode:
      input: false
      pullup: false
      output: true

switch:
  - platform: restart
    name: "${devname} Restart"
    id: rst_switch

  - platform: template
    name: "${devname} LCD Backlight"
    id: "${devname}_lcd_backlight"
    lambda: |-
      if (id(display_backlight) > 0) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
      - lambda: |-
          id(display_ln2_mode) = 0;
      - sensor.template.publish:
          id: "${devname}_display_ln2_mode"
          state: !lambda 'return id(display_ln2_mode);'
      - script.execute: display_on
      - component.update: lcd_display
    turn_off_action:
      - script.execute: display_off


script:
  - id: display_on
    mode: restart
    then:
      - lambda: |-
          id(lcd_display).backlight();
          id(display_backlight) = 1;

  - id: display_off
    mode: restart
    then:
      - lambda: |-
          id(lcd_display).no_backlight();
          id(display_backlight) = 0;

interval:
  - interval: 20sec
    then:
      - if:
          condition:
            lambda: 'return id(api_was_connected) > 0;'
          then:
            - if:
                condition:
                  not:
                    api.connected:
                then:
                  - if:
                      condition:
                        lambda: 'return id(display_backlight) < 1;'
                      then:
                        - script.execute: display_on
                        - component.update: lcd_display
                  # Reboot after 20 cycles (20 * 20sec = 5min)
                  - if:
                      condition:
                        lambda: 'return id(reboot_counter) > 20;'
                      then:
                        - switch.turn_on: rst_switch
                        - lambda: |-
                            id(reboot_counter) = 0;
                  - lambda: |-
                      id(reboot_counter) += 1;
                  - if:
                      condition:
                        lambda: 'return id(display_backlight) < 1;'
                      then:
                        - script.execute: display_on
                  #-
                else:
                  - lambda: |-
                      id(reboot_counter) = 0;

i2c:
  sda: D2
  scl: D1
  frequency: 200kHz

display:
  - platform: lcd_pcf8574
    update_interval: 10s
    id: lcd_display
    dimensions: 16x2
    address: 0x3f
    lambda: |-
      if (id(statussensor).state) {
          if (id(display_backlight) > 0) {
          it.printf(0,0,"                ");
          it.printf(0,0,id(${devname}_display_l1).state.c_str());
          it.printf(0,1,"                ");
          it.printf(0,1,id(${devname}_display_l2).state.c_str());
        }
      } else {
        if (id(api_was_connected) > 0){
          it.printf(0,0,"                ");
          it.printf(0,1,"                ");
          it.printf(0,0,"Connection lost!");
          it.printf(0,1,"Reconnecting....");
        } else {
          it.printf(0,0,"                ");
          it.printf(0,1,"                ");
          it.printf(0,0,"Starting system");
          it.printf(0,1,"and connecting!");
        }
      }
# EOF

Hope this helps.
Best regards,

1 Like

Did you find a solution to integrate your TLC2206?

I did the same thing as @IgorZg, except without the display. I just monitor the level in HA. It works great. I bought a threaded PVC cap which fit in one of the tank openings and drilled it out to hold the JSN-SR04T sensor.

That said, if the EPTTech device can be made to work local, without any cloud dependencies, that would be worth a look.

1 Like

Hi IgorZg, Apologies for delay in responding. Many thanks for taking the time to provide that level of detail, I shall take a closer look. And to Doudy, no I haven’t had the time to investigate as yet.
Phil

I have a Zigbee EPTTech unit running on my water tanks at home. I’ve connected it via Zigbee2MQTT as per: https://github.com/Koenkk/zigbee2mqtt/issues/21015

I have used a custom template, like one of the other users has set up, so it shows me 100% when the tank is almost full and shows 0% when it’s really about 10% above the water pickup outlet. Seems to work OK so far, but I have only run it down to 50% so far.

I didn’t like the idea of the Sensor sitting direct in the hot NZ Sun, so I have 3D printed a couple of styles of clip on covers to shade it and divert rain away too.