d_simple_logger.ino example

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

1/**
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>
6 * @date August 2013
7 *
8 * @brief Example D: Check all Addresses for Active Sensors and Log Data
9 *
10 * This is a simple demonstration of the SDI-12 library for Arduino.
11 *
12 * It discovers the address of all sensors active on a single bus and takes measurements
13 * from them.
14 *
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.
18 *
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
21 * Arduino.
22 *
23 * To address a sensor, please see Example B: b_address_change.ino
24 */
25
26#include <SDI12.h>
27
28#ifndef SDI12_DATA_PIN
29#define SDI12_DATA_PIN 7
30#endif
31#ifndef SDI12_POWER_PIN
32#define SDI12_POWER_PIN 22
33#endif
34
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') */
42bool printIO = false;
43
44/** Define the SDI-12 bus */
45SDI12 mySDI12(dataPin);
46
47// keeps track of active addresses
48bool isActive[64];
49
50uint8_t numSensors = 0;
51
52/**
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
55 * addresses.
56 */
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'))
61 return i - 'A' + 36;
62 else
63 return i;
64}
65
66/**
67 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
68 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
69 *
70 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
71 */
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))
76 return i + 'A' - 36;
77 else
78 return i;
79}
80
81/**
82 * @brief gets identification information from a sensor, and prints it to the serial
83 * port
84 *
85 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
86 */
87void printInfo(char i) {
88 String command = "";
89 command += (char)i;
90 command += "I!";
91 mySDI12.sendCommand(command, wake_delay);
92 delay(30);
93
94 String sdiResponse = mySDI12.readStringUntil('\n');
95 sdiResponse.trim();
96 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
97 Serial.print(sdiResponse.substring(0, 1)); // address
98 Serial.print(", ");
99 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
100 Serial.print(", ");
101 Serial.print(sdiResponse.substring(3, 11)); // vendor id
102 Serial.print(", ");
103 Serial.print(sdiResponse.substring(11, 17)); // sensor model
104 Serial.print(", ");
105 Serial.print(sdiResponse.substring(17, 20)); // sensor version
106 Serial.print(", ");
107 Serial.print(sdiResponse.substring(20)); // sensor id
108 Serial.print(", ");
109}
110
111bool getResults(char address, int resultsExpected) {
112 uint8_t resultsReceived = 0;
113 uint8_t cmd_number = 0;
114 uint8_t cmd_retries = 0;
115
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();
129 String command = "";
130 command += address;
131 command += "D";
132 command += cmd_number;
133 command += "!";
134 mySDI12.sendCommand(command, wake_delay);
135 delay(30);
136 if (printIO) {
137 Serial.print(">>>");
138 Serial.println(command);
139 }
140
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) {}
145
146 // read the returned address to remove it from the buffer
147 char returnedAddress = mySDI12.read();
148 if (printIO) {
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));
155 }
156 Serial.print("<<<");
157 Serial.write(returnedAddress);
158 Serial.print(", ");
159 Serial.println();
160 }
161
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();
170 if (printIO) {
171 Serial.print("<<<");
172 Serial.println(String(result, 7));
173 } else {
174 Serial.print(String(result, 7));
175 Serial.print(", ");
176 }
177 if (result != -9999) {
178 gotResults = true;
179 cmd_results++;
180 }
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); }
184 mySDI12.read();
185 } else {
186 if (printIO) {
187 Serial.print(F("<<< INVALID CHARACTER IN RESPONSE:"));
188 Serial.write(c);
189 Serial.println();
190 }
191 // Read the character to make sure it's removed from the buffer
192 mySDI12.read();
193 bad_read = true;
194 }
195 delay(10); // 1 character ~ 7.5ms
196 }
197 if (!gotResults) {
198 if (printIO) {
199 Serial.println(F(" No results received, will not continue requests!"));
200 break; // don't do another loop if we got nothing
201 }
202 }
203 if (gotResults && !bad_read) {
204 resultsReceived = resultsReceived + cmd_results;
205 if (printIO) {
206 Serial.print(F(" Total Results Received: "));
207 Serial.print(resultsReceived);
208 Serial.print(F(", Remaining: "));
209 Serial.println(resultsExpected - resultsReceived);
210 }
211 cmd_number++;
212 } else {
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
216 // a valid response
217 cmd_retries++;
218 }
219 mySDI12.clearBuffer();
220 }
221
222 return resultsReceived == resultsExpected;
223}
224
225bool takeMeasurement(char i, String meas_type = "") {
226 mySDI12.clearBuffer();
227 String command = "";
228 command += i;
229 command += "M";
230 command += meas_type;
231 command += "!"; // SDI-12 measurement command format [address]['M'][!]
232 mySDI12.sendCommand(command, wake_delay);
233 delay(30);
234
235 if (printIO) {
236 Serial.print(">>>");
237 Serial.println(command);
238 }
239
240 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
241 // measurements available, 0-9]
242 String sdiResponse = mySDI12.readStringUntil('\n');
243 sdiResponse.trim();
244 if (printIO) {
245 Serial.print("<<<");
246 Serial.println(sdiResponse);
247 }
248
249 String returnedAddress = sdiResponse.substring(0, 1);
250 Serial.print(returnedAddress);
251 Serial.print(", ");
252
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);
256 Serial.print(", ");
257
258 // Set up the number of results to expect
259 int numResults = sdiResponse.substring(4).toInt();
260 Serial.print(numResults);
261 Serial.print(", ");
262
263 if (printIO) { Serial.println(); }
264
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
268 {
269 if (printIO) {
270 Serial.print("<<<");
271 Serial.write(mySDI12.read());
272 Serial.print(" (");
273 }
274 Serial.print(millis() - timerStart);
275 if (printIO) {
276 Serial.println(")");
277 } else {
278 Serial.print(", ");
279 }
280 mySDI12.clearBuffer();
281 break;
282 }
283 }
284 // Wait for anything else and clear it out
285 delay(30);
286 mySDI12.clearBuffer();
287
288 if (numResults > 0) { return getResults(i, numResults); }
289
290 return true;
291}
292
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 = "";
297 myCommand = "";
298 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
299 myCommand += "!";
300
301 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
302 mySDI12.sendCommand(myCommand, wake_delay);
303 if (printIO) {
304 Serial.print(">>>");
305 Serial.println(myCommand);
306 }
307 delay(30);
308 if (mySDI12.available()) { // If we hear anything, assume we have an active sensor
309 if (printIO) {
310 Serial.print("<<<");
311 while (mySDI12.available()) { Serial.write(mySDI12.read()); }
312 } else {
313 mySDI12.clearBuffer();
314 }
315 return true;
316 }
317 }
318 mySDI12.clearBuffer();
319 return false;
320}
321
322void setup() {
323 Serial.begin(serialBaud);
324 while (!Serial && millis() < 10000L);
325
326 Serial.println("Opening SDI-12 bus...");
327 mySDI12.begin();
328 delay(500); // allow things to settle
329
330 Serial.println("Timeout value: ");
331 Serial.println(mySDI12.TIMEOUT);
332
333 // Power the sensors;
334 if (powerPin >= 0) {
335 Serial.println("Powering up sensors, wait...");
336 pinMode(powerPin, OUTPUT);
337 digitalWrite(powerPin, HIGH);
338 delay(5000L);
339 }
340
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");
345
346 for (int8_t i = firstAddress; i <= lastAddress; i++) {
347 char addr = decToChar(i);
348 if (checkActive(addr)) {
349 numSensors++;
350 isActive[i] = 1;
351 printInfo(addr);
352 Serial.println();
353 }
354 }
355 Serial.print("Total number of sensors found: ");
356 Serial.println(numSensors);
357
358 if (numSensors == 0) {
359 Serial.println(
360 "No sensors found, please check connections and restart the Arduino.");
361 while (true) { delay(10); } // do nothing forever
362 }
363
364 Serial.println();
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("-------------------------------------------------------------------"
369 "------------");
370}
371
372void loop() {
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);
378 if (isActive[i]) {
379 if (printIO) {
380 Serial.print(addr);
381 Serial.print(" - millis: ");
382 Serial.print(millis());
383 Serial.println();
384 } else {
385 Serial.print(millis());
386 Serial.print(", ");
387 }
388 takeMeasurement(addr, commands[a]);
389 Serial.println();
390 }
391 }
392 }
393
394 delay(10000L); // wait ten seconds between measurement attempts.
395}