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.
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>
7 * @brief Example K: Concurrent Measurements
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.
19#define SDI12_DATA_PIN 7
21#ifndef SDI12_POWER_PIN
22#define SDI12_POWER_PIN 22
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') */
34/** Define the SDI-12 bus */
35SDI12 mySDI12(dataPin);
37// keeps track of active addresses
40// keeps track of the wait time for each active addresses
41uint8_t meas_time_ms[64];
43// keeps track of the time each sensor was started
44uint32_t millisStarted[64];
46// keeps track of the time each sensor will be ready
47uint32_t millisReady[64];
49// keeps track of the number of results expected
50uint8_t expectedResults[64];
52// keeps track of the number of results returned
53uint8_t returnedResults[64];
55uint8_t numSensors = 0;
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
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'))
72 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
73 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
75 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
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))
87 * @brief gets identification information from a sensor, and prints it to the serial
90 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
92void printInfo(char i) {
96 mySDI12.sendCommand(command, wake_delay);
99 String sdiResponse = mySDI12.readStringUntil('\n');
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
117bool getResults(char addr, int resultsExpected) {
118 uint8_t resultsReceived = 0;
119 uint8_t cmd_number = 0;
120 uint8_t cmd_retries = 0;
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();
138 command += cmd_number;
140 mySDI12.sendCommand(command, wake_delay);
144 Serial.println(command);
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) {}
152 // read the returned address to remove it from the buffer
153 char returnedAddress = mySDI12.read();
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));
163 Serial.write(returnedAddress);
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();
178 Serial.println(String(result, 7));
180 Serial.print(String(result, 7));
183 if (result != -9999) {
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); }
193 Serial.print(F("<<< INVALID CHARACTER IN RESPONSE:"));
197 // Read the character to make sure it's removed from the buffer
201 delay(10); // 1 character ~ 7.5ms
205 Serial.println(F(" No results received, will not continue requests!"));
206 break; // don't do another loop if we got nothing
209 if (gotResults && !bad_read) {
210 resultsReceived = resultsReceived + cmd_results;
212 Serial.print(F(" Total Results Received: "));
213 Serial.print(resultsReceived);
214 Serial.print(F(", Remaining: "));
215 Serial.println(resultsExpected - resultsReceived);
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
225 mySDI12.clearBuffer();
228 return resultsReceived == resultsExpected;
231int startConcurrentMeasurement(char i, String meas_type = "") {
232 mySDI12.clearBuffer();
236 command += meas_type;
237 command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!]
238 mySDI12.sendCommand(command, wake_delay);
243 Serial.println(command);
246 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
247 // measurements available, 0-9]
248 String sdiResponse = mySDI12.readStringUntil('\n');
252 Serial.println(sdiResponse);
255 String addr = sdiResponse.substring(0, 1);
259 // find out how long we have to wait (in seconds).
260 uint8_t wait = sdiResponse.substring(1, 4).toInt();
264 // Set up the number of results to expect
265 int numResults = sdiResponse.substring(4).toInt();
266 Serial.print(numResults);
269 if (printIO) { Serial.println(); }
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();
275 millisReady[sensorNum] = millis();
277 millisReady[sensorNum] = millis() + wait * 1000;
279 expectedResults[sensorNum] = numResults;
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 = "";
289 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
292 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
293 mySDI12.sendCommand(myCommand, wake_delay);
296 Serial.println(myCommand);
299 if (mySDI12.available()) { // If we hear anything, assume we have an active sensor
302 while (mySDI12.available()) { Serial.write(mySDI12.read()); }
304 mySDI12.clearBuffer();
309 mySDI12.clearBuffer();
314 Serial.begin(serialBaud);
315 while (!Serial && millis() < 10000L);
317 Serial.println("Opening SDI-12 bus...");
319 delay(500); // allow things to settle
321 Serial.println("Timeout value: ");
322 Serial.println(mySDI12.TIMEOUT);
324 // Power the sensors;
326 Serial.println("Powering up sensors, wait...");
327 pinMode(powerPin, OUTPUT);
328 digitalWrite(powerPin, HIGH);
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");
337 for (int8_t i = firstAddress; i <= lastAddress; i++) {
338 char addr = decToChar(i);
339 if (checkActive(addr)) {
346 Serial.print("Total number of sensors found: ");
347 Serial.println(numSensors);
349 if (numSensors == 0) {
351 "No sensors found, please check connections and restart the Arduino.");
352 while (true) { delay(10); } // do nothing forever
356 Serial.println("Time Elapsed (s), Measurement 1, Measurement 2, ... etc.");
358 "-------------------------------------------------------------------------------");
363 if (printIO) { Serial.println("-------------"); }
365 // re-zero before starting a new measurement round
366 for (int8_t i = firstAddress; i <= lastAddress; i++) {
368 expectedResults[i] = 0;
369 returnedResults[i] = 0;
372 // start all sensors measuring concurrently
373 for (int8_t i = firstAddress; i <= lastAddress; i++) {
374 char addr = decToChar(i);
376 startConcurrentMeasurement(addr);
377 if (printIO) { Serial.println(); }
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];
388 while ((millis() - first_ready) > 50) { delay(1); }
391 uint8_t numReadingsRecorded = 0;
396 Serial.print("Round ");
398 Serial.print(" sensors to read: ");
399 Serial.print(numSensors);
400 Serial.print(" finished sensors: ");
401 Serial.print(numReadingsRecorded);
404 for (int8_t i = firstAddress; i <= lastAddress; i++) {
406 char addr = decToChar(i);
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]);
419 if (isActive[i] && millis() > millisReady[i] && expectedResults[i] > 0 &&
420 (returnedResults[i] < expectedResults[i])) {
422 Serial.print(millis() / 1000);
427 getResults(addr, expectedResults[i]);
428 numReadingsRecorded++;
434 } while (numReadingsRecorded < numSensors);
436 delay(10000L); // wait ten seconds between measurement attempts.