SDI-12 for Arduino > Examples > i_SDI-12_interface.ino

i_SDI-12_interface.ino example

Example I: SDI-12 PC Interface

Code for an Arduino-based USB dongle translates serial comm from PC to SDI-12 (electrical and timing)

  1. Allows user to communicate to SDI-12 devices from a serial terminal emulator (e.g. PuTTY).
  2. Able to spy on an SDI-12 bus for troubleshooting comm between datalogger and sensors.
  3. Can also be used as a hardware middleman for interfacing software to an SDI-12 sensor. For example, implementing an SDI-12 datalogger in Python on a PC. Use verbatim mode with feedback off in this case.

Note: "translation" means timing and electrical interface. It does not ensure SDI-12 compliance of commands sent via it.

PlatformIO Configuration

; PlatformIO Project Configuration File

[platformio]
description = SDI-12 Library Example J:  Creating an SDI-12 Translator Dongle
src_dir = .piolibdeps/Arduino-SDI-12_ID1486/examples/j_SDI-12_interface

[env:mayfly]
monitor_speed = 115200
board = mayfly
platform = atmelavr
framework = arduino
lib_ldf_mode = deep+
lib_ignore = RTCZero
lib_deps =
    SDI-12

The Complete Example

/**
 * @file h_SDI-12_slave_implementation.ino
 * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC)
 *                          and the EnviroDIY Development Team
 *            This example is published under the BSD-3 license.
 * @date 2016
 * @author D. Wasielewski
 *
 * @brief Example I:  SDI-12 PC Interface
 *
 *  Arduino-based USB dongle translates serial comm from PC to SDI-12 (electrical and
 * timing)
 *  1. Allows user to communicate to SDI-12 devices from a serial terminal emulator
 * (e.g. PuTTY).
 *  2. Able to spy on an SDI-12 bus for troubleshooting comm between datalogger and
 * sensors.
 *  3. Can also be used as a hardware middleman for interfacing software to an SDI-12
 * sensor. For example, implementing an SDI-12 datalogger in Python on a PC.  Use
 * verbatim mode with feedback off in this case.
 *
 *  Note: "translation" means timing and electrical interface.  It does not ensure
 * SDI-12 compliance of commands sent via it.
 *
 * D. Wasielewski, 2016
 * Builds upon work started by:
 * https://github.com/jrzondagh/AgriApps-SDI-12-Arduino-Sensor
 * https://github.com/Jorge-Mendes/Agro-Shield/tree/master/SDI-12ArduinoSensor
 *
 * Known issues:
 *  - Backspace adds a "backspace character" into the serialMsgStr (which gets sent
 *    out on the SDI-12 interface) instead of removing the previous char from it
 *  - Suceptible to noise on the SDI-12 data line; consider hardware filtering or
 *    software error-checking
 */

#define HELPTEXT                                                                    \
  "OPTIONS:\r\n"                                                                    \
  "help   : Print this message\r\n"                                                 \
  "mode s : SDI-12 command mode (uppercase and ! automatically corrected) "         \
  "[default]\r\n"                                                                   \
  "mode v : verbatim mode (text will be sent verbatim)\r\n"                         \
  "fb on  : Enable feedback (characters visible while typing) [default]\r\n"        \
  "fb off : Disable feedback (characters not visible while typing; may be desired " \
  "for developers)\r\n"                                                             \
  "(else) : send command to SDI-12 bus"

#include <SDI12.h>

#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */
#define DATA_PIN 7         /*!< The pin of the SDI-12 data bus */
#define POWER_PIN 22       /*!< The sensor power pin (or -1 if not switching power) */
#define SENSOR_ADDRESS 1

/** Define the SDI-12 bus */
SDI12 mySDI12(DATA_PIN);

void setup() {
  Serial.begin(SERIAL_BAUD);
  while (!Serial)
    ;

  // Power the sensors;
  if (POWER_PIN > 0) {
    Serial.println("Powering up sensors...");
    pinMode(POWER_PIN, OUTPUT);
    digitalWrite(POWER_PIN, HIGH);
    delay(200);
  }

  // Initiate serial connection to SDI-12 bus
  mySDI12.begin();
  delay(500);
  mySDI12.forceListen();

  // Print help text (may wish to comment out if used for communicating to software)
  Serial.println(HELPTEXT);
}

void loop() {
  static String  serialMsgStr;
  static boolean serialMsgReady = false;

  static String  sdiMsgStr;
  static boolean sdiMsgReady = false;

  static boolean verbatim = false;
  static boolean feedback = true;


  // -- READ SERIAL (PC COMMS) DATA --
  // If serial data is available, read in a single byte and add it to
  // a String on each iteration
  if (Serial.available()) {
    char inByte1 = Serial.read();
    if (feedback) { Serial.print(inByte1); }
    if (inByte1 == '\r' || inByte1 == '\n') {
      serialMsgReady = true;
    } else {
      serialMsgStr += inByte1;
    }
  }

  // -- READ SDI-12 DATA --
  // If SDI-12 data is available, keep reading until full message consumed
  // (Normally I would prefer to allow the loop() to keep executing while the string
  //  is being read in--as the serial example above--but SDI-12 depends on very precise
  //  timing, so it is probably best to let it hold up loop() until the string is
  //  complete)
  int avail = mySDI12.available();
  if (avail < 0) {
    mySDI12.clearBuffer();
  }  // Buffer is full; clear
  else if (avail > 0) {
    for (int a = 0; a < avail; a++) {
      char inByte2 = mySDI12.read();
      Serial.println(inByte2);
      if (inByte2 == '\n') {
        sdiMsgReady = true;
      } else if (inByte2 == '!') {
        sdiMsgStr += "!";
        sdiMsgReady = true;
      } else {
        sdiMsgStr += String(inByte2);
      }
    }
  }


  // Report completed SDI-12 messages back to serial interface
  if (sdiMsgReady) {
    Serial.println(sdiMsgStr);
    // Reset String for next SDI-12 message
    sdiMsgReady = false;
    sdiMsgStr   = "";
  }

  // Send completed Serial message as SDI-12 command
  if (serialMsgReady) {
    Serial.println();
    // Check if the serial message is a known command to the SDI-12 interface program
    String lowerMsgStr = serialMsgStr;
    lowerMsgStr.toLowerCase();
    if (lowerMsgStr == "mode v") {
      verbatim = true;
      Serial.println("Verbatim mode; exact text will be sent.  Enter \"mode s\" for "
                     "SDI-12 command mode.");
    } else if (lowerMsgStr == "mode s") {
      verbatim = false;
      Serial.println("SDI-12 command mode; uppercase and ! suffix optional.  Enter "
                     "\"mode v\" for verbatim mode.");
    } else if (lowerMsgStr == "help") {
      Serial.println(HELPTEXT);
    } else if (lowerMsgStr == "fb off") {
      feedback = false;
      Serial.println("Feedback off; typed commands will not be visible.  Enter \"fb "
                     "on\" to enable feedback.");
    } else if (lowerMsgStr == "fb on") {
      feedback = true;
      Serial.println("Feedback on; typed commands will be visible.  Enter \"fb off\" "
                     "to disable feedback.");
    }
    // If not a known command to the SDI-12 interface program, send out on SDI-12 data
    // pin
    else {
      if (verbatim) {
        mySDI12.sendCommand(serialMsgStr);
      } else {
        serialMsgStr.toUpperCase();
        mySDI12.sendCommand(serialMsgStr + "!");
      }
    }
    // Reset String for next serial message
    serialMsgReady = false;
    serialMsgStr   = "";
  }
}