Example K: Concurrent Measurements

This is very similar to example D - finding all attached sensors and logging data from them. Unlike example D, 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.

PlatformIO Configuration

1; PlatformIO Project Configuration File
2
3[platformio]
4description = SDI-12 Library Example K: Using Concurrent Measurements
5src_dir = .piolibdeps/Arduino-SDI-12_ID1486/examples/k_concurrent_logger
6
7[env:mayfly]
8monitor_speed = 115200
9board = mayfly
10platform = atmelavr
11framework = arduino
12lib_ldf_mode = deep+
13lib_ignore = RTCZero
14lib_deps =
15 SDI-12

The Complete Example

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/* connection information */
19uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */
20int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */
21int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */
22int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */
23int8_t lastAddress = 62; /* The last address in the address space to check (62='z') */
24
25/** Define the SDI-12 bus */
26SDI12 mySDI12(dataPin);
27
28// keeps track of active addresses
29bool isActive[64];
30
31// keeps track of the wait time for each active addresses
32uint8_t meas_time_ms[64];
33
34// keeps track of the time each sensor was started
35uint32_t millisStarted[64];
36
37// keeps track of the time each sensor will be ready
38uint32_t millisReady[64];
39
40// keeps track of the number of results expected
41uint8_t expectedResults[64];
42
43// keeps track of the number of results returned
44uint8_t returnedResults[64];
45
46uint8_t numSensors = 0;
47
48/**
49 * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a
50 * decimal number between 0 and 61 (inclusive) to cover the 62 possible
51 * addresses.
52 */
53byte charToDec(char i) {
54 if ((i >= '0') && (i <= '9')) return i - '0';
55 if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10;
56 if ((i >= 'A') && (i <= 'Z'))
57 return i - 'A' + 36;
58 else
59 return i;
60}
61
62/**
63 * @brief maps a decimal number between 0 and 61 (inclusive) to allowable
64 * address characters '0'-'9', 'a'-'z', 'A'-'Z',
65 *
66 * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL.
67 */
68char decToChar(byte i) {
69 if (i < 10) return i + '0';
70 if ((i >= 10) && (i < 36)) return i + 'a' - 10;
71 if ((i >= 36) && (i <= 62))
72 return i + 'A' - 36;
73 else
74 return i;
75}
76
77bool getResults(char address, int resultsExpected) {
78 uint8_t resultsReceived = 0;
79 uint8_t cmd_number = 0;
80 while (resultsReceived < resultsExpected && cmd_number <= 9) {
81 String command = "";
82 command = "";
83 command += address;
84 command += "D";
85 command += cmd_number;
86 command += "!"; // SDI-12 command to get data [address][D][dataOption][!]
87 mySDI12.sendCommand(command);
88
89 uint32_t start = millis();
90 while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
91 mySDI12.read(); // ignore the repeated SDI12 address
92 char c = mySDI12.peek(); // check if there's a '+' and toss if so
93 if (c == '+') { mySDI12.read(); }
94
95 while (mySDI12.available()) {
96 char c = mySDI12.peek();
97 if (c == '-' || (c >= '0' && c <= '9') || c == '.') {
98 float result = mySDI12.parseFloat(SKIP_NONE);
99 Serial.print(String(result, 10));
100 if (result != -9999) { resultsReceived++; }
101 } else if (c == '+') {
102 mySDI12.read();
103 Serial.print(", ");
104 } else {
105 mySDI12.read();
106 }
107 delay(10); // 1 character ~ 7.5ms
108 }
109 if (resultsReceived < resultsExpected) { Serial.print(", "); }
110 cmd_number++;
111 }
112 mySDI12.clearBuffer();
113 returnedResults[charToDec(address)] = resultsReceived;
114 return resultsReceived == resultsExpected;
115}
116
117int startConcurrentMeasurement(char i, String meas_type = "") {
118 mySDI12.clearBuffer();
119 String command = "";
120 command += i;
121 command += "C";
122 command += meas_type;
123 command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!]
124 mySDI12.sendCommand(command);
125 delay(30);
126
127 // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of
128 // measurments available, 0-9]
129 String sdiResponse = mySDI12.readStringUntil('\n');
130 sdiResponse.trim();
131
132 // find out how long we have to wait (in seconds).
133 uint8_t wait = sdiResponse.substring(1, 4).toInt();
134
135 // Set up the number of results to expect
136 int numResults = sdiResponse.substring(4).toInt();
137
138 uint8_t sensorNum = charToDec(i); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61.
139 meas_time_ms[sensorNum] = wait;
140 millisStarted[sensorNum] = millis();
141 if (wait == 0) {
142 millisReady[sensorNum] = millis();
143 } else {
144 millisReady[sensorNum] = millis() + wait * 1000;
145 }
146 expectedResults[sensorNum] = numResults;
147
148 return numResults;
149}
150
151// this checks for activity at a particular address
152// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z'
153boolean checkActive(char i) {
154 String myCommand = "";
155 myCommand = "";
156 myCommand += (char)i; // sends basic 'acknowledge' command [address][!]
157 myCommand += "!";
158
159 for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts
160 mySDI12.sendCommand(myCommand);
161 delay(100);
162 if (mySDI12.available()) { // If we here anything, assume we have an active sensor
163 mySDI12.clearBuffer();
164 return true;
165 }
166 }
167 mySDI12.clearBuffer();
168 return false;
169}
170
171/**
172 * @brief gets identification information from a sensor, and prints it to the serial
173 * port
174 *
175 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
176 * @param printCommands true to print the raw output and input from the command
177 */
178void printInfo(char i) {
179 String command = "";
180 command += (char)i;
181 command += "I!";
182 mySDI12.sendCommand(command);
183 delay(100);
184
185 String sdiResponse = mySDI12.readStringUntil('\n');
186 sdiResponse.trim();
187 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
188 Serial.print("Address: ");
189 Serial.print(sdiResponse.substring(0, 1)); // address
190 Serial.print(", SDI-12 Version: ");
191 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
192 Serial.print(", Vendor ID: ");
193 Serial.print(sdiResponse.substring(3, 11)); // vendor id
194 Serial.print(", Sensor Model: ");
195 Serial.print(sdiResponse.substring(11, 17)); // sensor model
196 Serial.print(", Sensor Version: ");
197 Serial.print(sdiResponse.substring(17, 20)); // sensor version
198 Serial.print(", Sensor ID: ");
199 Serial.print(sdiResponse.substring(20)); // sensor id
200 Serial.println();
201}
202
203void setup() {
204 Serial.begin(serialBaud);
205 while (!Serial)
206 ;
207
208 Serial.println("Opening SDI-12 bus...");
209 mySDI12.begin();
210 delay(500); // allow things to settle
211
212 Serial.println("Timeout value: ");
213 Serial.println(mySDI12.TIMEOUT);
214
215 // Power the sensors;
216 if (powerPin >= 0) {
217 Serial.println("Powering up sensors, wait...");
218 pinMode(powerPin, OUTPUT);
219 digitalWrite(powerPin, HIGH);
220 delay(10000L);
221 }
222
223 // Quickly Scan the Address Space
224 Serial.println("Scanning all addresses, please wait...");
225 Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, "
226 "Sensor Version, Sensor ID");
227
228 for (int8_t i = firstAddress; i <= lastAddress; i++) {
229 char addr = decToChar(i);
230 if (checkActive(addr)) {
231 numSensors++;
232 isActive[i] = 1;
233 printInfo(addr);
234 Serial.println();
235 }
236 }
237 Serial.print("Total number of sensors found: ");
238 Serial.println(numSensors);
239
240 if (numSensors == 0) {
241 Serial.println(
242 "No sensors found, please check connections and restart the Arduino.");
243 while (true) { delay(10); } // do nothing forever
244 }
245
246 Serial.println();
247 Serial.println("Time Elapsed (s), Measurement 1, Measurement 2, ... etc.");
248 Serial.println(
249 "-------------------------------------------------------------------------------");
250}
251
252void loop() {
253 // start all sensors measuring concurrently
254 for (int8_t i = firstAddress; i <= lastAddress; i++) {
255 char addr = decToChar(i);
256 if (isActive[i]) { startConcurrentMeasurement(addr); }
257 }
258
259 // get all readings
260 uint8_t numReadingsRecorded = 0;
261 do {
262 for (int8_t i = firstAddress; i <= lastAddress; i++) {
263 char addr = decToChar(i);
264 if (isActive[i] && millis() > millisReady[i] && expectedResults[i] > 0 &&
265 (returnedResults[i] < expectedResults[i])) {
266 Serial.print(millis() / 1000);
267 Serial.print(", ");
268 Serial.print(addr);
269 Serial.print(", ");
270 getResults(addr, expectedResults[i]);
271 numReadingsRecorded++;
272 Serial.println();
273 }
274 }
275 } while (numReadingsRecorded < numSensors);
276
277 delay(10000); // wait ten seconds between measurement attempts.
278}