Example logging data and publishing to Monitor My Watershed.
        Example logging data and publishing to Monitor My Watershed.
See the walkthrough page for detailed instructions.
  1/** ========================================================================= 
  2 * @example{lineno} logging_to_MMW.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 logging data and publishing to Monitor My Watershed. 
  9 * See [the walkthrough page](@ref example_mmw) for detailed instructions. 
 11 * @m_examplenavigation{example_mmw,} 
 12 * ======================================================================= */ 
 14// ========================================================================== 
 16// ========================================================================== 
 18#ifndef TINY_GSM_RX_BUFFER 
 19#define TINY_GSM_RX_BUFFER 64 
 21#ifndef TINY_GSM_YIELD_MS 
 22#define TINY_GSM_YIELD_MS 2 
 26// ========================================================================== 
 27//  Include the libraries required for any data logger 
 28// ========================================================================== 
 29/** Start [includes] */ 
 30// The Arduino library is needed for every Arduino program. 
 33// Include the main header for ModularSensors 
 34#include <ModularSensors.h> 
 38// ========================================================================== 
 39//  Data Logging Options 
 40// ========================================================================== 
 41/** Start [logging_options] */ 
 42// The name of this program file 
 43const char* sketchName = "logging_to MMW.ino"; 
 44// Logger ID, also becomes the prefix for the name of the data file on SD card 
 45const char* LoggerID = "XXXXX"; 
 46// How frequently (in minutes) to log data 
 47const int8_t loggingInterval = 15; 
 48// Your logger's timezone. 
 49const int8_t timeZone = -5;  // Eastern Standard Time 
 50// NOTE:  Daylight savings time will not be applied!  Please use standard time! 
 52// Set the input and output pins for the logger 
 53// NOTE:  Use -1 for pins that do not apply 
 54const int32_t serialBaud = 115200;  // Baud rate for debugging 
 55const int8_t  greenLED   = 8;       // Pin for the green LED 
 56const int8_t  redLED     = 9;       // Pin for the red LED 
 57const int8_t  buttonPin  = 21;      // Pin for debugging mode (ie, button pin) 
 58const int8_t  wakePin    = 31;  // MCU interrupt/alarm pin to wake from sleep 
 60// Set the wake pin to -1 if you do not want the main processor to sleep. 
 61// In a SAMD system where you are using the built-in rtc, set wakePin to 1 
 62const int8_t sdCardPwrPin   = -1;  // MCU SD card power pin 
 63const int8_t sdCardSSPin    = 12;  // SD card chip select/slave select pin 
 64const int8_t sensorPowerPin = 22;  // MCU pin controlling main sensor power 
 65/** End [logging_options] */ 
 68// ========================================================================== 
 69//  Wifi/Cellular Modem Options 
 70// ========================================================================== 
 71/** Start [digi_xbee_cellular_transparent] */ 
 72// For any Digi Cellular XBee's 
 73// NOTE:  The u-blox based Digi XBee's (3G global and LTE-M global) can be used 
 74// in either bypass or transparent mode, each with pros and cons 
 75// The Telit based Digi XBees (LTE Cat1) can only use this mode. 
 76#include <modems/DigiXBeeCellularTransparent.h> 
 78// Create a reference to the serial port for the modem 
 79HardwareSerial& modemSerial = Serial1;  // Use hardware serial if possible 
 80const int32_t   modemBaud   = 9600;     // All XBee's use 9600 by default 
 82// Modem Pins - Describe the physical pin connection of your modem to your board 
 83// NOTE:  Use -1 for pins that do not apply 
 84const int8_t modemVccPin    = -2;     // MCU pin controlling modem power 
 85const int8_t modemStatusPin = 19;     // MCU pin used to read modem status 
 86const bool useCTSforStatus  = false;  // Flag to use the XBee CTS pin for status 
 87const int8_t modemResetPin  = 20;     // MCU pin connected to modem reset pin 
 88const int8_t modemSleepRqPin = 23;    // MCU pin for modem sleep/wake request 
 89const int8_t modemLEDPin = redLED;    // MCU pin connected an LED to show modem 
 90                                      // status (-1 if unconnected) 
 92// Network connection information 
 93const char* apn = "xxxxx";  // The APN for the gprs connection 
 95// NOTE:  If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but 
 96// the `CTS` pin can also be used if necessary 
 97DigiXBeeCellularTransparent modemXBCT(&modemSerial, modemVccPin, modemStatusPin, 
 98                                      useCTSforStatus, modemResetPin, 
 99                                      modemSleepRqPin, apn); 
100// Create an extra reference to the modem by a generic name 
101DigiXBeeCellularTransparent modem = modemXBCT; 
102/** End [digi_xbee_cellular_transparent] */ 
105// ========================================================================== 
106//  Using the Processor as a Sensor 
107// ========================================================================== 
108/** Start [processor_sensor] */ 
109#include <sensors/ProcessorStats.h> 
111// Create the main processor chip "sensor" - for general metadata 
112const char*    mcuBoardVersion = "v1.1"; 
113ProcessorStats mcuBoard(mcuBoardVersion); 
114/** End [processor_sensor] */ 
117// ========================================================================== 
118//  Maxim DS3231 RTC (Real Time Clock) 
119// ========================================================================== 
121#include <sensors/MaximDS3231.h> 
123// Create a DS3231 sensor object 
124MaximDS3231 ds3231(1); 
128// ========================================================================== 
129//  Bosch BME280 Environmental Sensor 
130// ========================================================================== 
132#include <sensors/BoschBME280.h> 
134const int8_t I2CPower    = sensorPowerPin;  // Power pin (-1 if unconnected) 
135uint8_t      BMEi2c_addr = 0x76; 
136// The BME280 can be addressed either as 0x77 (Adafruit default) or 0x76 (Grove 
137// default) Either can be physically modified for the other address 
139// Create a Bosch BME280 sensor object 
140BoschBME280 bme280(I2CPower, BMEi2c_addr); 
144// ========================================================================== 
145//  Maxim DS18 One Wire Temperature Sensor 
146// ========================================================================== 
148#include <sensors/MaximDS18.h> 
150// OneWire Address [array of 8 hex characters] 
151// If only using a single sensor on the OneWire bus, you may omit the address 
152// DeviceAddress OneWireAddress1 = {0x28, 0xFF, 0xBD, 0xBA, 0x81, 0x16, 0x03, 
154const int8_t OneWirePower = sensorPowerPin;  // Power pin (-1 if unconnected) 
155const int8_t OneWireBus   = 6;  // OneWire Bus Pin (-1 if unconnected) 
157// Create a Maxim DS18 sensor objects (use this form for a known address) 
158// MaximDS18 ds18(OneWireAddress1, OneWirePower, OneWireBus); 
160// Create a Maxim DS18 sensor object (use this form for a single sensor on bus 
161// with an unknown address) 
162MaximDS18 ds18(OneWirePower, OneWireBus); 
166// ========================================================================== 
167//  Creating the Variable Array[s] and Filling with Variable Objects 
168// ========================================================================== 
169/** Start [variable_arrays] */ 
170Variable* variableList[] = { 
171    new ProcessorStats_SampleNumber(&mcuBoard, 
172                                    "12345678-abcd-1234-ef00-1234567890ab"), 
173    new BoschBME280_Temp(&bme280, "12345678-abcd-1234-ef00-1234567890ab"), 
174    new BoschBME280_Humidity(&bme280, "12345678-abcd-1234-ef00-1234567890ab"), 
175    new BoschBME280_Pressure(&bme280, "12345678-abcd-1234-ef00-1234567890ab"), 
176    new BoschBME280_Altitude(&bme280, "12345678-abcd-1234-ef00-1234567890ab"), 
177    new MaximDS18_Temp(&ds18, "12345678-abcd-1234-ef00-1234567890ab"), 
178    new ProcessorStats_Battery(&mcuBoard, 
179                               "12345678-abcd-1234-ef00-1234567890ab"), 
180    new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab"), 
181    new Modem_RSSI(&modem, "12345678-abcd-1234-ef00-1234567890ab"), 
182    new Modem_SignalPercent(&modem, "12345678-abcd-1234-ef00-1234567890ab"), 
186// Count up the number of pointers in the array 
187int variableCount = sizeof(variableList) / sizeof(variableList[0]); 
189// Create the VariableArray object 
190VariableArray varArray(variableCount, variableList); 
191/** End [variable_arrays] */ 
194// ========================================================================== 
195//  The Logger Object[s] 
196// ========================================================================== 
197/** Start [loggers] */ 
198// Create a new logger instance 
199Logger dataLogger(LoggerID, loggingInterval, &varArray); 
203// ========================================================================== 
204//  Creating Data Publisher[s] 
205// ========================================================================== 
206/** Start [publishers] */ 
207// A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal 
208// Device registration and sampling feature information can be obtained after 
209// registration at https://monitormywatershed.org or https://data.envirodiy.org 
210const char* registrationToken = 
211    "12345678-abcd-1234-ef00-1234567890ab";  // Device registration token 
212const char* samplingFeature = 
213    "12345678-abcd-1234-ef00-1234567890ab";  // Sampling feature UUID 
215// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint 
216#include <publishers/EnviroDIYPublisher.h> 
217EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, 
219/** End [publishers] */ 
222// ========================================================================== 
224// ========================================================================== 
225/** Start [working_functions] */ 
226// Flashes the LED's on the primary board 
227void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { 
228    for (uint8_t i = 0; i < numFlash; i++) { 
229        digitalWrite(greenLED, HIGH); 
230        digitalWrite(redLED, LOW); 
232        digitalWrite(greenLED, LOW); 
233        digitalWrite(redLED, HIGH); 
236    digitalWrite(redLED, LOW); 
239// Reads the battery voltage 
240// NOTE: This will actually return the battery level from the previous update! 
241float getBatteryVoltage() { 
242    if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); 
243    return mcuBoard.sensorValues[0]; 
245/** End [working_functions] */ 
248// ========================================================================== 
249//  Arduino Setup Function 
250// ========================================================================== 
253// Wait for USB connection to be established by PC 
254// NOTE:  Only use this when debugging - if not connected to a PC, this 
255// could prevent the script from starting 
256#if defined(SERIAL_PORT_USBVIRTUAL) 
257    while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000)) { 
262    // Start the primary serial connection 
263    Serial.begin(serialBaud); 
265    // Print a start-up note to the first serial port 
266    Serial.print(F("Now running ")); 
267    Serial.print(sketchName); 
268    Serial.print(F(" on Logger ")); 
269    Serial.println(LoggerID); 
272    Serial.print(F("Using ModularSensors Library version ")); 
273    Serial.println(MODULAR_SENSORS_VERSION); 
274    Serial.print(F("TinyGSM Library version ")); 
275    Serial.println(TINYGSM_VERSION); 
278// Allow interrupts for software serial 
279#if defined(SoftwareSerial_ExtInts_h) 
280    enableInterrupt(softSerialRx, SoftwareSerial_ExtInts::handle_interrupt, 
283#if defined(NeoSWSerial_h) 
284    enableInterrupt(neoSSerial1Rx, neoSSerial1ISR, CHANGE); 
287    // Start the serial connection with the modem 
288    modemSerial.begin(modemBaud); 
290    // Set up pins for the LED's 
291    pinMode(greenLED, OUTPUT); 
292    digitalWrite(greenLED, LOW); 
293    pinMode(redLED, OUTPUT); 
294    digitalWrite(redLED, LOW); 
295    // Blink the LEDs to show the board is on and starting up 
298    // Set the timezones for the logger/data and the RTC 
299    // Logging in the given time zone 
300    Logger::setLoggerTimeZone(timeZone); 
301    // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0) 
302    loggerClock::setRTCOffset(0); 
304    // Attach the modem and information pins to the logger 
305    dataLogger.attachModem(modem); 
306    modem.setModemLED(modemLEDPin); 
307    dataLogger.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin, 
313    // Note:  Please change these battery voltages to match your battery 
314    // Set up the sensors, except at lowest battery level 
315    if (getBatteryVoltage() > 3.4) { 
316        Serial.println(F("Setting up sensors...")); 
317        varArray.setupSensors(); 
320    // Sync the clock if it isn't valid or we have battery to spare 
321    if (getBatteryVoltage() > 3.55 || !loggerClock::isRTCSane()) { 
322        // Synchronize the RTC with NIST 
323        // This will also set up the modem 
324        dataLogger.syncRTC(); 
327    // Create the log file, adding the default header to it 
328    // Do this last so we have the best chance of getting the time correct and 
329    // all sensor names correct 
330    // Writing to the SD card can be power intensive, so if we're skipping 
331    // the sensor setup we'll skip this too. 
332    if (getBatteryVoltage() > 3.4) { 
333        Serial.println(F("Setting up file on SD card")); 
334        dataLogger.turnOnSDcard( 
335            true);  // true = wait for card to settle after power up 
336        dataLogger.createLogFile(true);  // true = write a new header 
337        dataLogger.turnOffSDcard( 
338            true);  // true = wait for internal housekeeping after write 
341    // Call the processor sleep 
342    Serial.println(F("Putting processor to sleep\n")); 
343    dataLogger.systemSleep(); 
348// ========================================================================== 
349//  Arduino Loop Function 
350// ========================================================================== 
352// Use this short loop for simple data logging and sending 
354    // Note:  Please change these battery voltages to match your battery 
355    // At very low battery, just go back to sleep 
356    if (getBatteryVoltage() < 3.4) { 
357        dataLogger.systemSleep(); 
359    // At moderate voltage, log data but don't send it over the modem 
360    else if (getBatteryVoltage() < 3.55) { 
361        dataLogger.logData(); 
363    // If the battery is good, send the data to the world 
365        dataLogger.logDataAndPublish(); 
370// cspell: ignore XBCT