SDI-12 Logger through HomeAssistant

My temporary (and not so good) solution to this problem is to restart HASS just before a new measurement is taken. Example to measure every 15 minutes:

- id: Log all sensors
  alias: Log all sensors
  trigger:
    platform: time_pattern
    minutes: '/1'              # Sends measurement request after 1 minute of hass restarted
  action:
  - 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.c_vwc_s1.state }};
                  {{ states.sensor.temperature_s1.state }};
                  {{ states.sensor.electric_conductivity_s1.state }};
                  {{ states.sensor.pore_water_ec_s1.state }};"
  - delay: 00:14:00.0 # Delay 14 min before restart hass
  - service: homeassistant.restart # Restart to power cycle SDI12 sensor

Until I find a solution to power cycle the SDI 12 interface/sensor, that’s it.

I’m thinking about install a Raspbian and running Hass in a docker container as the RPi version os HAOS is caped on linux commands.

Well I’m stumped. I loaded your YAML files into my HA and couldn’t replicate the issue. I’m getting unique measurements. The only difference I’m able to discern is my logbook shows “mm_sensor_1 TURNED ON/OFF triggered by automation…” and your logbook is showing “mm_sensor_1 SWITCHED ON/OFF triggered by automation…”. I can’t seem to find any information about that specific wording but maybe there’s a clue there?

Are you running all the latest updates?

If you had access to another sensor you could see if it’s maybe a faulty sensor. Maybe buy one on Amazon to test and return it afterwards?

Maybe you have the wiring backwards? Not sure if you could even get a reading if that was the case but worth double checking. I’m basically grasping at straws here

The bit you found in the Integrator Guide about the DDI serial timing isn’t applicable since you’re communicating over SDI12.

Was hoping loading up your config files would give me the same results. I love this little data logger and really wanted to find an AHA! in here somewhere but I’m out of ideas. Happy to keep digging at with ya if something new comes to light.

I think there’s something wrong with your EC formula. When I read with ZSC and Aroya app or Zentra app I read 3.182 dS/m. My calculated EC in HA read 3.262 dS/m. Your formula give me 0.0028 dS/m.

Thanks for giving me a rabbit hole to explore! I just spent some time going over my formulas, trying to remember what the heck I had even come up with. I’m 99% sure my formula is correct, here’s how I came up with it.

I started with the equation

σp = (εp * σb) / (εb − εσb=0)

the top half of which we both match. In the bottom half of the equation, your formula takes the VWC reading and subtracts 4.1, the number suggested as a generic offset for εσb=0.

In my formula, I used

ε=(2.887×10−9× RAW3−2.080 ×10−5× RAW2 + 5.276 ×10−2× RAW−43.39)2

for εb and then if I remember right just fiddled around with numbers for εσb=0 until I got readings that matched the Aroya app, since 4.1 was only suggested as a generic offset.

Ultimately, if your formula is giving you numbers that match the output from the bluetooth reader/app, that’s fantastic. Math isn’t my forte so this took me a long time to figure out and I still don’t fully comprehend it. But an example of readings I get from HA are 43.2% VWC, 79.34 Temp (F), and 0.549 EC (super low cause I’m trying to correct some PH issues in my coco). The readings from the Aroya app are 43.03% VWC, 79.34 Temp (F), and 0.54 EC.

All I really ever wanted to accomplish was to get those two measurement methods as close to in sync as possible, which is pretty darn close and works for me. YMMV but if you’ve got working results then run with those.

One thing I noticed is that your calculated VWC formula looks kind of weird. I dug through as many Terros documents as I could find and couldn’t find that equation anywhere. When I run it in my template editor I get 11611599.4%. From your screenshot it looks like you might’ve changed that since you posted your configuration yaml but you may want to look at that.

For reference, the Terros Integrator Guide uses the equation (soilless media)

Θ(m3 / m3)= 6.771×10−10 ×RAW 3 −5.105×10−6 ×RAW 2 +1.302×10−2 ×RAW −10.848

and

Θ(m3 / m3)= 3.879×10−4 ×RAW −0.6956 (for soil)

I love that there’s someone else out there playing around with this

As I’m trying your formula for EC (I’m also on soilless media) the first values looked quite good. However, at a temperature below 20 degress celcius, and at low VWC, negative EC values apppear.

Did you ever experience negative EC values with this formula?

I found doing this is much easier with node-red. 4 simple nodes uploading data to InfluxDB and using grafana. If anyone is interested in importing the JSON I’ll leave it below. You’ll have to enter your correct serial port number and database credentials.

[{“id”:“9208c24cfbc1368a”,“type”:“tab”,“label”:“SDI-12 to InfluxDB”,“disabled”:false,“info”:“”},{“id”:“365c44977bd7e5d8”,“type”:“influxdb out”,“z”:“9208c24cfbc1368a”,“influxdb”:“39f3b86e.898e82”,“name”:“Store in InfluxDB”,“measurement”:“sensor_data”,“precision”:“”,“retentionPolicy”:“”,“database”:“”,“retentionPolicyV18Flux”:“”,“org”:“”,“bucket”:“”,“x”:770,“y”:80,“wires”:},{“id”:“08a9be70a83aa3cd”,“type”:“inject”,“z”:“9208c24cfbc1368a”,“name”:“Trigger Command”,“props”:[{“p”:“payload”}],“repeat”:“10”,“crontab”:“”,“once”:true,“onceDelay”:0.1,“topic”:“”,“payload”:“1R0!”,“payloadType”:“str”,“x”:130,“y”:80,“wires”:[[“2c86100ae8c196f8”]]},{“id”:“c2891a5cb0dad5b6”,“type”:“debug”,“z”:“9208c24cfbc1368a”,“name”:“debug 2”,“active”:true,“tosidebar”:true,“console”:false,“tostatus”:false,“complete”:“false”,“statusVal”:“”,“statusType”:“auto”,“x”:840,“y”:480,“wires”:},{“id”:“2c86100ae8c196f8”,“type”:“serial request”,“z”:“9208c24cfbc1368a”,“name”:“”,“serial”:“ed6024ea.e1b668”,“x”:350,“y”:80,“wires”:[[“52f778e6aa58fb9c”,“c2891a5cb0dad5b6”]]},{“id”:“52f778e6aa58fb9c”,“type”:“function”,“z”:“9208c24cfbc1368a”,“name”:“function 1”,“func”:“var data = msg.payload;\nvar temp = data.split("+");\nvar out = {};\n\n// Extract values and convert to numbers\nout["VWC"] = Number(temp[1]);\nout["Temp"] = Number(temp[2]);\nout["EC"] = Number(temp[3]); // Corrected index for EC\n\n// Calculate dielectric permittivity of the bulk soil (epsilon_b) using VWC\nvar epsilon_b = Math.pow(\n 2.887e-9 * Math.pow(out["VWC"], 3) -\n 2.080e-5 * Math.pow(out["VWC"], 2) +\n 5.276e-2 * out["VWC"] - 43.39, 2\n);\n\n// Set the generic offset for dry soil\nvar epsilon_zero = 4.1;\n\n// Calculate dielectric permittivity of the soil pore water (epsilon_p)\nvar temperature = out["Temp"];\nvar epsilon_p = (80.3 - 0.37 * (temperature - 20));\n\n// Calculate pore water EC\nout["porewaterEC"] = (epsilon_p / (epsilon_b - epsilon_zero));\n\n// Calculate adjustedVWC using the adjusted formula\nout["adjustedVWC"] = (\n (6.771e-10 * Math.pow(out["VWC"], 3) -\n 5.105e-6 * Math.pow(out["VWC"], 2) +\n 1.302e-2 * out["VWC"] - 10.848) * 100\n);\n\n\nmsg.payload = out;\nreturn msg;\n”,“outputs”:1,“timeout”:0,“noerr”:0,“initialize”:“”,“finalize”:“”,“libs”:,“x”:560,“y”:80,“wires”:[[“365c44977bd7e5d8”,“c2891a5cb0dad5b6”]]},{“id”:“39f3b86e.898e82”,“type”:“influxdb”,“hostname”:“111.111.111.111”,“port”:“8086”,“protocol”:“http”,“database”:“enteryourdbhere”,“name”:“InfluxDB”,“usetls”:false,“tls”:“”,“influxdbVersion”:“1.x”,“url”:“”,“rejectUnauthorized”:false},{“id”:“ed6024ea.e1b668”,“type”:“serial-port”,“serialport”:“/dev/ttyUSB0”,“serialbaud”:“9600”,“databits”:“8”,“parity”:“none”,“stopbits”:“1”,“waitfor”:“”,“newline”:“\n”,“bin”:“false”,“out”:“char”,“addchar”:“”,“responsetimeout”:“10000”}]

I have found this thread extremely helpful and I have got everything working with a apogee sq-521 sdi 12 sensor. The only thing that is weird is that when I restart I get a fresh sensor readings, then from there on out I seem to get a stored reading stored reading.
I am sure that I am missing a syntax error or something simple. what confuses me is why do I get a new reading at restart only. If some one could take a look at my Yaml, I am at a loss for where to turn.

command_line:
  - switch:
      name: ppfd
      command_on: echo "1M!" > /dev/serial/by-id/usb-FTDI_FT231X_USB_UART_D30FERP7-if00-port0   # Sends a measurement request to sensor
      command_off: echo "1D0!"> /dev/serial/by-id/usb-FTDI_FT231X_USB_UART_D30FERP7-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/PPFD.csv #Places measurements inside media/PPFD folder
    timestamp: true

  

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

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

  - platform: time_date
    display_options:
      - "date_time"

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

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

      ###################################################################
      #                               PPFD SENSORS
      ###################################################################

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

here is the automation:

- id: Log all sensors
  alias: Log all sensors
  trigger:
    platform: time_pattern
   # seconds: '/59' #Sends measurement requests every 60 seconds
    minutes: '/1'              #Sends measurement requests every 15 minutes
  action: # MAKE MEAUREMENTS FOR SENSOR 1
    - service: switch.turn_on
      entity_id: switch.ppfd
    - delay: 00:00:03.00 #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.ppfd
    - delay: 00:00:01.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.ppfd_address')}};
          {{ states('sensor.ppfd_s1')}};
          {{ states('sensor.serial_sensor')}};"

for reference, I finall got it working. needed echo -n "1M!>. now it works flawlessly