SDI-12 Logger through HomeAssistant

For everyone
Who wishes to take sensoring to the next level, but does not want to break the bank for the overpriced loggers that come with them. Here’s my first take on a way to DIY a Home Assistant SDI-12 data logger.

Why use Home Assistant for this? Well because it’s an awesome piece of automation software but also because it easily integrates sleek dashboards, notification services and .CSV logging.

Following needs/wishes:

  • Log up to 61 SDI-12 sensors on the same wire *(hence the big advantage of SDI-12 sensors)
  • Reliability
  • Connectivity for telemetry
  • Simple CSV output of data
  • Phone/email notification on sensor not updating values and/or datalogger reboots
  • Around €100 per data logger setup

Hardware used

How it works
The way Hass.IO is set up, the Pi will automatically boot if power is available (especially after a power cut). Furthermore, a notification will be sent on boot and the Pi will start logging sensor measurements.

Each measurement is directly saved to a .CSV file on the SSD. I use the Samba share addon to retrieve the log file over the network. If a sensor does not report a new value for i.e. volumetric water content for over 30 minutes, a notification will be sent as well.

Setup
For setup of the Pi to boot Hass.os on the SSD, I followed this thread: Installing Home Assistant on a RPi 4b with SSD boot

To get the right USB address that starts with ‘/dev/serial/…’ go to Supervisor within HassIO>System> click the three vertical dots in the bottom of the ‘Host’ tab >Hardware>Look for the line that lists your SDI-12 USB adapter and change it in your configuration.yaml. My address in the example below is ‘/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AC00Z2T4-if00-port0’

To assign a new address to the SDI-12 sensors I hooked the SDI-12 board up to my laptop and launched ‘Terminal’ by Br@y++(Terminal) Mind you to only hook up one sensor at a time when changing their address. Also bear in mind that SDI-12 sensors only be assigned a one-character address from the following characters: 0-9, a-z, A-Z hence 62 as the maximum number of sensors on the SDI-12 bus. **Please note that the Teros11/12 sensors are assigned adress 0 out of the factory and also print out a DDI serial startup string on power-up, resulting in gibberish readout in Home Assistant. The Teros11/12 will omit the DDI Serial startup string (sensor identification) when the SDI-12 address is nonzero. Changing the address to a nonzero address is recommended for this reason. (Thank you @matthewallen00 for pointing this out)

The serial output from a Teros12 sensor with address set to 1 looks like this: ‘1+1812.50+22.9+1’ it shows; the assigned sensor address, volumetric water content, temperature, and electric conductivity. My configuration below basically grabs every string that a sensor publishes over serial, then converts it to single sensor values.

For the sake of length, I’ll show my config for one Teros12 sensor, adding more sensors is currently copy/paste work, if someone has suggestions for templating all these blocks into shorter code that recognize separate sensors, I’d be all ears for input :wink:

The awesome Liudr SDI-12 USB adapter (notice that I wired up four sensors to two inputs since every senor is individually addressed, it is theoretically possible to hook up 62 sensors to one input):

Teros12 unplugged to the DIYed female 3,5mm connector:

configuration.yaml

###################################################################
#                           SDI-12 Measure commands
###################################################################

switch:
  - platform: command_line
    switches:
      mm_sensor_1:
        command_on: 'echo "1M!" > /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AC00Z2T4-if00-port0'    # Sends a measurement request to sensor 
        command_off: 'echo "1D0!" > /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AC00Z2T4-if00-port0'  # Retrieves measurement data from last measurement of sensor 


###################################################################
#       Logging sensor outputs to a .CSV file and make Telegram messaging possible
###################################################################

notify:
  - platform: file
    name: filenotify
    filename: /media/soilsensors.csv #Places measurements inside media/soilsensors folder
    timestamp: true

  - name: aardevosensorbot
    platform: telegram
    chat_id: 11111111

telegram_bot:
  - platform: polling
    api_key: TelegramBotAPIkey
    allowed_chat_ids:
      - 11111111 # My Telegram chat with 

###################################################################
#                           Untemplated SDI-12 string
###################################################################

sensor:
  - platform: serial
    serial_port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AC00Z2T4-if00-port0
    baudrate: 9600

  - platform: time_date
    display_options:
    - "date_time"

###################################################################
#                               SENSOR ADRESS
###################################################################

  - platform: template
    sensors:
      sensor_number_s1:
        friendly_name: Sensor Address 1
        value_template: >
          {% if '1' in states('sensor.serial_sensor') [0:1] -%}                
            {{ states('sensor.serial_sensor').split('+')[0] }}
          {% else -%}
            {{ states.sensor.sensor_number_s1.state }}
          {% endif -%}


###################################################################
#                 VOLUMETRIC WATER CONTENT SENSORS
###################################################################

      vwc_s1:
        friendly_name: Volumetric Water Content Sensor 1
        unit_of_measurement: "θ"
        value_template: >
          {% if '1' in states('sensor.serial_sensor') [0:1] -%}
            {{ states('sensor.serial_sensor').split('+')[1] | default}}
          {% else -%}
            {{ states.sensor.vwc_s1.state }}
          {% endif -%}

      c_vwc_s1:
        friendly_name: Calculated Volumetric Water Content Sensor 1
        unit_of_measurement: "θ"
        value_template: >
          {{ ((6.771e-10) * (states('sensor.vwc_s1')|float ** 3) -
             (5.105e-6) * (states('sensor.vwc_s1')|float ** 2) +
             (1.302e-2) * (states('sensor.vwc_s1')|float) -10.848) }}

      filtered_vwc_s1:
        friendly_name: Filtered Volumetric Water Content Sensor 1
        unit_of_measurement: "θ"
        value_template: >
          {%- if states.sensor.c_vwc_s1.state|float >= 0 and states.sensor.c_vwc_s1.state|float <= 1 -%}
            {{(states.sensor.c_vwc_s1.state) | float | round (3) }}
          {%- else -%}
            nan
          {%- endif -%}

###################################################################
#                               TEMPERATURE SENSORS
###################################################################

      temperature_s1:
        friendly_name: Temperature Sensor 1
        unit_of_measurement: "°C"
        value_template: >
          {% if '1' in states('sensor.serial_sensor') [0:1] -%}                
            {{ states('sensor.serial_sensor').split('+')[2] }}
          {% else -%}
            {{ states.sensor.temperature_s1.state }}
          {% endif -%}

###################################################################
#                        ELECTRIC CONDUCTIVITY SENSORS
###################################################################
      electric_conductivity_s1:
        friendly_name: Electric Conductivity Sensor 1
        unit_of_measurement: "dS/m"
        value_template: >
          {% if '1' in states('sensor.serial_sensor') [0:1] -%}                
            {{ states('sensor.serial_sensor').split('+')[3] }}
          {% else -%}
            {{ states.sensor.electric_conductivity_s1.state }}
          {% endif -%}

automations.yaml

###################################################################
#   Sending measurement commands and retrieve data commands to/from the sensor
###################################################################
- id: Log all sensors 
  alias: Log all sensors
  trigger:
    platform: time_pattern
#    seconds: '/60'             #Sends measurement requests every 60 seconds
    minutes: '/15'              #Sends measurement requests every 15 minutes
  action:
# MAKE MEAUREMENTS FOR SENSOR 1
    - service: switch.turn_on
      entity_id: switch.mm_sensor_1
    - delay: 00:00:00.2  #Delay is in to enable the sensor to initiate the measurements and respond with information about itself and how many measurements it made
    - service: switch.turn_off
      entity_id: switch.mm_sensor_1
    - delay: 00:00:00.8  #Delay is in for the sensor time to respond with the actual sensor data
    - service: notify.filenotify #Log sensors data directly after measurement was retrieved
      data_template: 
        message: "{{ states.sensor.sensor_number_s1.state }};
                  {{ states.sensor.sensor_number_s1.state }};
                  {{ states.sensor.vwc_s1.state }};
                  {{ states.sensor.temperature_s1.state }};
                  {{ states.sensor.electric_conductivity_s1.state }};"


###################################################################
#                 Notifications on DataLogger restart
###################################################################

- alias: Notify on restart
  initial_state: 'on'
  trigger:
    - event: start
      platform: homeassistant
  action:
    - delay: 00:0:30
    - service: notify.aardevosensorbot
      data_template: 
        title: '*Soil Sensor Datalogger*'
        message: 'DataLogger has just restarted!'

###################################################################
#                 Notifications on sensor update failure
###################################################################

- alias: Notify if sensor 1 state unchanged
  trigger:
    platform: time_pattern
#    hours: "/1"
    minutes: '/30'
#    seconds: '/20'
  condition:
    condition: template
    value_template: "{% if as_timestamp(states.sensor.date_time.last_updated) - as_timestamp(states.sensor.vwc_s1.last_updated) > 30*60 %}true{% endif %}"
  action:
  - service: notify.aardevosensorbot
    data:
      title: '*Soil Sensor Datalogger*'
      message: 'Sensor 1 failed to update volumetric water content for over 30 minutes'
6 Likes

Hi

I’m using more or less the same config as you do. I store every 5 minutes all the received values in a Maria DB and draw a little graphic to show in a browser. But I have some doubts about the received values. I have recognized a dependency between the temperature and the moisture and in addition I have some kind of saw tooth on the moisture values. I wonder if this is related to the fact that I’m using Raspi USB to provide the power supply to the SDI-12 board or something different. Did you experienced similar issues?

BR,

Michael

Interesting values indeed. I do not have seen similar events happening with my soil sensors (have hooked up 4 of them right now).

Does your sensor(s) require 12V maybe? What kind of sensor do you use? You use the same Liudr SDI-12 adapter?

I’m using Teros 12 sensor and Liudr board. So I guess 12V are not needed. I think the most annoying thing is the temperature dependency. :frowning:

To my eye, the temperature looks quite ok, how did you calculate moisture in %? Teros12 outputs a VWC, I generally read values from 1900 and up for VWC

I found a formula for calculation of moisture in soilless media in the web:

// for soilless media using calibration equation
// float VWC = ((6.771 * pow(10, -10) * pow(mySensor.sensorTEROS12.calibratedCountsVWC, 3))
// - (5.105 * pow(10, -6) * pow(mySensor.sensorTEROS12.calibratedCountsVWC, 2))
// + (1.302 * pow(10, -2) * mySensor.sensorTEROS12.calibratedCountsVWC))
// - 10.848;

This creates a value between 0.x and 1. When I multiply this value with 100 I get this ~80% which seems to be OK for a completely flooded stone wool slab. But you can’t reach 100%. We are in the calibration phase right now. So maybe we will change the formula if we are not satisfied with reaching only 80%.

3 Likes

Hi Rick and Gargamel,

Sorry in advance for the multiple questions.
Would you please provide more information on how you were able to receive data from the Teros 12 sensors?

Would you please provide more images about how the sensors connect to the Liudr connector?

Do you have any sort of external power source connected to power the sensors?

For me, with a very similar setup, sending write command to the serial at the port id (and dev/ttyUSBx) always returns a blank reading, no matter what parameters are used (regardless of baud rate, parity, etc…) and regardless of whether an external power supply is used for the Liudr connector.

Blank readings are an empty array.

Any thoughts on this?

Sure thing, I’m sure you’ll get it to work, however my logger is in the greenhouse at work (today is a free day in NL) so taking pictures is not really possible atm.

However, your problem sounds like you have the Teros not wired up correctly. I have made use of these www

They clearly label an RST (Ring, Sleeve, Tip):

On the jack plug side: RST = S-+ on the Liudr board side
So connect ring to S, sleeve to - and tip to +. You can check this wiring on the picture in my starting post.

Then I’d recommend testing a connection with the Teros on a laptop first (you can use Terminal).

I did not use external power, for now I have hooked up 4 Teros12 to the board with 0 issues over a few months.

Thank you, that would be awesome. I’m working with 20 of them, and they all came in already as stripped wires, so the connections are ready to use, like this:

I was able to get some readings. I would be great if we can compare data in the long run.

Best,

getting ready to tap in with you guys on the sdi-12 / teros12 use

hey i am trying to do exactly this, where were you able to purchase the teros 12s? meter keeps blowing me off.

also is there a known way to do this with a esp32 and esphome?

Purchased b2b from Meter group AG, the Teros12 were €265 each.

The ESP32 idea would be fun, I guess you could try to compose something using UART within ESPhome?

In terms of practicality, the Teros12’s are wired, you will always end up with a bunch of connections coming together that you probably need to shield from the outside humidity. In my case, that container can fit a Raspberry and SSD easily. How would you like to setup the logger?

1 Like

Hello, im trying to do the same thing but get it to display on graphana. I have a teros 12 and the liudr board and when I connect it i get no readings, it says I need a uniqe ID. I tried hooking it up to terminal and running some of the sdi commands through but no luck. Im also a huge noob when it comes to this. I checked my wiring and everything seems to be wired correctly. I can send over any photos or anything that might help.

update I got it hooked up to my pc and got a reading, yay! But now on my HO it shows 0 on all of the sensor readings.

Thanks for pointing me towards that equation. After reading into the user manual, I figured I
could like to give it a try too and share you the output graphs I get. How/where did you template this equation in HA (it doesn’t look like jinja)? Could you share the config file/script?

With some help I templated the following sensor out of the formula, this gives VWC based on the Soilless Media equation that Meter group provides on the Teros12 manual (page 19).

  - platform: template
    sensors:
      c_vwc_s1:
        friendly_name: Calculated Volumetric Water Content Sensor 1
        unit_of_measurement: "θ"
        value_template: >
          {{ ((6.771 * 10 **-10) * (states('sensor.vwc_s1')|float ** 3) -
             (5.105 * 10 **-6) * (states('sensor.vwc_s1')|float ** 2) +
             (1.302 * 10 **-2) * (states('sensor.vwc_s1')|float) -10.848) | round(3)  }}

If you have a working sensor, make sure to have named the sensor similar to your home assistant configuration. In the example, the sensor is named ‘1’. Your sensor can be named anything from 1-9, a-z or A-Z (60 possibilities).

Also, the standard interval for measurements in HA is set to 15 minutes, it can take up some time before the first measurement is made in HA. You can set the interval up to several seconds in the automations.yaml if you’d like

Hi

I’m using a Python script which I found in the net and adapted it to my needs. The script is started by a cron job and writes all data (not only the EC,WC and temp.) to a Maria DB. In addition I get some weather data from a weather station to the DB. With NVD3 I have created some graphs to be presented in a browser to show the dependencies. Pumps and lights are connected with an Gembird EG-PMS2 USB and can be switched on/off by command line and cron job.
BR,
Michael

1 Like

Thanks! Can I ask what formula you used for converting your raw EC values?

Hi, can you please specify how you get it displayed on grafana? Did you succeed eventually?