ESPHome GPIO Touch Sensor as Capacitive Input for Waterlevel

Hi there,
I found a nice video about a Capacitive Water Level sensor: DIY Capacitive Water Level Sensor using ESP32 Touch Read function - YouTube

I‘m try to do it with ESPHome.

I‘m new with ESP Home.

I don‘t know how i can read the value of the gpio.

How can I create a value list with the different translation values?

Would be nice for a hint how i can solve this.

Regards
Adrian

The docs are here ESP32 Touch Pad — ESPHome

Thanks, I know this page.
I don‘t want a binary sensor but a Value/Level Sensor in percent.

Unfortunately there is no component in ESPHome that will do that, unless you use one of the I2C touch controllers.

You could have a go at writing a custom component.

You could also try defining multiple binary sensors using the same pin but with different threshold values. The compiler doesn’t complain if I do that - but I don’t have the hardware to test.

1 Like

I found a way to do that.

esphome yaml code:

esphome:
  name: waterlevel
  includes:
    - waterlevel.h

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxx"

ota:
  password: "xxxx"

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "waterlevel"
    password: "xx"

captive_portal:

esp32_touch:
  #setup_mode: true



sensor:
- platform: custom
  lambda: |-
    auto WaterLevel = new MyWaterLevel();
    App.register_component(WaterLevel);
    return {WaterLevel};

  sensors:
    name: "Water Level"
    unit_of_measurement: "%"
    accuracy_decimals: 0

waterlevel.h

class MyWaterLevel : public PollingComponent, public Sensor {
	public:
		#define TOUCHPIN 12
		int reading;

		MyWaterLevel() : PollingComponent(1000) { }

		void setup() override {
		}

		void update() override {
			reading = touchRead(TOUCHPIN);
			publish_state(reading);
		}
};

Next Step:

  • Calibration Table:
    I need to create a table where i can set the following correlation:
    60 = 0, 55= 10, 40= 20, 10=100 etc.

Someone an idee how? :see_no_evil:

Calibrate linear or polynomial filter should work to map reading to level percent. This would let calibration be adjustable via yaml config. Otherwise you could write a function to map reading to the percent and pass that to publish_state()

publish_state(-0.0022*reading*reading*reading + 0.2778*reading*reading - 11.889*reading + 193.33);

Below sample uses a lambda call that will get the raw value from the esp32 touch binary sensor and returns it in a template sensor (as an alternative to your custom component).

esphome:
  name: capacitive-level-test

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable Home Assistant API
api:

logger:
  baud_rate: 115200

ota:
  password: !secret esp_home_ota_pw

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
# Sync time with Home Assistant.
time:
  - platform: homeassistant
    id: homeassistant_time

esp32_touch:
  setup_mode: false

binary_sensor:
  - platform: esp32_touch
    #name: "ESP32 Touch Pad GPIO27"
    pin: GPIO27
    threshold: 1000
    id: capacitive_level_hack

sensor:
  - platform: template
    name: "Water Level"
    icon: mdi:car-coolant-level
    update_interval: 1s
    lambda: |-
      return (float) id(capacitive_level_hack).get_value();
    filters:
      - calibrate_linear:
        - 20.0 -> 100.0
        - 2080.0 -> 0.1

Those figures, if accurate, are certainly not linear, so I would use a polynomial filter.

BUT if the vessel is regular in shape, then the volume will be linear and there is no need to use a filter at all, because the volume is just the surface area X the height. A simple lambda calc will work.

Hi nickrout.

I’m not sure if it will be linear or not.
I just ordered some copper tape. it will be here in 24h.

Then i will create a measurement ruler and show how it would work.
I will continue update this thread will all information.

@mulcmu Thanks a lot for your help. You made my day!!! Much easier this way. :upside_down_face:

Cheers, intrigued to know what vessel you are using. Happy to help :slight_smile:

Hi there (@nickrout )
Here is my actual code:

substitutions:
  devicename: waterlevel-humidificator
  upper_devicename: WaterLevel Huidificator
  ip: 10.xx.xx.xx



esphome:
  name: $devicename
  on_boot:
    priority: -200
    then:
      - wait_until:
          condition:
            wifi.connected: 
          timeout: 5s

# workaround for "wifi auth expired" 
#https://community.home-assistant.io/t/esphome-wifi-auth-expired/443790/22
i2c:
  sda: 21
  scl: 22
  frequency: 800kHz         


esp32:
  board: esp32dev
  framework:
    type: arduino

deep_sleep:
  run_duration: 60s
  sleep_duration: 1740s
  id: deep_sleep_1
  wakeup_pin:
    number: 12
    inverted: True
    mode: INPUT_PULLUP
  wakeup_pin_mode: KEEP_AWAKE

# Enable logging
logger:
    level: DEBUG

# Enable Home Assistant API
api:
  encryption:
    key: !secret esphome_api_encryption_key

ota:
  password: !secret esphome_ota

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename}_FH"
    password: !secret wifi_hotspot_password
  manual_ip:
    static_ip: $ip
    gateway: 10.xx.xx.xx
    subnet: 255.255.255.0
    dns1: 10.xx.xx.xx

captive_portal:

web_server:
  port: 80
  include_internal: true

esp32_touch:
  setup_mode: false

switch:
  - platform: restart
    name: "Reboot ESP32"
    icon: "mdi:restart"
      

binary_sensor:
  - platform: status
    name: "${upper_devicename} Status"
  - platform: esp32_touch
    pin: GPIO15
    threshold: 2000
    id: capacitive_level_hack
  - platform: gpio
    name: "${upper_devicename} Defeat"
    id: "defeat"
    pin:
      number: 12
      mode: INPUT_PULLUP
      inverted: True
    on_press:
      then:
        - logger.log: "Prevent deep sleep"
        - deep_sleep.prevent: deep_sleep_1
    on_release:
      then:
        - logger.log: "Allow deep sleep"
        - deep_sleep.allow: deep_sleep_1

sensor:
  - platform: template
    name: "${upper_devicename}"
    icon: mdi:car-coolant-level
    #update_interval: 10s
    update_interval: 1s
    unit_of_measurement: "%"
    lambda: |-
      ESP_LOGD("custom", "INT: %u", id(capacitive_level_hack).get_value());
      int MIN_VALUE = 32;
      int MAX_VALUE = 112;
      int CURR_VALUE = id(capacitive_level_hack).get_value();
      
      if(MIN_VALUE <= CURR_VALUE && CURR_VALUE <= MAX_VALUE)
      {
        //ESP_LOGD("custom", "Return: %u", CURR_VALUE);
        return CURR_VALUE;
      }
      else
      {
        if(CURR_VALUE < MIN_VALUE)
        {
          //ESP_LOGD("custom", "Return: %u", MIN_VALUE);
          return MIN_VALUE;
        }
        else
        { 
          //ESP_LOGD("custom", "Return: %u", MAX_VALUE);
          return MAX_VALUE;
        }
       }
    accuracy_decimals: 0
    filters:
        - calibrate_polynomial:
            degree: 3
            datapoints:
            # Map 0.0 (from sensor) to 0.0 (true value)
            - 112 -> 0.0 #
            - 86 -> 20.0 #
            - 54 -> 40.0 #
            - 45 -> 60.0 #
            - 38 -> 80.0 #
            - 32 -> 100.0 #
        - heartbeat: 5s
        - quantile:
            window_size: 7
            send_every: 4
            send_first_at: 3
            quantile: .9 
  - platform: adc
    name: "${upper_devicename} Battery voltage"
    pin: GPIO34
    id: GPIO34
    accuracy_decimals: 2
    update_interval: 5s
    attenuation: 11dB
    filters:
      - multiply: 2.0  # The voltage divider requires us to multiply by 2
      - quantile:
          window_size: 7
          send_every: 4
          send_first_at: 3
          quantile: .9 

  - platform: template
    name: "${upper_devicename} Battery Percent"
    icon: mdi:car-coolant-level
    update_interval: 15s
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      return  (float) ((id(GPIO34).state));
    filters:
        - calibrate_polynomial:
            degree: 2
            datapoints:
            # Map 0.0 (from sensor) to 0.0 (true value)
            - 4.3 -> 100.0
            - 4.2 -> 100.0
            - 4.1 -> 94.0
            - 4.0 -> 84.0
            - 3.9 -> 74.0
            - 3.8 -> 62.0
            - 3.7 -> 53.0
            - 3.6 -> 39.0
            - 3.5 -> 22.0
            - 3.4 -> 13.0
            - 3.3 -> 3.0
            - 3.2 -> 0.0
            - 3.1 -> 0.0
        - heartbeat: 20s
  
text_sensor:
# Reports the ESPHome Version with compile date
  - platform: version
    name: ${upper_devicename} ESPHome Version

The sensor runs since 10 days on battery (NCR18650B) 3200mAh and has 80% (4.000V) capacity.

Regards,
Adrian

2 Likes

And here how i made the waterlevel hardware sensor:

1 Like