2 * @example{lineno} TestSensorTiming.ino
3 * @copyright Stroud Water Research Center
4 * @license This example is published under the BSD-3 license.
5 * @author Sara Damiano <sdamiano@stroudcenter.org>
12#define SDI12_DATA_PIN 7
14#ifndef SDI12_POWER_PIN
15#define SDI12_POWER_PIN 22
18/* connection information */
19uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */
20int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */
21int8_t powerPin = SDI12_POWER_PIN; /*!< The sensor power pin (or -1) */
22uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */
23char sensorAddress = '0'; /*!< The address of the SDI-12 sensor */
25/** Define the SDI-12 bus */
26SDI12 mySDI12(dataPin);
28/** Define some testing specs */
30/** Error codes, if returned */
31int8_t error_result_number = 7;
32float no_error_value = 0;
34/** Testing turning off power */
35bool testPowerOff = false;
36int32_t min_power_delay = 100L; /*!< The min time to test wake after power on. */
37int32_t max_power_delay = 180000L; /*!< The max time to test wake after power on. */
38int32_t increment_power_delay = 100L; /*!< The time to lengthen waits between reps. */
39int32_t power_off_time = 60000L; /*!< The time to power off between tests. */
40/** NOTE: set the power off time to be something similar to what you will be using the
41 * the real world! Some sensors take longer to warm up if they've been off for a while.
44/** Testing the length of the break */
46int32_t min_wake_delay = 0; /*!< The min time to test wake after a line break. */
47int32_t max_wake_delay = 100; /*!< The max time to test wake (should be <=100). */
48int32_t increment_wake = 5; /*!< The time to lengthen waits between reps. */
50/** set some initial values */
51int32_t power_delay = min_power_delay;
52int32_t wake_delay = min_wake_delay;
54int32_t total_meas_time = 0;
55int32_t total_meas_made = 0;
56uint32_t max_meas_time = 0;
58struct startMeasurementResult { // Structure declaration
59 String returned_address;
64struct getResultsResult { // Structure declaration
65 uint8_t resultsReceived;
66 uint8_t maxDataCommand;
73getResultsResult getResults(char address, int resultsExpected, bool verify_crc = false,
74 bool printCommands = true) {
75 uint8_t resultsReceived = 0;
76 uint8_t cmd_number = 0;
77 // The maximum number of characters that can be returned in the <values> part of the
78 // response to a D command is either 35 or 75. If the D command is issued to
79 // retrieve data in response to a concurrent measurement command, or in response to
80 // a high-volume ASCII measurement command, the maximum is 75. The maximum is also
81 // 75 in response to a continuous measurement command. Otherwise, the maximum is 35.
82 int max_sdi_response = 76;
83 // max chars in a unsigned 64 bit number
84 int max_sdi_digits = 21;
86 String compiled_response = "";
90 // Create the return struct
91 getResultsResult return_result;
92 return_result.resultsReceived = 0;
93 return_result.maxDataCommand = 0;
94 return_result.addressMatch = true;
95 return_result.crcMatch = true;
96 return_result.errorCode = false;
97 return_result.success = true;
99 while (resultsReceived < resultsExpected && cmd_number <= 9) {
103 command += cmd_number;
104 command += "!"; // SDI-12 command to get data [address][D][dataOption][!]
105 mySDI12.sendCommand(command, wake_delay);
107 // uint32_t start = millis();
110 Serial.println(command);
112 char resp_buffer[max_sdi_response] = {'\0'};
114 // read bytes into the char array until we get to a new line (\r\n)
115 size_t bytes_read = mySDI12.readBytesUntil('\n', resp_buffer, max_sdi_response);
116 // Serial.print(bytes_read);
117 // Serial.println(" characters");
119 size_t data_bytes_read = bytes_read - 1; // subtract one for the /r before the /n
120 String sdiResponse = String(resp_buffer);
121 compiled_response += sdiResponse;
125 Serial.println(sdiResponse);
126 // Serial.println(sdiResponse.length());
127 // Serial.print("<<<");
128 // Serial.println(resp_buffer);
129 // Serial.println(strnlen(resp_buffer, max_sdi_response));
131 // read and clear anything else from the buffer
133 while (mySDI12.available()) {
134 Serial.write(mySDI12.read());
137 if (extra_chars > 0) {
138 Serial.print(extra_chars);
139 Serial.println(" additional characters received.");
141 mySDI12.clearBuffer();
143 // check the address, break if it's incorrect
144 char returned_address = resp_buffer[0];
145 if (returned_address != address) {
147 Serial.println("Wrong address returned!");
148 Serial.print("Expected ");
149 Serial.print(String(address));
150 Serial.print(" Got ");
151 Serial.println(String(returned_address));
152 Serial.println(String(resp_buffer));
155 return_result.addressMatch = false;
159 // check the crc, break if it's incorrect
161 bool crcMatch = mySDI12.verifyCRC(sdiResponse);
162 data_bytes_read = data_bytes_read - 3;
164 if (printCommands) { Serial.println("CRC valid"); }
166 if (printCommands) { Serial.println("CRC check failed!"); }
167 return_result.crcMatch = false;
173 bool gotResults = false;
174 char float_buffer[max_sdi_digits] = {'\0'};
175 char* dec_pl = float_buffer;
176 uint8_t fb_pos = 0; // start at start of buffer
177 bool finished_last_number = false;
178 // iterate through the char array and to check results
179 // NOTE: start at 1 since we already looked at the address!
180 for (size_t i = 1; i < data_bytes_read; i++) {
181 // Get the character at position
182 char c = resp_buffer[i];
184 // Serial.print(" of ");
185 // Serial.print(data_bytes_read);
186 // Serial.print(" '");
188 // Serial.println("'");
189 // if we didn't get something number-esque or we're at the end of the buffer,
190 // assume the last number finished and parse it
191 //(c != '-' && (c < '0' || c > '9') && c != '.')
192 if (c == '-' || (c >= '0' && c <= '9') || c == '.') {
193 // if there's a number, a decimal, or a negative sign next in the
194 // buffer, add it to the float buffer.
195 float_buffer[fb_pos] = c;
197 float_buffer[fb_pos] = '\0'; // null terminate the buffer
198 finished_last_number = false;
199 // Serial.print("Added to float buffer, currently: '");
200 // Serial.print(float_buffer);
201 // Serial.println("'");
203 // Serial.println("Non Numeric");
204 finished_last_number = true;
206 // if we've gotten to the end of a number or the end of the buffer, parse the
208 if ((finished_last_number || i == data_bytes_read - 1) &&
209 strnlen(float_buffer, max_sdi_digits) > 0) {
210 float result = atof(float_buffer);
212 Serial.print("Result ");
213 Serial.print(resultsReceived);
214 Serial.print(", Raw value: ");
215 Serial.print(float_buffer);
216 dec_pl = strchr(float_buffer, '.');
217 size_t len_post_dec = 0;
218 if (dec_pl != nullptr) { len_post_dec = strnlen(dec_pl, max_sdi_digits) - 1; }
219 Serial.print(", Len after decimal: ");
220 Serial.print(len_post_dec);
221 Serial.print(", Parsed value: ");
222 Serial.println(String(result, len_post_dec));
224 // add how many results we have
225 if (result != -9999) {
229 // check for a failure error code at the end
230 if (error_result_number >= 1) {
231 if (resultsReceived == error_result_number && result != no_error_value) {
233 return_result.errorCode = true;
235 Serial.print("Got a failure code of ");
236 Serial.println(String(result, strnlen(dec_pl, max_sdi_digits) - 1));
242 float_buffer[0] = '\0';
249 Serial.println((" No results received, will not continue requests!"));
252 } // don't do another loop if we got nothing
255 Serial.print("Total Results Received: ");
256 Serial.print(resultsReceived);
257 Serial.print(", Remaining: ");
258 Serial.println(resultsExpected - resultsReceived);
264 mySDI12.clearBuffer();
267 Serial.print("After ");
268 Serial.print(cmd_number);
269 Serial.print(" data commands got ");
270 Serial.print(resultsReceived);
271 Serial.print(" results of the expected ");
272 Serial.print(resultsExpected);
273 Serial.print(" expected. This is a ");
274 Serial.println(resultsReceived == resultsExpected ? "success." : "failure.");
277 success &= resultsReceived == resultsExpected;
278 return_result.resultsReceived = resultsReceived;
279 return_result.maxDataCommand = cmd_number;
280 return_result.success = success;
281 return return_result;
284startMeasurementResult startMeasurement(char address, bool is_concurrent = false,
285 bool request_crc = false, String meas_type = "",
286 bool printCommands = true) {
287 // Create the return struct
288 startMeasurementResult return_result;
289 return_result.returned_address = "";
290 return_result.meas_time_s = 0;
291 return_result.numberResults = 0;
294 command += address; // All commands start with the address
295 command += is_concurrent ? "C" : "M"; // C for concurrent, M for standard
296 command += request_crc ? "C" : ""; // add an additional C to request a CRC
297 command += meas_type; // Measurement type, "" or 0-9
298 command += "!"; // All commands end with "!"
299 mySDI12.sendCommand(command, wake_delay);
302 Serial.println(command);
305 // wait for acknowledgement with format [address][ttt (3 char, seconds)][number of
306 // measurements available, 0-9]
307 String sdiResponse = mySDI12.readStringUntil('\n');
311 Serial.println(sdiResponse);
313 mySDI12.clearBuffer();
315 // check the address, return if it's incorrect
316 String returned_address = sdiResponse.substring(0, 1);
317 char ret_addr_array[2];
318 returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array));
319 return_result.returned_address = ret_addr_array[0];
320 if (returned_address != String(address)) {
322 Serial.println("Wrong address returned!");
323 Serial.print("Expected ");
324 Serial.print(String(address));
325 Serial.print(" Got ");
326 Serial.println(returned_address);
328 return return_result;
331 // find out how long we have to wait (in seconds).
332 uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt();
333 return_result.meas_time_s = meas_time_s;
335 Serial.print("expected measurement time: ");
336 Serial.print(meas_time_s);
337 Serial.print(" s, ");
340 // Set up the number of results to expect
341 int numResults = sdiResponse.substring(4).toInt();
342 return_result.numberResults = numResults;
344 Serial.print("Number Results: ");
345 Serial.println(numResults);
348 return return_result;
351uint32_t takeMeasurement(char address, bool request_crc = false, String meas_type = "",
352 bool printCommands = true) {
353 startMeasurementResult startResult = startMeasurement(address, false, request_crc,
354 meas_type, printCommands);
355 if (startResult.numberResults == 0) { return -1; }
357 uint32_t timerStart = millis();
358 uint32_t measTime = -1;
359 // wait up to 1 second longer than the specified return time
360 while ((millis() - timerStart) <
361 (static_cast<uint32_t>(startResult.meas_time_s) + 1) * 1000) {
362 if (mySDI12.available()) {
364 } // sensor can interrupt us to let us know it is done early
366 measTime = millis() - timerStart;
367 String interrupt_response = mySDI12.readStringUntil('\n');
370 Serial.println(interrupt_response);
371 Serial.print("Completed after ");
372 Serial.print(measTime);
373 Serial.println(" ms");
376 // if we got results, return the measurement time, else -1
377 if (getResults(address, startResult.numberResults, request_crc, printCommands)
385// this checks for activity at a particular address
386// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z'
387bool checkActive(char address, int8_t numPings = 3, bool printCommands = true) {
389 command += (char)address; // sends basic 'acknowledge' command [address][!]
392 for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts
395 Serial.println(command);
397 mySDI12.sendCommand(command, wake_delay);
399 // the sensor should just return its address
400 String sdiResponse = mySDI12.readStringUntil('\n');
404 Serial.println(sdiResponse);
406 mySDI12.clearBuffer();
408 // check the address, return false if it's incorrect
409 String returned_address = sdiResponse.substring(0, 1);
410 char ret_addr_array[2];
411 returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array));
412 if (returned_address == String(address)) { return true; }
414 mySDI12.clearBuffer();
419 * @brief gets identification information from a sensor, and prints it to the serial
422 * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'.
423 * @param printCommands true to print the raw output and input from the command
425bool printInfo(char i, bool printCommands = true) {
429 mySDI12.sendCommand(command, wake_delay);
432 Serial.println(command);
436 String sdiResponse = mySDI12.readStringUntil('\n');
438 // allccccccccmmmmmmvvvxxx...xx<CR><LF>
441 Serial.println(sdiResponse);
444 Serial.print("Address: ");
445 Serial.print(sdiResponse.substring(0, 1)); // address
446 Serial.print(", SDI-12 Version: ");
447 Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number
448 Serial.print(", Vendor ID: ");
449 Serial.print(sdiResponse.substring(3, 11)); // vendor id
450 Serial.print(", Sensor Model: ");
451 Serial.print(sdiResponse.substring(11, 17)); // sensor model
452 Serial.print(", Sensor Version: ");
453 Serial.print(sdiResponse.substring(17, 20)); // sensor version
454 Serial.print(", Sensor ID: ");
455 Serial.print(sdiResponse.substring(20)); // sensor id
458 if (sdiResponse.length() < 3) { return false; };
463 Serial.begin(serialBaud);
464 while (!Serial && millis() < 10000L);
466 Serial.print("Opening SDI-12 bus on pin ");
467 Serial.print(String(dataPin));
468 Serial.println("...");
470 delay(500); // allow things to settle
472 Serial.println("Timeout value: ");
473 Serial.println(mySDI12.TIMEOUT);
475 // Power the sensors;
476 if (powerPin >= 0 && !testPowerOff) {
477 Serial.println("Powering up sensors with pin ");
478 Serial.print(String(powerPin));
479 Serial.println(", wait 30s...");
480 pinMode(powerPin, OUTPUT);
481 digitalWrite(powerPin, HIGH);
484 Serial.println("Wait 5s...");
490 bool got_good_results = true;
491 int checks_at_time = 0;
492 while (got_good_results && checks_at_time < 25) {
493 Serial.print("Repeat attempt ");
494 Serial.print(checks_at_time);
495 Serial.print(" with power on warm-up time of ");
496 Serial.println(power_delay);
498 // Power down the sensors;
499 if (powerPin >= 0 && testPowerOff) {
500 Serial.print("Powering down sensors, wait ");
501 Serial.print(power_off_time);
502 Serial.println(" ms");
503 pinMode(powerPin, OUTPUT);
504 digitalWrite(powerPin, LOW);
505 delay(power_off_time);
508 // Power up the sensors;
509 if (powerPin >= 0 && testPowerOff) {
510 Serial.print("Powering up sensors, wait ");
511 Serial.print(power_delay);
512 Serial.println(" ms");
513 pinMode(powerPin, OUTPUT);
514 digitalWrite(powerPin, HIGH);
516 mySDI12.clearBuffer();
519 // checkActive(sensorAddress, true);
521 uint32_t this_meas_time = takeMeasurement(sensorAddress, true, "", true);
522 bool this_result_good = this_meas_time != static_cast<uint32_t>(-1);
524 if (this_result_good) {
525 total_meas_time += this_meas_time;
527 if (this_meas_time > max_meas_time) { max_meas_time = this_meas_time; }
528 Serial.print("Warm-up Time: ");
529 Serial.print(power_delay);
530 Serial.print(", This measurement time: ");
531 Serial.print(this_meas_time);
532 Serial.print(", Mean measurement time: ");
533 Serial.print(total_meas_time / total_meas_made);
534 Serial.print(", Max measurement time: ");
535 Serial.println(max_meas_time);
538 got_good_results &= this_result_good;
541 if (!got_good_results) {
542 Serial.print("Time check failed after ");
543 Serial.print(checks_at_time);
544 Serial.println(" reps");
551 // if we got a good result 25x at this warm-up, keep testing how long the
553 while (got_good_results) {
554 uint32_t this_meas_time = takeMeasurement(sensorAddress, true, "", false);
555 if (this_meas_time != static_cast<uint32_t>(-1)) {
556 total_meas_time += this_meas_time;
558 if (this_meas_time > max_meas_time) { max_meas_time = this_meas_time; }
559 Serial.print("Warm-up Time: ");
560 Serial.print(power_delay);
561 Serial.print(", This measurement time: ");
562 Serial.print(this_meas_time);
563 Serial.print(", Mean measurement time: ");
564 Serial.print(total_meas_time / total_meas_made);
565 Serial.print(", Max measurement time: ");
566 Serial.println(max_meas_time);
571 Serial.println("-------------------------------------------------------------------"
573 if (power_delay > max_power_delay) {
574 power_delay = min_power_delay;
576 power_delay = power_delay + increment_power_delay;