Soil Moisture sensor output as a Percentage

Hello all,

I have recently assembled the components for a Soil Capacitance Sensor, which I have configure using EPSHome. I am pleased to announce that the sensor appears to be working and it provides an output which changes depending on the moisture level.

The issue I am having is the output it provides is in volts and I would like to output this in a more friendly reading as a percentage.

I have done some experiments and have determined that when submerged in water the output is 1.31v and when the sensor is dry it is 2.79v

I believe I can achieve the outcome I am looking for by using a template sensor with a lambda filter however despite my best efforts to research how to achieve this I haven’t been able to locate anything workable.

I would like my lambda to output 100% if the sensor reports a reading of 1.31v or below, and 0% if the sensor reports a reading of 2.79v or above. Then based on these min and max values calculate a percentage value for any reading in-between.

Hardware:

  • DFRobot Analog Capacitive Soil Moisture Sensor
  • DFRobot FireBeetle ESP32 IoT Microcontroller

Code:

sensor:
  - platform: adc
    pin: A0
    name: "Soil Moisture volts"
    id: SMV
    update_interval: 1s
    attenuation: 11db

  - platform: template
    name: "Soil Moisture"
    id: SML
    lambda: ' '

Would appreciate any assistance.

Thanks

The equation you want is:

%moisture = -67.57*V + 188.51

To ensue V is between 1.31 and 2.79 you would do this in jinja:

([ 1.31, V, 2.79]|sort)[1]

Converting these two to C (lambdas) is however another problem that I can’t help with.

2 Likes

This is what I use, it gives 0-100% based on 0.85v is dry, 0.44v is wet, you’ll have to replace those values with yours:

sensor:
  - platform: adc
    pin: A0
    name: "Soil Moisture"
    update_interval: 600s
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 0.85 -> 0.00
        - 0.44 -> 100.00
    - lambda: if (x < 1) return 0; else return (x);
    accuracy_decimals: 0
10 Likes

Calibrate linear is a great way to to this.

2 Likes

Works perfectly, thank you so much guys.

For anyone interested using the same hardware here is the final code:

sensor:
  - platform: adc
    pin: A0
    name: "Soil Moisture"
    update_interval: 10s
    unit_of_measurement: "%"
    attenuation: 11db
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 1.25 -> 100.00
        - 2.8 -> 0.00
    - lambda: if (x < 1) return 0; else return (x);
    accuracy_decimals: 0
6 Likes

You might want to change this:

- lambda: if (x < 1) return 0; else return (x);

To:

- lambda: |
    if (x < 0) return 0; 
    else if (x > 100) return 100;
    else return (x);

This restricts the value to 0 to 100 rather than just anything above 0.

2 Likes

Good point, I’ve never run into the problem of over 100, but it won’t hurt to add. Thanks

I know this is an old thread, but which sensor is this for?

@gctseng as per OP:

Assuming this one

3 Likes

Thank you for the info here. I managed to setup my sensor using ADC for a really really really cheap sensor (1$) + nodemcu.

Did you manage to get it to deep_sleep? I am trying to do so via mqtt but it’s not working…

My code:

# ESPhome Soil Moisture Sensor v 0.1
esphome:
  name: garden-moisture-sensor
  platform: ESP8266
  board: d1_mini

# WIFI Credentials
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

# Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Soilsensor Fallback Hotspot"
    password: !secret wifi_password

captive_portal:

# Enable logging
logger:

mqtt:
  broker: 192.168.x.xxx
  port: xxxx
  username: xxxx
  password: xxxx
  birth_message:
  will_message:
  on_message:
    - topic: /bedford/ota_mode
      payload: 'ON'
      then:
        - deep_sleep.prevent: deep_sleep_1
    - topic: bedford/sleep_mode
      payload: 'ON'
      then:
        - deep_sleep.enter: deep_sleep_1

ota:
  password: "xxxxxxx"

# Deep Sleep Feature. Needs adjustments...
deep_sleep:
  id: deep_sleep_1
  run_duration: 10s
  sleep_duration: 60min

sensor:
  - platform: adc
    pin: A0
    name: "Soil Moisture"
    update_interval: 600s
    unit_of_measurement: "%"
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 0.85 -> 0.00
        - 0.44 -> 100.00
    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);

automation.yaml:

- alias: bedford sleep after mqtt receipt
  trigger:
    - platform: mqtt
      topic: bedford/sensor/bedford_pres/state
  action:
    - data:
        payload: "ON"
        topic: bedford/sleep_mode
      service: mqtt.publish
1 Like

have you connected GPIO16 to the RST pin so that it will wake up again?

This is definitely on my ‘to do’ list as well - did you get deep sleep working?

Hi there,
(I just started with some easy automation with Home Assistant) is there any way to have a calibration feature in Home Assistant for future calibration? These capacitive sensors run out of calibration really quickly… I was wondering about an easy way to calibrate them for the UI.
Thanks!

Thanks for the snippet :slight_smile:

I’m running on a D1 mini pro.
The sensor measures 1.3V wet and 2.5V dry.

It sends values between 143% and 171%
What do I do wrong here?

Cheers! :wink:

Sensor:
  - platform: adc
    pin: A0
    name: "Soil Moisture"
    update_interval: 2s
    unit_of_measurement: "%"
    #attenuation: 11db (only on ESP32)
    filters:
    - median:
        window_size: 7
        send_every: 4
        send_first_at: 1
    - calibrate_linear:
        - 1.3 -> 100
        - 2.5 -> 0
    - lambda: if (x < 1) return 0; else return (x);
    accuracy_decimals: 0

Look at my solution here: Outdoor soil moisture sensor - #23 by hubikj

It looks like your sensor measures much lower voltage than 1.3 V for wet. How did you get your calibration? You can fix the range by using the following lambda, but you should fix your calibration first.

    - lambda: |
        if (x < 0) return 0; 
        else if (x > 100) return 100;
        else return (x);
1 Like

Could relate to the D1 mini onboard voltage divider?

I used this guide to get deep sleep working with an helper entity instead of MQTT. Seemed like a much simpler approach to getting this up and running.

1 Like

You forgot that the Wemos boards have an external converter to put the 0.0-1.0 V range and spread it between 0.0-3.3V of real voltage.

So you need first a multiply filter of 3.3.

Then there’s the question which voltage the sensor is running at: I gpt some of the 2.0 Version. They can either run at 3.3 V or 5 V. Depending on the supplied voltage for VCC on them they’ll likely have a different voltage at the analog output.

thanks a bunch this works for me using an esp32 and a capacitive soil moisture sensor. just had to remove all the stuff below attenuation line to get the raw readings to get my 0 and 100 values. I have a question about the filters - median : window_size: 7 send_every: 4 send_first_at: 1" first what do these values correspond to? is this a timing thing where it skips sending data? I am using my esp 32 in a constant power environment so it should not need to sleep or worry about updating too much.

2 Likes