Example D: Check all Addresses for Active Sensors and Log Data.
Example D: Check all Addresses for Active Sensors and Log Data
This is a simple demonstration of the SDI-12 library for Arduino.
It discovers the address of all sensors active on a single bus and takes measurements from them.
Every SDI-12 device is different in the time it takes to take a measurement, and the amount of data it returns. This sketch will not serve every sensor type, but it will likely be helpful in getting you started.
Each sensor should have a unique address already - if not, multiple sensors may respond simultaneously to the same request and the output will not be readable by the Arduino.
To address a sensor, please see Example B: b_address_change.ino
2 * @example{lineno} d_simple_logger.ino
3 * @copyright Stroud Water Research Center
4 * @license This example is published under the BSD-3 license.
5 * @author Kevin M.Smith <SDI12@ethosengineering.org>
8 * @brief Example D: Check all Addresses for Active Sensors and Log Data
10 * This is a simple demonstration of the SDI-12 library for Arduino.
12 * It discovers the address of all sensors active on a single bus and takes measurements
15 * Every SDI-12 device is different in the time it takes to take a
16 * measurement, and the amount of data it returns. This sketch will not serve every
17 * sensor type, but it will likely be helpful in getting you started.
19 * Each sensor should have a unique address already - if not, multiple sensors may
20 * respond simultaneously to the same request and the output will not be readable by the
23 * To address a sensor, please see Example B: b_address_change.ino
29#define SDI12_DATA_PIN 7
31#ifndef SDI12_POWER_PIN
32#define SDI12_POWER_PIN 22
35/* connection information */
36uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */
37int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */
38int8_t powerPin = SDI12_POWER_PIN; /*!< The sensor power pin (or -1) */
39uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */
40int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */
41int8_t lastAddress = 61; /* The last address in the address space to check (61='z') */
44/** Define the SDI-12 bus */
45SDI12 mySDI12(dataPin);
47// keeps track of active addresses
50uint8_t numSensors = 0;
53 * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a
54 * decimal number between 0 and 61 (inclusive) to cover the 62 possible
57byte charToDec(char i) {
58 if ((i >= '0') && (i <= '9')) return i - '0';
59 if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10;
60 if ((i >= 'A') && (i <= 'Z'))
67 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
68 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
70 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
72char decToChar(byte i) {
73 if (i < 10) return i + '0';
74 if ((i >= 10) && (i < 36)) return i + 'a' - 10;
75 if ((i >= 36) && (i <= 62))
82 * @brief gets identification information from a sensor, and prints it to the serial
85 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
87void printInfo(char i) {
91 mySDI12.sendCommand(command, wake_delay);
94 String sdiResponse = mySDI12.readStringUntil('\n');
96 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
97 Serial.print(sdiResponse.substring(0, 1)); // address
99 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
101 Serial.print(sdiResponse.substring(3, 11)); // vendor id
103 Serial.print(sdiResponse.substring(11, 17)); // sensor model
105 Serial.print(sdiResponse.substring(17, 20)); // sensor version
107 Serial.print(sdiResponse.substring(20)); // sensor id
111bool getResults(char address, int resultsExpected) {
112 uint8_t resultsReceived = 0;
113 uint8_t cmd_number = 0;
114 uint8_t cmd_retries = 0;
116 // When requesting data, the sensor sends back up to ~80 characters at a
117 // time to each data request. If it needs to return more results than can
118 // fit in the first data request (D0), we need to make additional requests
119 // (D1-9). Since this is a parent to all sensors, we're going to keep
120 // requesting data until we either get as many results as we expect or no
121 // more data is returned.
122 while (resultsReceived < resultsExpected && cmd_number <= 9 && cmd_retries < 5) {
123 bool gotResults = false;
124 uint8_t cmd_results = 0;
125 // Assemble the command based on how many commands we've already sent,
126 // starting with D0 and ending with D9
127 // SDI-12 command to get data [address][D][dataOption][!]
128 mySDI12.clearBuffer();
132 command += cmd_number;
134 mySDI12.sendCommand(command, wake_delay);
138 Serial.println(command);
141 // Wait for the first few characters to arrive. The response from a data
142 // request should always have more than three characters
143 uint32_t start = millis();
144 while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
146 // read the returned address to remove it from the buffer
147 char returnedAddress = mySDI12.read();
149 if (returnedAddress != address) {
150 Serial.println("Wrong address returned!");
151 Serial.print("Expected ");
152 Serial.print(String(address));
153 Serial.print(" Got ");
154 Serial.println(String(returnedAddress));
157 Serial.write(returnedAddress);
162 bool bad_read = false;
163 // While there is any data left in the buffer
164 while (mySDI12.available() && (millis() - start) < 3000) {
165 char c = mySDI12.peek();
166 // if there's a polarity sign, a number, or a decimal next in the
167 // buffer, start reading it as a float.
168 if (c == '-' || c == '+' || (c >= '0' && c <= '9') || c == '.') {
169 float result = mySDI12.parseFloat();
172 Serial.println(String(result, 7));
174 Serial.print(String(result, 7));
177 if (result != -9999) {
181 // if we get to a new line, we've made it to the end of the response
182 } else if (c == '\r' || c == '\n') {
183 if (printIO) { Serial.write(c); }
187 Serial.print(F("<<< INVALID CHARACTER IN RESPONSE:"));
191 // Read the character to make sure it's removed from the buffer
195 delay(10); // 1 character ~ 7.5ms
199 Serial.println(F(" No results received, will not continue requests!"));
200 break; // don't do another loop if we got nothing
203 if (gotResults && !bad_read) {
204 resultsReceived = resultsReceived + cmd_results;
206 Serial.print(F(" Total Results Received: "));
207 Serial.print(resultsReceived);
208 Serial.print(F(", Remaining: "));
209 Serial.println(resultsExpected - resultsReceived);
213 // if we got a bad charater in the response, add one to the retry
214 // attempts but do not bump up the command number or transfer any
215 // results because we want to retry the same data command to try get
219 mySDI12.clearBuffer();
222 return resultsReceived == resultsExpected;
225bool takeMeasurement(char i, String meas_type = "") {
226 mySDI12.clearBuffer();
230 command += meas_type;
231 command += "!"; // SDI-12 measurement command format [address]['M'][!]
232 mySDI12.sendCommand(command, wake_delay);
237 Serial.println(command);
240 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
241 // measurements available, 0-9]
242 String sdiResponse = mySDI12.readStringUntil('\n');
246 Serial.println(sdiResponse);
249 String returnedAddress = sdiResponse.substring(0, 1);
250 Serial.print(returnedAddress);
253 // find out how long we have to wait (in seconds).
254 uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt();
255 Serial.print(meas_time_s);
258 // Set up the number of results to expect
259 int numResults = sdiResponse.substring(4).toInt();
260 Serial.print(numResults);
263 if (printIO) { Serial.println(); }
265 unsigned long timerStart = millis();
266 while ((millis() - timerStart) < (1000UL * (meas_time_s + 1))) {
267 if (mySDI12.available()) // sensor can interrupt us to let us know it is done early
271 Serial.write(mySDI12.read());
274 Serial.print(millis() - timerStart);
280 mySDI12.clearBuffer();
284 // Wait for anything else and clear it out
286 mySDI12.clearBuffer();
288 if (numResults > 0) { return getResults(i, numResults); }
293// this checks for activity at a particular address
294// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z'
295bool checkActive(char i) {
296 String myCommand = "";
298 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
301 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
302 mySDI12.sendCommand(myCommand, wake_delay);
305 Serial.println(myCommand);
308 if (mySDI12.available()) { // If we hear anything, assume we have an active sensor
311 while (mySDI12.available()) { Serial.write(mySDI12.read()); }
313 mySDI12.clearBuffer();
318 mySDI12.clearBuffer();
323 Serial.begin(serialBaud);
324 while (!Serial && millis() < 10000L);
326 Serial.println("Opening SDI-12 bus...");
328 delay(500); // allow things to settle
330 Serial.println("Timeout value: ");
331 Serial.println(mySDI12.TIMEOUT);
333 // Power the sensors;
335 Serial.println("Powering up sensors, wait...");
336 pinMode(powerPin, OUTPUT);
337 digitalWrite(powerPin, HIGH);
341 // Quickly scan the address space
342 Serial.println("Scanning all addresses, please wait...");
343 Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, "
344 "Sensor Version, Sensor ID");
346 for (int8_t i = firstAddress; i <= lastAddress; i++) {
347 char addr = decToChar(i);
348 if (checkActive(addr)) {
355 Serial.print("Total number of sensors found: ");
356 Serial.println(numSensors);
358 if (numSensors == 0) {
360 "No sensors found, please check connections and restart the Arduino.");
361 while (true) { delay(10); } // do nothing forever
365 Serial.println("Time Elapsed (s), Measurement Type, Sensor Address, Est Measurement "
366 "Time (s), Number Measurements, "
367 "Real Measurement Time (ms), Measurement 1, Measurement 2, ... etc.");
368 Serial.println("-------------------------------------------------------------------"
373 String commands[] = {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
374 for (uint8_t a = 0; a < 1; a++) {
375 // measure one at a time
376 for (int8_t i = firstAddress; i <= lastAddress; i++) {
377 char addr = decToChar(i);
381 Serial.print(" - millis: ");
382 Serial.print(millis());
385 Serial.print(millis());
388 takeMeasurement(addr, commands[a]);
394 delay(10000L); // wait ten seconds between measurement attempts.