Example for DRWI CitSci LTE sites.
Example for DRWI CitSci LTE sites.=========================================================================
See the walkthrough page for detailed instructions.
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
44const char* sketchName = "DRWI_DigiLTE.ino";
45// Logger ID, also becomes the prefix for the name of the data file on SD card
46const char* LoggerID = "XXXXX";
47// How frequently (in minutes) to log data
48const uint8_t loggingInterval = 15;
49// Your logger's timezone.
50const 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
55const int32_t serialBaud = 57600; // Baud rate for debugging
56const int8_t greenLED = 8; // Pin for the green LED
57const int8_t redLED = 9; // Pin for the red LED
58const int8_t buttonPin = 21; // Pin for debugging mode (ie, button pin)
59const 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
63const int8_t sdCardPwrPin = -1; // MCU SD card power pin
64const int8_t sdCardSSPin = 12; // SD card chip select/slave select pin
65const 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
80HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible
81const 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
85const int8_t modemVccPin = -2; // MCU pin controlling modem power
86const int8_t modemStatusPin = 19; // MCU pin used to read modem status
87const bool useCTSforStatus = false; // Flag to use the modem CTS pin for status
88const int8_t modemResetPin = 20; // MCU pin connected to modem reset pin
89const int8_t modemSleepRqPin = 23; // MCU pin for modem sleep/wake request
90const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem
91 // status (-1 if unconnected)
93// Network connection information
94const char* apn = "hologram"; // The APN for the gprs connection
96DigiXBeeCellularTransparent modemXBCT(&modemSerial, modemVccPin, modemStatusPin,
97 useCTSforStatus, modemResetPin,
98 modemSleepRqPin, apn);
99// Create an extra reference to the modem by a generic name
100DigiXBeeCellularTransparent 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
111const char* mcuBoardVersion = "v0.5b";
112ProcessorStats 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
123MaximDS3231 ds3231(1);
127// ==========================================================================
128// Campbell OBS 3 / OBS 3+ Analog Turbidity Sensor
129// ==========================================================================
131#include <sensors/CampbellOBS3.h>
133const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected)
134const uint8_t OBS3NumberReadings = 10;
135const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC
136// Campbell OBS 3+ *Low* Range Calibration in Volts
137const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output
138const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range]
139const float OBSLow_B = 1.000E+00; // "B" value (X) [*low* range]
140const float OBSLow_C = 0.000E+00; // "C" value [*low* range]
142// Create a Campbell OBS3+ *low* range sensor object
143CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C,
144 ADSi2c_addr, OBS3NumberReadings);
147// Campbell OBS 3+ *High* Range Calibration in Volts
148const int8_t OBSHighADSChannel = 1; // ADS channel for *high* range output
149const float OBSHigh_A = 0.000E+00; // "A" value (X^2) [*high* range]
150const float OBSHigh_B = 1.000E+00; // "B" value (X) [*high* range]
151const float OBSHigh_C = 0.000E+00; // "C" value [*high* range]
153// Create a Campbell OBS3+ *high* range sensor object
154CampbellOBS3 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>
165const char* hydrosSDI12address = "1"; // The SDI-12 Address of the Hydros 21
166const uint8_t hydrosNumberReadings = 6; // The number of readings to average
167const int8_t SDI12Power = sensorPowerPin; // Power pin (-1 if unconnected)
168const int8_t SDI12Data = 7; // The SDI12 data pin
170// Create a Meter Hydros 21 sensor object
171MeterHydros21 hydros(*hydrosSDI12address, SDI12Power, SDI12Data,
172 hydrosNumberReadings);
176// ==========================================================================
177// Creating the Variable Array[s] and Filling with Variable Objects
178// ==========================================================================
179/** Start [variable_arrays] */
180Variable* 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 ---------------------
210const 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)
221const char* registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token
222const 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
229int variableCount = sizeof(variableList) / sizeof(variableList[0]);
231// Create the VariableArray object
232VariableArray varArray(variableCount, variableList, UUIDs);
233/** End [variable_arrays] */
236// ==========================================================================
237// The Logger Object[s]
238// ==========================================================================
239/** Start [loggers] */
240// Create a new logger instance
241Logger 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>
251EnviroDIYPublisher 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
261void 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!
275float 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();