Example M: Non-Blocking Concurrent Logger

This advanced example has functionality similar to Example K, but it never waits inside a recorder transaction. loop() repeatedly calls SDI12Recorder::update() while a small application state machine scans the bus, starts concurrent measurements, and retrieves each sensor's complete data when isMeasurementReady() reports readiness.

The logger starts as many measurements as the recorder's fixed context table can retain. A RESULT_CONTEXT_CAPACITY result ends the start pass without changing the library's default configuration.

PlatformIO Configuration

1; PlatformIO Project Configuration File
2
3[platformio]
4description = SDI-12 Library Example L: Non-Blocking Concurrent Logger
5src_dir = .piolibdeps/Arduino-SDI-12_ID1486/examples/m_non_blocking_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} m_non_blocking_concurrent_logger.ino
3 * @copyright Stroud Water Research Center
4 * @license This example is published under the BSD-3 license.
5 * @author Codex, at instruction of Sara Geleskie Damiano <sdamiano@stroudcenter.org>
6 *
7 * @brief Example M: Non-blocking concurrent measurements.
8 *
9 * This example discovers sensors, starts concurrent measurements, and retrieves each
10 * result when ready. Every recorder transaction is advanced from loop() with update().
11 */
12
13#include <SDI12Config.h>
14#if (defined(__AVR__) || defined(ARDUINO_ARCH_AVR)) && !defined(SDI12_INTERNAL_PCINT)
15#include <EnableInterrupt.h>
16#endif
17
18#include <SDI12Recorder.h>
19
20#ifndef SDI12_DATA_PIN
21#define SDI12_DATA_PIN 7
22#endif
23#ifndef SDI12_POWER_PIN
24#define SDI12_POWER_PIN 22
25#endif
26#ifndef SDI12_TERMINAL_BAUD
27#define SDI12_TERMINAL_BAUD 115200
28#endif
29
30const int8_t dataPin = SDI12_DATA_PIN;
31const int8_t powerPin = SDI12_POWER_PIN;
32const uint32_t serialBaud = SDI12_TERMINAL_BAUD;
33const int8_t firstAddress = 0;
34const int8_t lastAddress = 7;
35const uint8_t addressCount = lastAddress - firstAddress + 1;
36const bool useCRC = true;
37const uint32_t loggingPeriod = 10000UL;
38
39SDI12 mySDI12(dataPin);
40SDI12Recorder mySDI12Recorder(mySDI12);
41
42bool isActive[lastAddress + 1] = {};
43bool measurementPending[lastAddress + 1] = {};
44float values[99];
45
46enum LoggerPhase : uint8_t { SCAN_BUS, START_MEASUREMENTS, READ_MEASUREMENTS, PAUSE };
47
48enum PendingOperation : uint8_t {
49 NO_OPERATION,
50 ACKNOWLEDGEMENT,
51 IDENTIFICATION,
52 CONCURRENT_MEASUREMENT,
53 DATA_RETRIEVAL
54};
55
56LoggerPhase loggerPhase = SCAN_BUS;
57PendingOperation pendingOperation = NO_OPERATION;
58int8_t nextAddress = firstAddress;
59int8_t startAddress = firstAddress;
60int8_t pendingIndex = -1;
61uint8_t sensorsFound = 0U;
62uint8_t sensorsStarted = 0U;
63uint8_t sensorsRead = 0U;
64uint8_t addressesChecked = 0U;
65uint32_t roundEndMillis = 0U;
66
67int8_t nextAddressAfter(int8_t address) {
68 return address < lastAddress ? address + 1 : firstAddress;
69}
70
71int8_t nextActiveAddressAfter(int8_t address) {
72 for (uint8_t i = 0U; i < addressCount; ++i) {
73 address = nextAddressAfter(address);
74 if (isActive[address]) { return address; }
75 }
76 return firstAddress;
77}
78
79void printOperationFailure(char address) {
80 Serial.print(F("Address "));
81 Serial.print(address);
82 Serial.print(F(": "));
83 mySDI12Recorder.printRecorderResult(Serial);
84 Serial.println();
85}
86
87void printValues(char address) {
88 Serial.print(millis() / 1000UL);
89 Serial.print(F(", "));
90 Serial.print(address);
91 for (size_t i = 0U; i < mySDI12Recorder.dataValueCount(); ++i) {
92 Serial.print(F(", "));
93 Serial.print(values[i], 7);
94 }
95 Serial.println();
96}
97
98void handleCompletedOperation() {
99 const char address = SDI12Address::fromIndex(pendingIndex);
100 const bool success = mySDI12Recorder.result() == SDI12Recorder::RESULT_SUCCESS;
101
102 switch (pendingOperation) {
103 case ACKNOWLEDGEMENT:
104 if (success) {
105 isActive[pendingIndex] = true;
106 ++sensorsFound;
107 mySDI12Recorder.startIdentify(address);
108 pendingOperation = IDENTIFICATION;
109 return;
110 }
111 ++nextAddress;
112 break;
113
114 case IDENTIFICATION:
115 if (success) {
116 mySDI12Recorder.printSensorInfo(mySDI12Recorder.identification(), Serial);
117 } else {
118 printOperationFailure(address);
119 }
120 ++nextAddress;
121 break;
122
123 case CONCURRENT_MEASUREMENT:
124 if (success) {
125 measurementPending[pendingIndex] = true;
126 ++sensorsStarted;
127 } else {
128 printOperationFailure(address);
129 if (mySDI12Recorder.result() == SDI12Recorder::RESULT_CONTEXT_CAPACITY) {
130 loggerPhase = READ_MEASUREMENTS;
131 }
132 }
133 nextAddress = nextAddressAfter(nextAddress);
134 ++addressesChecked;
135 break;
136
137 case DATA_RETRIEVAL:
138 if (success) {
139 printValues(address);
140 } else {
141 printOperationFailure(address);
142 }
143 measurementPending[pendingIndex] = false;
144 ++sensorsRead;
145 break;
146
147 case NO_OPERATION: break;
148 }
149
150 pendingOperation = NO_OPERATION;
151 pendingIndex = -1;
152}
153
154void setup() {
155 Serial.begin(serialBaud);
156 while (!Serial && millis() < 10000UL);
157
158 if (powerPin >= 0) {
159 pinMode(powerPin, OUTPUT);
160 digitalWrite(powerPin, HIGH);
161 delay(10000UL);
162 }
163
164 mySDI12.begin();
165
166 Serial.println(F("Scanning the SDI-12 bus without blocking loop()."));
167}
168
169void loop() {
170 mySDI12Recorder.update();
171
172 if (mySDI12Recorder.isBusy()) { return; }
173 if (pendingOperation != NO_OPERATION) { handleCompletedOperation(); }
174 if (pendingOperation != NO_OPERATION) { return; }
175
176 switch (loggerPhase) {
177 case SCAN_BUS:
178 if (nextAddress <= lastAddress) {
179 pendingIndex = nextAddress;
180 mySDI12Recorder.startRequestAcknowledgement(
181 SDI12Address::fromIndex(pendingIndex));
182 pendingOperation = ACKNOWLEDGEMENT;
183 } else {
184 Serial.print(F("Sensors found: "));
185 Serial.println(sensorsFound);
186 startAddress = nextActiveAddressAfter(lastAddress);
187 nextAddress = startAddress;
188 addressesChecked = 0U;
189 loggerPhase = START_MEASUREMENTS;
190 }
191 break;
192
193 case START_MEASUREMENTS:
194 while (addressesChecked < addressCount && !isActive[nextAddress]) {
195 nextAddress = nextAddressAfter(nextAddress);
196 ++addressesChecked;
197 }
198 if (addressesChecked < addressCount) {
199 pendingIndex = nextAddress;
200 mySDI12Recorder.startConcurrentMeasurement(
201 SDI12Address::fromIndex(pendingIndex), 0U, useCRC);
202 pendingOperation = CONCURRENT_MEASUREMENT;
203 } else {
204 loggerPhase = READ_MEASUREMENTS;
205 }
206 break;
207
208 case READ_MEASUREMENTS:
209 if (sensorsRead >= sensorsStarted) {
210 roundEndMillis = millis();
211 loggerPhase = PAUSE;
212 break;
213 }
214 for (int8_t i = firstAddress; i <= lastAddress; ++i) {
215 const char address = SDI12Address::fromIndex(i);
216 if (measurementPending[i] && mySDI12Recorder.isMeasurementReady(address)) {
217 pendingIndex = i;
218 mySDI12Recorder.requestData(address, values, 99U);
219 pendingOperation = DATA_RETRIEVAL;
220 break;
221 }
222 }
223 break;
224
225 case PAUSE:
226 if (millis() - roundEndMillis >= loggingPeriod) {
227 for (int8_t i = firstAddress; i <= lastAddress; ++i) {
228 measurementPending[i] = false;
229 }
230 sensorsStarted = 0U;
231 sensorsRead = 0U;
232 startAddress = nextActiveAddressAfter(startAddress);
233 nextAddress = startAddress;
234 addressesChecked = 0U;
235 loggerPhase = START_MEASUREMENTS;
236 }
237 break;
238 }
239}