This example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. It includes a Meter Hydros21 CTD (formerly know as a Decagon), a Campbell OBS3+ (Turbidity), and a Digi XBee3 LTE-M cellular board for communication. The Digi LTE module also required the use of a EnviroDIY LTEbee Adapter board (discontinued in 2021). The Digi LTE modules are no longer recommended for use and have been replace by the EnviroDIY LTEbee in all DRWI-SWRC-managed stations.
The exact hardware configuration used in this example:
Mayfly v0.5b board Digi XBee LTE module (with Hologram SIM card and EnviroDIY bee adapter) Hydros21 CTD sensor Campbell OBS3+ turbidity sensor Unique Features of the DRWI Digi LTE Example Specifically for sites within the Delaware River Watershed Initiative. Uses a Digi XBee3 LTE-M for live data. To Use this Example Prepare and set up PlatformIO Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal (http:/ / monitormywatershed.org/ ) Create a new PlatformIO project Replace the contents of the platformio.ini for your new project with the platformio.ini file in the examples/DRWI_DigiLTE folder on GitHub.It is important that your PlatformIO configuration has the lib_ldf_mode and build flags set as they are in the example. Without this, the program won't compile. Open DRWI_ DigiLTE.ino and save it to your computer.After opening the link, you should be able to right click anywhere on the page and select "Save Page As". Move it into the src directory of your project. Delete main.cpp in that folder. Set the logger ID Change the "XXXX" in this section of code to the loggerID assigned by Stroud: 1 // Logger ID, also becomes the prefix for the name of the data file on SD card
2 const char * LoggerID = "XXXX" ;
Set the calibration coefficients for the Campbell OBS3+ The OBS3+ ships with a calibration certificate; you need this sheet! Change all of the the 0.000E+00
and 1.000E+00
values in this section of code to the values on that calibration sheet. Use numbers from the side of the calibration sheet that shows the calibration in volts .The sketch will not compile if these values are not entered properly. Do not change any values except those that are 0.000E+00
and 1.000E+00
! 1 // ==========================================================================
2 // Campbell OBS 3 / OBS 3+ Analog Turbidity Sensor
3 // ==========================================================================
4 #include <sensors/CampbellOBS3.h>
5 const int8_t OBS3Power = sensorPowerPin ; // Power pin (-1 if unconnected)
6 const uint8_t OBS3numberReadings = 10 ;
7 const uint8_t ADSi2c_addr = 0x48 ; // The I2C address of the ADS1115 ADC
8 // Campbell OBS 3+ *Low* Range Calibration in Volts
9 const int8_t OBSLowADSChannel = 0 ; // ADS channel for *low* range output
10 const float OBSLow_A = 0.000E+00 ; // "A" value (X^2) [*low* range]
11 const float OBSLow_B = 1.000E+00 ; // "B" value (X) [*low* range]
12 const float OBSLow_C = 0.000E+00 ; // "C" value [*low* range]
13 CampbellOBS3 osb3low ( OBS3Power , OBSLowADSChannel , OBSLow_A , OBSLow_B , OBSLow_C , ADSi2c_addr , OBS3numberReadings );
14 // Campbell OBS 3+ *High* Range Calibration in Volts
15 const int8_t OBSHighADSChannel = 1 ; // ADS channel for *high* range output
16 const float OBSHigh_A = 0.000E+00 ; // "A" value (X^2) [*high* range]
17 const float OBSHigh_B = 1.000E+00 ; // "B" value (X) [*high* range]
18 const float OBSHigh_C = 0.000E+00 ; // "C" value [*high* range]
19 CampbellOBS3 osb3high ( OBS3Power , OBSHighADSChannel , OBSHigh_A , OBSHigh_B , OBSHigh_C , ADSi2c_addr , OBS3numberReadings );
Set the universally universal identifiers (UUID) for each variable Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal (http:/ / monitormywatershed.org/ ) Find and click the white "View Token UUID List" button above the small map on your site page VERY CAREFULLY check that the variables are in exactly the same order as in the variable array:1 Variable * variableList [] = {
If any of the variables are in a different order on the web page than in your code reorder the variables in your code to match the website . After you are completely certain that you have the order right in the variable section of your code use the teal "Copy" button on the website to copy the section of code containing all of the UUID's. Paste the code from the website into your program in this section below the variable array 1 // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION ***
2 // Check the order of your variables in the variable list!!!
3 // Be VERY certain that they match the order of your UUID's!
4 // Rearrange the variables in the variable list if necessary to match!
5 // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION ***
8 "12345678-abcd-1234-ef00-1234567890ab" , // Electrical conductivity (Decagon_CTD-10_Cond)
9 "12345678-abcd-1234-ef00-1234567890ab" , // Temperature (Decagon_CTD-10_Temp)
10 "12345678-abcd-1234-ef00-1234567890ab" , // Water depth (Decagon_CTD-10_Depth)
11 "12345678-abcd-1234-ef00-1234567890ab" , // Turbidity (Campbell_OBS3_Turb)
12 "12345678-abcd-1234-ef00-1234567890ab" , // Turbidity (Campbell_OBS3_Turb)
13 "12345678-abcd-1234-ef00-1234567890ab" , // Battery voltage (EnviroDIY_Mayfly_Batt)
14 "12345678-abcd-1234-ef00-1234567890ab" , // Temperature (EnviroDIY_Mayfly_Temp)
15 "12345678-abcd-1234-ef00-1234567890ab" , // Received signal strength indication (Digi_Cellular_RSSI)
16 "12345678-abcd-1234-ef00-1234567890ab" // Percent full scale (Digi_Cellular_SignalPercent)
18 const char * registrationToken = "12345678-abcd-1234-ef00-1234567890ab" ; // Device registration token
19 const char * samplingFeature = "12345678-abcd-1234-ef00-1234567890ab" ; // Sampling feature UUID
Upload! Test everything at home before deploying out in the wild! PlatformIO Configuration 1 ; PlatformIO Project Configuration File
3 ; Build options: build flags, source filter
4 ; Upload options: custom upload port, speed and extra flags
5 ; Library options: dependencies, extra library storages
6 ; Advanced options: extra scripting
8 ; Please visit documentation for the other options and examples
9 ; http://docs.platformio.org/page/projectconf.html
12 description = ModularSensors example intended for DRWI users with CTD, turbidity, and a Digi brand LTE modem
30 -DSDI12_EXTERNAL_PCINT
32 envirodiy/EnviroDIY_ModularSensors
33 ; ^^ Use this when working from an official release of the library
34 ; https://github.com/EnviroDIY/ModularSensors.git#develop
35 ; ^^ Use this when if you want to pull from the develop branch
The Complete Code 1 /** =========================================================================
2 * @example{lineno} DRWI_DigiLTE.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 for DRWI CitSci LTE sites.
9 * See [the walkthrough page](@ref example_drwi_digilte) for detailed
12 * @m_examplenavigation{example_drwi_digilte,}
13 * ======================================================================= */
15 // ==========================================================================
17 // ==========================================================================
19 #ifndef TINY_GSM_RX_BUFFER
20 #define TINY_GSM_RX_BUFFER 64
22 #ifndef TINY_GSM_YIELD_MS
23 #define TINY_GSM_YIELD_MS 2
27 // ==========================================================================
28 // Include the libraries required for any data logger
29 // ==========================================================================
30 /** Start [includes] */
31 // The Arduino library is needed for every Arduino program.
34 // Include the main header for ModularSensors
35 #include <ModularSensors.h>
39 // ==========================================================================
40 // Data Logging Options
41 // ==========================================================================
42 /** Start [logging_options] */
43 // The name of this program file
44 const char * sketchName = "DRWI_DigiLTE.ino" ;
45 // Logger ID, also becomes the prefix for the name of the data file on SD card
46 const char * LoggerID = "XXXXX" ;
47 // How frequently (in minutes) to log data
48 const uint8_t loggingInterval = 15 ;
49 // Your logger's timezone.
50 const int8_t timeZone = -5 ; // Eastern Standard Time
51 // NOTE: Daylight savings time will not be applied! Please use standard time!
53 // Set the input and output pins for the logger
54 // NOTE: Use -1 for pins that do not apply
55 const int32_t serialBaud = 57600 ; // Baud rate for debugging
56 const int8_t greenLED = 8 ; // Pin for the green LED
57 const int8_t redLED = 9 ; // Pin for the red LED
58 const int8_t buttonPin = 21 ; // Pin for debugging mode (ie, button pin)
59 const int8_t wakePin = 31 ; // MCU interrupt/alarm pin to wake from sleep
61 // Set the wake pin to -1 if you do not want the main processor to sleep.
62 // In a SAMD system where you are using the built-in rtc, set wakePin to 1
63 const int8_t sdCardPwrPin = -1 ; // MCU SD card power pin
64 const int8_t sdCardSSPin = 12 ; // SD card chip select/slave select pin
65 const int8_t sensorPowerPin = 22 ; // MCU pin controlling main sensor power
66 /** End [logging_options] */
69 // ==========================================================================
70 // Wifi/Cellular Modem Options
71 // ==========================================================================
72 /** Start [digi_xbee_cellular_transparent] */
73 // For any Digi Cellular XBee's
74 // NOTE: The u-blox based Digi XBee's (3G global and LTE-M global)
75 // are more stable used in bypass mode (below)
76 // The Telit based Digi XBees (LTE Cat1) can only use this mode.
77 #include <modems/DigiXBeeCellularTransparent.h>
79 // Create a reference to the serial port for the modem
80 HardwareSerial & modemSerial = Serial1 ; // Use hardware serial if possible
81 const int32_t modemBaud = 9600 ; // All XBee's use 9600 by default
83 // Modem Pins - Describe the physical pin connection of your modem to your board
84 // NOTE: Use -1 for pins that do not apply
85 const int8_t modemVccPin = -2 ; // MCU pin controlling modem power
86 const int8_t modemStatusPin = 19 ; // MCU pin used to read modem status
87 const bool useCTSforStatus = false ; // Flag to use the modem CTS pin for status
88 const int8_t modemResetPin = 20 ; // MCU pin connected to modem reset pin
89 const int8_t modemSleepRqPin = 23 ; // MCU pin for modem sleep/wake request
90 const int8_t modemLEDPin = redLED ; // MCU pin connected an LED to show modem
91 // status (-1 if unconnected)
93 // Network connection information
94 const char * apn = "hologram" ; // The APN for the gprs connection
96 DigiXBeeCellularTransparent modemXBCT ( & modemSerial , modemVccPin , modemStatusPin ,
97 useCTSforStatus , modemResetPin ,
98 modemSleepRqPin , apn );
99 // Create an extra reference to the modem by a generic name
100 DigiXBeeCellularTransparent modem = modemXBCT ;
101 /** End [digi_xbee_cellular_transparent] */
104 // ==========================================================================
105 // Using the Processor as a Sensor
106 // ==========================================================================
107 /** Start [processor_sensor] */
108 #include <sensors/ProcessorStats.h>
110 // Create the main processor chip "sensor" - for general metadata
111 const char * mcuBoardVersion = "v0.5b" ;
112 ProcessorStats mcuBoard ( mcuBoardVersion );
113 /** End [processor_sensor] */
116 // ==========================================================================
117 // Maxim DS3231 RTC (Real Time Clock)
118 // ==========================================================================
120 #include <sensors/MaximDS3231.h>
122 // Create a DS3231 sensor object
123 MaximDS3231 ds3231 ( 1 );
127 // ==========================================================================
128 // Campbell OBS 3 / OBS 3+ Analog Turbidity Sensor
129 // ==========================================================================
131 #include <sensors/CampbellOBS3.h>
133 const int8_t OBS3Power = sensorPowerPin ; // Power pin (-1 if unconnected)
134 const uint8_t OBS3NumberReadings = 10 ;
135 const uint8_t ADSi2c_addr = 0x48 ; // The I2C address of the ADS1115 ADC
136 // Campbell OBS 3+ *Low* Range Calibration in Volts
137 const int8_t OBSLowADSChannel = 0 ; // ADS channel for *low* range output
138 const float OBSLow_A = 0.000E+00 ; // "A" value (X^2) [*low* range]
139 const float OBSLow_B = 1.000E+00 ; // "B" value (X) [*low* range]
140 const float OBSLow_C = 0.000E+00 ; // "C" value [*low* range]
142 // Create a Campbell OBS3+ *low* range sensor object
143 CampbellOBS3 osb3low ( OBS3Power , OBSLowADSChannel , OBSLow_A , OBSLow_B , OBSLow_C ,
144 ADSi2c_addr , OBS3NumberReadings );
147 // Campbell OBS 3+ *High* Range Calibration in Volts
148 const int8_t OBSHighADSChannel = 1 ; // ADS channel for *high* range output
149 const float OBSHigh_A = 0.000E+00 ; // "A" value (X^2) [*high* range]
150 const float OBSHigh_B = 1.000E+00 ; // "B" value (X) [*high* range]
151 const float OBSHigh_C = 0.000E+00 ; // "C" value [*high* range]
153 // Create a Campbell OBS3+ *high* range sensor object
154 CampbellOBS3 osb3high ( OBS3Power , OBSHighADSChannel , OBSHigh_A , OBSHigh_B ,
155 OBSHigh_C , ADSi2c_addr , OBS3NumberReadings );
159 // ==========================================================================
160 // Meter Hydros 21 Conductivity, Temperature, and Depth Sensor
161 // ==========================================================================
162 /** Start [hydros21] */
163 #include <sensors/MeterHydros21.h>
165 const char * hydrosSDI12address = "1" ; // The SDI-12 Address of the Hydros 21
166 const uint8_t hydrosNumberReadings = 6 ; // The number of readings to average
167 const int8_t SDI12Power = sensorPowerPin ; // Power pin (-1 if unconnected)
168 const int8_t SDI12Data = 7 ; // The SDI12 data pin
170 // Create a Meter Hydros 21 sensor object
171 MeterHydros21 hydros ( * hydrosSDI12address , SDI12Power , SDI12Data ,
172 hydrosNumberReadings );
176 // ==========================================================================
177 // Creating the Variable Array[s] and Filling with Variable Objects
178 // ==========================================================================
179 /** Start [variable_arrays] */
180 Variable * variableList [] = {
181 new MeterHydros21_Cond ( & hydros ),
182 new MeterHydros21_Depth ( & hydros ),
183 new MeterHydros21_Temp ( & hydros ),
184 new CampbellOBS3_Turbidity ( & osb3low , "" , "TurbLow" ),
185 new CampbellOBS3_Turbidity ( & osb3high , "" , "TurbHigh" ),
186 new ProcessorStats_Battery ( & mcuBoard ),
187 new MaximDS3231_Temp ( & ds3231 ),
188 new Modem_SignalPercent ( & modem ),
191 // All UUID's, device registration, and sampling feature information can be
192 // pasted directly from Monitor My Watershed.
193 // To get the list, click the "View token UUID list" button on the upper right
196 // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION ***
197 // Check the order of your variables in the variable list!!!
198 // Be VERY certain that they match the order of your UUID's!
199 // Rearrange the variables in the variable list ABOVE if necessary to match!
200 // Do not change the order of the variables in the section below.
201 // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION ***
203 // Replace all of the text in the following section with the UUID array from
206 /* clang-format off */
207 // --------------------- Beginning of Token UUID List ---------------------
210 const char * UUIDs [] = // UUID array for device sensors
212 "12345678-abcd-1234-ef00-1234567890ab" , // Specific conductance (Meter_Hydros21_Cond)
213 "12345678-abcd-1234-ef00-1234567890ab" , // Water depth (Meter_Hydros21_Depth)
214 "12345678-abcd-1234-ef00-1234567890ab" , // Temperature (Meter_Hydros21_Temp)
215 "12345678-abcd-1234-ef00-1234567890ab" , // Turbidity (Campbell_OBS3_Turb) (Low)
216 "12345678-abcd-1234-ef00-1234567890ab" , // Turbidity (Campbell_OBS3_Turb) (High)
217 "12345678-abcd-1234-ef00-1234567890ab" , // Battery voltage (EnviroDIY_Mayfly_Batt)
218 "12345678-abcd-1234-ef00-1234567890ab" , // Battery voltage (EnviroDIY_Mayfly_Batt)
219 "12345678-abcd-1234-ef00-1234567890ab" , // Percent full scale (Digi_Cellular_SignalPercent)
221 const char * registrationToken = "12345678-abcd-1234-ef00-1234567890ab" ; // Device registration token
222 const char * samplingFeature = "12345678-abcd-1234-ef00-1234567890ab" ; // Sampling feature UUID
225 // ----------------------- End of Token UUID List -----------------------
228 // Count up the number of pointers in the array
229 int variableCount = sizeof ( variableList ) / sizeof ( variableList [ 0 ]);
231 // Create the VariableArray object
232 VariableArray varArray ( variableCount , variableList , UUIDs );
233 /** End [variable_arrays] */
236 // ==========================================================================
237 // The Logger Object[s]
238 // ==========================================================================
239 /** Start [loggers] */
240 // Create a new logger instance
241 Logger dataLogger ( LoggerID , loggingInterval , & varArray );
245 // ==========================================================================
246 // Creating Data Publisher[s]
247 // ==========================================================================
248 /** Start [publishers] */
249 // Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint
250 #include <publishers/EnviroDIYPublisher.h>
251 EnviroDIYPublisher EnviroDIYPOST ( dataLogger , & modem . gsmClient ,
252 registrationToken , samplingFeature );
253 /** End [publishers] */
256 // ==========================================================================
258 // ==========================================================================
259 /** Start [working_functions] */
260 // Flashes the LED's on the primary board
261 void greenredflash ( uint8_t numFlash = 4 , uint8_t rate = 75 ) {
262 for ( uint8_t i = 0 ; i < numFlash ; i ++ ) {
263 digitalWrite ( greenLED , HIGH );
264 digitalWrite ( redLED , LOW );
266 digitalWrite ( greenLED , LOW );
267 digitalWrite ( redLED , HIGH );
270 digitalWrite ( redLED , LOW );
273 // Reads the battery voltage
274 // NOTE: This will actually return the battery level from the previous update!
275 float getBatteryVoltage () {
276 if ( mcuBoard . sensorValues [ 0 ] == -9999 ) mcuBoard . update ();
277 return mcuBoard . sensorValues [ 0 ];
281 // ==========================================================================
282 // Arduino Setup Function
283 // ==========================================================================
286 // Start the primary serial connection
287 Serial . begin ( serialBaud );
289 // Print a start-up note to the first serial port
290 Serial . print ( F ( "Now running " ));
291 Serial . print ( sketchName );
292 Serial . print ( F ( " on Logger " ));
293 Serial . println ( LoggerID );
296 Serial . print ( F ( "Using ModularSensors Library version " ));
297 Serial . println ( MODULAR_SENSORS_VERSION );
298 Serial . print ( F ( "TinyGSM Library version " ));
299 Serial . println ( TINYGSM_VERSION );
302 // Start the serial connection with the modem
303 modemSerial . begin ( modemBaud );
305 // Set up pins for the LED's
306 pinMode ( greenLED , OUTPUT );
307 digitalWrite ( greenLED , LOW );
308 pinMode ( redLED , OUTPUT );
309 digitalWrite ( redLED , LOW );
310 // Blink the LEDs to show the board is on and starting up
313 pinMode ( 20 , OUTPUT ); // for proper operation of the onboard flash memory
314 // chip's ChipSelect (Mayfly v1.0 and later)
316 // Set the timezones for the logger/data and the RTC
317 // Logging in the given time zone
318 Logger :: setLoggerTimeZone ( timeZone );
319 // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0)
320 Logger :: setRTCTimeZone ( 0 );
322 // Attach the modem and information pins to the logger
323 dataLogger . attachModem ( modem );
324 modem . setModemLED ( modemLEDPin );
325 dataLogger . setLoggerPins ( wakePin , sdCardSSPin , sdCardPwrPin , buttonPin ,
331 // Note: Please change these battery voltages to match your battery
332 // Set up the sensors, except at lowest battery level
333 if ( getBatteryVoltage () > 3.4 ) {
334 Serial . println ( F ( "Setting up sensors..." ));
335 varArray . setupSensors ();
338 // Extra modem set-up - selecting AT&T as the carrier and LTE-M only
339 // NOTE: The code for this could be shortened using the "commandMode" and
340 // other XBee specific commands in TinyGSM. I've written it this way in
341 // this example to show how the settings could be changed in either bypass
342 // OR transparent mode.
343 Serial . println ( F ( "Waking modem and setting Cellular Carrier Options..." ));
344 modem . modemWake (); // NOTE: This will also set up the modem
345 // Go back to command mode to set carrier options
346 for ( uint8_t i = 0 ; i < 5 ; i ++ ) {
347 // Wait the required guard time before entering command mode
349 modem . gsmModem . streamWrite ( GF ( "+++" )); // enter command mode
350 if ( modem . gsmModem . waitResponse ( 2000 , GF ( "OK \r " )) == 1 ) break ;
352 // Carrier Profile - 0 = Automatic selection
353 // - 1 = No profile/SIM ICCID selected
356 // NOTE: To select T-Mobile, you must enter bypass mode!
357 modem . gsmModem . sendAT ( GF ( "CP" ), 2 );
358 modem . gsmModem . waitResponse ( GF ( "OK \r " ));
359 // Cellular network technology - 0 = LTE-M with NB-IoT fallback
360 // - 1 = NB-IoT with LTE-M fallback
363 modem . gsmModem . sendAT ( GF ( "N#" ), 2 );
364 modem . gsmModem . waitResponse ();
365 // Write changes to flash and apply them
366 Serial . println ( F ( "Wait while applying changes..." ));
367 // Write changes to flash
368 modem . gsmModem . sendAT ( GF ( "WR" ));
369 modem . gsmModem . waitResponse ( GF ( "OK \r " ));
371 modem . gsmModem . sendAT ( GF ( "AC" ));
372 modem . gsmModem . waitResponse ( GF ( "OK \r " ));
373 // Reset the cellular component to ensure network settings are changed
374 modem . gsmModem . sendAT ( GF ( "!R" ));
375 modem . gsmModem . waitResponse ( 30000L , GF ( "OK \r " ));
376 // Force reset of the Digi component as well
377 // This effectively exits command mode
378 modem . gsmModem . sendAT ( GF ( "FR" ));
379 modem . gsmModem . waitResponse ( 5000L , GF ( "OK \r " ));
381 // Sync the clock if it isn't valid or we have battery to spare
382 if ( getBatteryVoltage () > 3.55 || ! dataLogger . isRTCSane ()) {
383 // Synchronize the RTC with NIST
384 // This will also set up the modem
385 dataLogger . syncRTC ();
388 // Create the log file, adding the default header to it
389 // Do this last so we have the best chance of getting the time correct and
390 // all sensor names correct
391 // Writing to the SD card can be power intensive, so if we're skipping
392 // the sensor setup we'll skip this too.
393 if ( getBatteryVoltage () > 3.4 ) {
394 Serial . println ( F ( "Setting up file on SD card" ));
395 dataLogger . turnOnSDcard (
396 true ); // true = wait for card to settle after power up
397 dataLogger . createLogFile ( true ); // true = write a new header
398 dataLogger . turnOffSDcard (
399 true ); // true = wait for internal housekeeping after write
402 // Call the processor sleep
403 Serial . println ( F ( "Putting processor to sleep \n " ));
404 dataLogger . systemSleep ();
409 // ==========================================================================
410 // Arduino Loop Function
411 // ==========================================================================
413 // Use this short loop for simple data logging and sending
415 // Note: Please change these battery voltages to match your battery
416 // At very low battery, just go back to sleep
417 if ( getBatteryVoltage () < 3.4 ) {
418 dataLogger . systemSleep ();
420 // At moderate voltage, log data but don't send it over the modem
421 else if ( getBatteryVoltage () < 3.55 ) {
422 dataLogger . logData ();
424 // If the battery is good, send the data to the world
426 dataLogger . logDataAndPublish ();