k_concurrent_logger.ino example

Example K: Concurrent Measurements.

Example K: Concurrent Measurements

This is very similar to example B - finding all attached sensors and logging data from them. Unlike example B, however, which waits for each sensor to complete a measurement, this asks all sensors to take measurements concurrently and then waits until each is finished to query for results. This can be much faster than waiting for each sensor when you have multiple sensor attached.

1/**
2 * @example{lineno} k_concurrent_logger.ino
3 * @copyright Stroud Water Research Center
4 * @license This example is published under the BSD-3 license.
5 * @author Sara Geleskie Damiano <sdamiano@stroudcenter.org>
6 *
7 * @brief Example K: Concurrent Measurements
8 *
9 * This is very similar to example B - finding all attached sensors and logging data
10 * from them. Unlike example B, however, which waits for each sensor to complete a
11 * measurement, this asks all sensors to take measurements concurrently and then waits
12 * until each is finished to query for results. This can be much faster than waiting for
13 * each sensor when you have multiple sensor attached.
14 */
15
16#include <SDI12.h>
17
18#ifndef SDI12_DATA_PIN
19#define SDI12_DATA_PIN 7
20#endif
21#ifndef SDI12_POWER_PIN
22#define SDI12_POWER_PIN 22
23#endif
24
25/* connection information */
26uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */
27int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */
28int8_t powerPin = SDI12_POWER_PIN; /*!< The sensor power pin (or -1) */
29uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */
30int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */
31int8_t lastAddress = 61; /* The last address in the address space to check (61='z') */
32bool printIO = false;
33
34/** Define the SDI-12 bus */
35SDI12 mySDI12(dataPin);
36
37// keeps track of active addresses
38bool isActive[64];
39
40// keeps track of the wait time for each active addresses
41uint8_t meas_time_ms[64];
42
43// keeps track of the time each sensor was started
44uint32_t millisStarted[64];
45
46// keeps track of the time each sensor will be ready
47uint32_t millisReady[64];
48
49// keeps track of the number of results expected
50uint8_t expectedResults[64];
51
52// keeps track of the number of results returned
53uint8_t returnedResults[64];
54
55uint8_t numSensors = 0;
56
57/**
58 * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a
59 * decimal number between 0 and 61 (inclusive) to cover the 62 possible
60 * addresses.
61 */
62byte charToDec(char i) {
63 if ((i >= '0') && (i <= '9')) return i - '0';
64 if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10;
65 if ((i >= 'A') && (i <= 'Z'))
66 return i - 'A' + 36;
67 else
68 return i;
69}
70
71/**
72 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
73 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
74 *
75 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
76 */
77char decToChar(byte i) {
78 if (i < 10) return i + '0';
79 if ((i >= 10) && (i < 36)) return i + 'a' - 10;
80 if ((i >= 36) && (i <= 62))
81 return i + 'A' - 36;
82 else
83 return i;
84}
85
86/**
87 * @brief gets identification information from a sensor, and prints it to the serial
88 * port
89 *
90 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
91 */
92void printInfo(char i) {
93 String command = "";
94 command += (char)i;
95 command += "I!";
96 mySDI12.sendCommand(command, wake_delay);
97 delay(30);
98
99 String sdiResponse = mySDI12.readStringUntil('\n');
100 sdiResponse.trim();
101 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
102 Serial.print("Address: ");
103 Serial.print(sdiResponse.substring(0, 1)); // address
104 Serial.print(", SDI-12 Version: ");
105 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
106 Serial.print(", Vendor ID: ");
107 Serial.print(sdiResponse.substring(3, 11)); // vendor id
108 Serial.print(", Sensor Model: ");
109 Serial.print(sdiResponse.substring(11, 17)); // sensor model
110 Serial.print(", Sensor Version: ");
111 Serial.print(sdiResponse.substring(17, 20)); // sensor version
112 Serial.print(", Sensor ID: ");
113 Serial.print(sdiResponse.substring(20)); // sensor id
114 Serial.println();
115}
116
117bool getResults(char addr, int resultsExpected) {
118 uint8_t resultsReceived = 0;
119 uint8_t cmd_number = 0;
120 uint8_t cmd_retries = 0;
121
122 // When requesting data, the sensor sends back up to ~80 characters at a
123 // time to each data request. If it needs to return more results than can
124 // fit in the first data request (D0), we need to make additional requests
125 // (D1-9). Since this is a parent to all sensors, we're going to keep
126 // requesting data until we either get as many results as we expect or no
127 // more data is returned.
128 while (resultsReceived < resultsExpected && cmd_number <= 9 && cmd_retries < 5) {
129 bool gotResults = false;
130 uint8_t cmd_results = 0;
131 // Assemble the command based on how many commands we've already sent,
132 // starting with D0 and ending with D9
133 // SDI-12 command to get data [address][D][dataOption][!]
134 mySDI12.clearBuffer();
135 String command = "";
136 command += addr;
137 command += "D";
138 command += cmd_number;
139 command += "!";
140 mySDI12.sendCommand(command, wake_delay);
141 delay(30);
142 if (printIO) {
143 Serial.print(">>>");
144 Serial.println(command);
145 }
146
147 // Wait for the first few characters to arrive. The response from a data
148 // request should always have more than three characters
149 uint32_t start = millis();
150 while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
151
152 // read the returned address to remove it from the buffer
153 char returnedAddress = mySDI12.read();
154 if (printIO) {
155 if (returnedAddress != addr) {
156 Serial.println("Wrong address returned!");
157 Serial.print("Expected ");
158 Serial.print(String(addr));
159 Serial.print(" Got ");
160 Serial.println(String(returnedAddress));
161 }
162 Serial.print("<<<");
163 Serial.write(returnedAddress);
164 Serial.print(", ");
165 Serial.println();
166 }
167
168 bool bad_read = false;
169 // While there is any data left in the buffer
170 while (mySDI12.available() && (millis() - start) < 3000) {
171 char c = mySDI12.peek();
172 // if there's a polarity sign, a number, or a decimal next in the
173 // buffer, start reading it as a float.
174 if (c == '-' || c == '+' || (c >= '0' && c <= '9') || c == '.') {
175 float result = mySDI12.parseFloat();
176 if (printIO) {
177 Serial.print("<<<");
178 Serial.println(String(result, 7));
179 } else {
180 Serial.print(String(result, 7));
181 Serial.print(", ");
182 }
183 if (result != -9999) {
184 gotResults = true;
185 cmd_results++;
186 }
187 // if we get to a new line, we've made it to the end of the response
188 } else if (c == '\r' || c == '\n') {
189 if (printIO) { Serial.write(c); }
190 mySDI12.read();
191 } else {
192 if (printIO) {
193 Serial.print(F("<<< INVALID CHARACTER IN RESPONSE:"));
194 Serial.write(c);
195 Serial.println();
196 }
197 // Read the character to make sure it's removed from the buffer
198 mySDI12.read();
199 bad_read = true;
200 }
201 delay(10); // 1 character ~ 7.5ms
202 }
203 if (!gotResults) {
204 if (printIO) {
205 Serial.println(F(" No results received, will not continue requests!"));
206 break; // don't do another loop if we got nothing
207 }
208 }
209 if (gotResults && !bad_read) {
210 resultsReceived = resultsReceived + cmd_results;
211 if (printIO) {
212 Serial.print(F(" Total Results Received: "));
213 Serial.print(resultsReceived);
214 Serial.print(F(", Remaining: "));
215 Serial.println(resultsExpected - resultsReceived);
216 }
217 cmd_number++;
218 } else {
219 // if we got a bad charater in the response, add one to the retry
220 // attempts but do not bump up the command number or transfer any
221 // results because we want to retry the same data command to try get
222 // a valid response
223 cmd_retries++;
224 }
225 mySDI12.clearBuffer();
226 }
227
228 return resultsReceived == resultsExpected;
229}
230
231int startConcurrentMeasurement(char i, String meas_type = "") {
232 mySDI12.clearBuffer();
233 String command = "";
234 command += i;
235 command += "C";
236 command += meas_type;
237 command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!]
238 mySDI12.sendCommand(command, wake_delay);
239 delay(30);
240
241 if (printIO) {
242 Serial.print(">>>");
243 Serial.println(command);
244 }
245
246 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
247 // measurements available, 0-9]
248 String sdiResponse = mySDI12.readStringUntil('\n');
249 sdiResponse.trim();
250 if (printIO) {
251 Serial.print("<<<");
252 Serial.println(sdiResponse);
253 }
254
255 String addr = sdiResponse.substring(0, 1);
256 Serial.print(addr);
257 Serial.print(", ");
258
259 // find out how long we have to wait (in seconds).
260 uint8_t wait = sdiResponse.substring(1, 4).toInt();
261 Serial.print(wait);
262 Serial.print(", ");
263
264 // Set up the number of results to expect
265 int numResults = sdiResponse.substring(4).toInt();
266 Serial.print(numResults);
267 Serial.print(", ");
268
269 if (printIO) { Serial.println(); }
270
271 uint8_t sensorNum = charToDec(i); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61.
272 meas_time_ms[sensorNum] = wait;
273 millisStarted[sensorNum] = millis();
274 if (wait == 0) {
275 millisReady[sensorNum] = millis();
276 } else {
277 millisReady[sensorNum] = millis() + wait * 1000;
278 }
279 expectedResults[sensorNum] = numResults;
280
281 return numResults;
282}
283
284// this checks for activity at a particular address
285// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z'
286bool checkActive(char i) {
287 String myCommand = "";
288 myCommand = "";
289 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
290 myCommand += "!";
291
292 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
293 mySDI12.sendCommand(myCommand, wake_delay);
294 if (printIO) {
295 Serial.print(">>>");
296 Serial.println(myCommand);
297 }
298 delay(30);
299 if (mySDI12.available()) { // If we hear anything, assume we have an active sensor
300 if (printIO) {
301 Serial.print("<<<");
302 while (mySDI12.available()) { Serial.write(mySDI12.read()); }
303 } else {
304 mySDI12.clearBuffer();
305 }
306 return true;
307 }
308 }
309 mySDI12.clearBuffer();
310 return false;
311}
312
313void setup() {
314 Serial.begin(serialBaud);
315 while (!Serial && millis() < 10000L);
316
317 Serial.println("Opening SDI-12 bus...");
318 mySDI12.begin();
319 delay(500); // allow things to settle
320
321 Serial.println("Timeout value: ");
322 Serial.println(mySDI12.TIMEOUT);
323
324 // Power the sensors;
325 if (powerPin >= 0) {
326 Serial.println("Powering up sensors, wait...");
327 pinMode(powerPin, OUTPUT);
328 digitalWrite(powerPin, HIGH);
329 delay(5000L);
330 }
331
332 // Quickly scan the address space
333 Serial.println("Scanning all addresses, please wait...");
334 Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, "
335 "Sensor Version, Sensor ID");
336
337 for (int8_t i = firstAddress; i <= lastAddress; i++) {
338 char addr = decToChar(i);
339 if (checkActive(addr)) {
340 numSensors++;
341 isActive[i] = 1;
342 printInfo(addr);
343 Serial.println();
344 }
345 }
346 Serial.print("Total number of sensors found: ");
347 Serial.println(numSensors);
348
349 if (numSensors == 0) {
350 Serial.println(
351 "No sensors found, please check connections and restart the Arduino.");
352 while (true) { delay(10); } // do nothing forever
353 }
354
355 Serial.println();
356 Serial.println("Time Elapsed (s), Measurement 1, Measurement 2, ... etc.");
357 Serial.println(
358 "-------------------------------------------------------------------------------");
359}
360
361void loop() {
362 // starting line
363 if (printIO) { Serial.println("-------------"); }
364
365 // re-zero before starting a new measurement round
366 for (int8_t i = firstAddress; i <= lastAddress; i++) {
367 millisReady[i] = 0;
368 expectedResults[i] = 0;
369 returnedResults[i] = 0;
370 }
371
372 // start all sensors measuring concurrently
373 for (int8_t i = firstAddress; i <= lastAddress; i++) {
374 char addr = decToChar(i);
375 if (isActive[i]) {
376 startConcurrentMeasurement(addr);
377 if (printIO) { Serial.println(); }
378 }
379 }
380
381 // Wait for the first sensor to be ready
382 uint32_t first_ready = millis() + 120000;
383 for (int8_t i = firstAddress; i <= lastAddress; i++) {
384 if (millisReady[i] != 0 && millisReady[i] < first_ready) {
385 first_ready = millisReady[i];
386 }
387 }
388 while ((millis() - first_ready) > 50) { delay(1); }
389
390 // get all readings
391 uint8_t numReadingsRecorded = 0;
392 uint8_t round = 1;
393
394 do {
395 if (printIO) {
396 Serial.print("Round ");
397 Serial.print(round);
398 Serial.print(" sensors to read: ");
399 Serial.print(numSensors);
400 Serial.print(" finished sensors: ");
401 Serial.print(numReadingsRecorded);
402 Serial.println();
403 }
404 for (int8_t i = firstAddress; i <= lastAddress; i++) {
405 if (isActive[i]) {
406 char addr = decToChar(i);
407 if (printIO) {
408 Serial.print(addr);
409 Serial.print(" - millis: ");
410 Serial.print(millis());
411 Serial.print(" ready at: ");
412 Serial.print(millisReady[i]);
413 Serial.print(" results expected: ");
414 Serial.print(expectedResults[i]);
415 Serial.print(" already returned: ");
416 Serial.print(returnedResults[i]);
417 Serial.println();
418 }
419 if (isActive[i] && millis() > millisReady[i] && expectedResults[i] > 0 &&
420 (returnedResults[i] < expectedResults[i])) {
421 if (!printIO) {
422 Serial.print(millis() / 1000);
423 Serial.print(", ");
424 Serial.print(addr);
425 Serial.print(", ");
426 }
427 getResults(addr, expectedResults[i]);
428 numReadingsRecorded++;
429 Serial.println();
430 }
431 }
432 }
433 round++;
434 } while (numReadingsRecorded < numSensors);
435
436 delay(10000L); // wait ten seconds between measurement attempts.
437}