/***************************************************************************** Mayfly_1.1A_ModemTest.ino Based on Code Written By: Sara Damiano (sdamiano@stroudcenter.org) Development Environment: PlatformIO Hardware Platform: EnviroDIY Mayfly Arduino Datalogger Software License: BSD-3. Copyright (c) 2017, Stroud Water Research Center (SWRC) and the EnviroDIY Development Team This example sketch is written for ModularSensors library version 0.33.4 This sketch is an example of logging data to an SD card and sending the data to both the EnviroDIY data portal as should be used by groups involved with The William Penn Foundation's Delaware River Watershed Initiative DISCLAIMER: THIS CODE IS PROVIDED "AS IS" - NO WARRANTY IS GIVEN. *****************************************************************************/ // ========================================================================== // Defines for the Arduino IDE // In PlatformIO, set these build flags in your platformio.ini // ========================================================================== /** Start [defines] */ #ifndef TINY_GSM_RX_BUFFER #define TINY_GSM_RX_BUFFER 64 #endif #ifndef TINY_GSM_YIELD_MS #define TINY_GSM_YIELD_MS 2 #endif /** End [defines] */ // ========================================================================== // Include the base required libraries // ========================================================================== /** Start [includes] */ #include // The base Arduino library #include // for external and pin change interrupts #include // The modular sensors library /** End [includes] */ // ========================================================================== // Data Logging Options // ========================================================================== /** Start [logging_options] */ // The name of this file const char* sketchName = "Mayfly_1.1A_ModemTest.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card const char* LoggerID = "WACH-TEST"; // How frequently (in minutes) to log data const uint8_t loggingInterval = 2; // Your logger's timezone. const int8_t timeZone = -5; // UTC // NOTE: Daylight savings time will not be applied! Please use standard time! // Set the input and output pins for the logger // NOTE: Use -1 for pins that do not apply const int32_t serialBaud = 57600; // Baud rate for debugging const int8_t greenLED = 8; // Pin for the green LED const int8_t redLED = 9; // Pin for the red LED const int8_t buttonPin = 21; // Pin for debugging mode (ie, button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. // In a SAMD system where you are using the built-in rtc, set wakePin to 1 const int8_t sdCardPwrPin = -1; // MCU SD card power pin const int8_t sdCardSSPin = 12; // SD card chip select/slave select pin const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power /** End [logging_options] */ // ========================================================================== // Wifi/Cellular Modem Options // ========================================================================== /** Start [xbee_cell_transparent] */ // For any Digi Cellular XBee's // NOTE: The u-blox based Digi XBee's (3G global and LTE-M global) // are more stable used in bypass mode (below) // The Telit based Digi XBees (LTE Cat1) can only use this mode. #include // Create a reference to the serial port for the modem HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible const int32_t modemBaud = 9600; // All XBee's use 9600 by default // Modem Pins - Describe the physical pin connection of your modem to your board // NOTE: Use -1 for pins that do not apply const int8_t modemVccPin = 18; // MCU pin controlling modem power --- Pin 18 is the power enable pin for the // bee socket on Mayfly v1.0, use -1 if using Mayfly 0.5b or if the bee socket // is constantly powered (ie you changed SJ18 on Mayfly 1.x to 3.3v) const bool useCTSforStatus = false; // Flag to use the modem CTS pin for status const int8_t modemStatusPin = 19; // MCU pin used to read modem status const int8_t modemResetPin = A5; // MCU pin connected to modem reset pin const int8_t modemSleepRqPin = 23; // MCU pin for modem sleep/wake request const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status // Network connection information const char* apn = "hologram"; // The APN for the gprs connection DigiXBee3GBypass modemXBCT(&modemSerial, modemVccPin, modemStatusPin, useCTSforStatus, modemResetPin, modemSleepRqPin, apn); // Create an extra reference to the modem by a generic name DigiXBee3GBypass modem = modemXBCT; // Create RSSI and signal strength variable pointers for the modem Variable* modemRSSI = new Modem_RSSI(&modem, "12345678-abcd-1234-ef00-1234567890ab"); Variable* modemSignalPct = new Modem_SignalPercent(&modem, "12345678-abcd-1234-ef00-1234567890ab"); /** Start [xbee_cell_transparent] */ // ========================================================================== // Using the Processor as a Sensor // ========================================================================== /** Start [processor_sensor] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); // Create sample number, battery voltage, and free RAM variable pointers for the // processor Variable* mcuBoardBatt = new ProcessorStats_Battery( &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); /** End [processor_sensor] */ // ========================================================================== // Maxim DS3231 RTC (Real Time Clock) // ========================================================================== /** Start [ds3231] */ #include // Create a DS3231 sensor object MaximDS3231 ds3231(1); // Create a temperature variable pointer for the DS3231 Variable* ds3231Temp = new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab"); /** End [ds3231] */ // Deleted Campbell Scientific Sensor - Not Applicable here // ========================================================================== // Meter Hydros 21 Conductivity, Temperature, and Depth Sensor // ========================================================================== /** Start [hydros21] */ #include const char* hydrosSDI12address = "1"; // The SDI-12 Address of the Hydros 21 const uint8_t hydrosNumberReadings = 6; // The number of readings to average const int8_t SDI12Power = sensorPowerPin; // Power pin (-1 if unconnected) const int8_t SDI12Data = 7; // The SDI12 data pin // Create a Meter Hydros 21 sensor object MeterHydros21 hydros(*hydrosSDI12address, SDI12Power, SDI12Data, hydrosNumberReadings); // Create specific conductance depth and temperature variable pointers for the Hydros21 sensor Variable* Hydros21Cond = new MeterHydros21_Cond(&hydros, "12345678-abcd-1234-ef00-1234567890ab"); Variable* Hydros21Depth = new MeterHydros21_Depth(&hydros, "12345678-abcd-1234-ef00-1234567890ab"); Variable* Hydros21Temp = new MeterHydros21_Temp(&hydros, "12345678-abcd-1234-ef00-1234567890ab"); /** End [hydros21] */ // ========================================================================== // Creating the Variable Array[s] and Filling with Variable Objects // ========================================================================== Variable* variableList_complete[] = { Hydros21Cond, Hydros21Depth, Hydros21Temp, mcuBoardBatt, ds3231Temp, modemRSSI, modemSignalPct }; // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! // Be VERY certain that they match the order of your UUID's! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Device registration and sampling feature information can be obtained after // registration at http://data.WikiWatershed.org const char *registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token const char *samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID // Count up the number of pointers in the array int variableCount_complete = sizeof(variableList_complete) / sizeof(variableList_complete[0]); // Create the VariableArray object VariableArray arrayComplete(variableCount_complete, variableList_complete); // Put only the particularly interesting variables into a second array // NOTE: We can the same variables into multiple arrays Variable* variableList_toGo[] = { Hydros21Cond, Hydros21Depth, Hydros21Temp}; // Count up the number of pointers in the array int variableCount_toGo = sizeof(variableList_toGo) / sizeof(variableList_toGo[0]); // Create the VariableArray object VariableArray arrayToGo(variableCount_toGo, variableList_toGo); /** End [variable_arrays] */ // ========================================================================== // The Logger Object[s] // ========================================================================== /** Start [loggers] */ // Create a new logger instance Logger loggerAllVars(LoggerID, loggingInterval, &arrayComplete); // Create "another" logger for the variables to go out over the internet Logger loggerToGo(LoggerID, loggingInterval, &arrayToGo); /** End [loggers] */ // ========================================================================== // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ // Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint // This is only attached to the logger with the shorter variable array #include EnviroDIYPublisher EnviroDIYPOST(loggerToGo, &modem.gsmClient, registrationToken, samplingFeature); /** End [publishers] */ // ========================================================================== // Working Functions // ========================================================================== /** Start [working_functions] */ // Flashes the LED's on the primary board void greenredflash(uint8_t numFlash = 4, uint8_t rate = 75) { for (uint8_t i = 0; i < numFlash; i++) { digitalWrite(greenLED, HIGH); digitalWrite(redLED, LOW); delay(rate); digitalWrite(greenLED, LOW); digitalWrite(redLED, HIGH); delay(rate); } digitalWrite(redLED, LOW); } // Read's the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ // ========================================================================== // Arduino Setup Function // ========================================================================== /** Start [setup] */ void setup() { // Start the primary serial connection Serial.begin(serialBaud); // Print a start-up note to the first serial port Serial.print(F("Now running ")); Serial.print(sketchName); Serial.print(F(" on Logger ")); Serial.println(LoggerID); Serial.println(); Serial.print(F("Using ModularSensors Library version ")); Serial.println(MODULAR_SENSORS_VERSION); Serial.print(F("TinyGSM Library version ")); Serial.println(TINYGSM_VERSION); Serial.println(); // Start the serial connection with the modem modemSerial.begin(modemBaud); // Set up pins for the LED's pinMode(greenLED, OUTPUT); digitalWrite(greenLED, LOW); pinMode(redLED, OUTPUT); digitalWrite(redLED, LOW); // Blink the LEDs to show the board is on and starting up greenredflash(); pinMode(20, OUTPUT); // for proper operation of the onboard flash memory // chip's ChipSelect (Mayfly v1.0 and later) // Set the timezones for the logger/data and the RTC // Logging in the given time zone Logger::setLoggerTimeZone(timeZone); // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0) Logger::setRTCTimeZone(0); // Attach the modem and information pins to the logger loggerAllVars.attachModem(modem); loggerToGo.attachModem(modem); modem.setModemLED(modemLEDPin); loggerAllVars.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin, greenLED); // Set up the connection information with EnviroDIY for both loggers // Doing this for both loggers ensures that the header of the csv will have // the tokens in it loggerAllVars.setSamplingFeatureUUID(samplingFeature); loggerToGo.setSamplingFeatureUUID(samplingFeature); // Note: Please change these battery voltages to match your battery // Set up the sensors, except at lowest battery level if (getBatteryVoltage() > 3.4) { Serial.println(F("Setting up sensors...")); arrayComplete.setupSensors(); } // Sync the clock if it isn't valid or we have battery to spare if (getBatteryVoltage() > 3.55 || !loggerAllVars.isRTCSane()) { // Synchronize the RTC with NIST // This will also set up the modem loggerAllVars.syncRTC(); } // Create the log file, adding the default header to it // Do this last so we have the best chance of getting the time correct and // all sensor names correct // Writing to the SD card can be power intensive, so if we're skipping // the sensor setup we'll skip this too. if (getBatteryVoltage() > 3.4) { Serial.println(F("Setting up file on SD card")); loggerAllVars.turnOnSDcard(true); // true = wait for card to settle after power up loggerAllVars.createLogFile(true); // true = write a new header loggerAllVars.turnOffSDcard(true); // true = wait for internal housekeeping after write } // Call the processor sleep Serial.println(F("Putting processor to sleep\n")); loggerAllVars.systemSleep(); } /** End [setup] */ // ========================================================================== // Arduino Loop Function // ========================================================================== /** Start [loop] */ // Use this long loop when you want to do something special // Because of the way alarms work on the RTC, it will wake the processor and // start the loop every minute exactly on the minute. // The processor may also be woken up by another interrupt or level change on a // pin - from a button or some other input. // The "if" statements in the loop determine what will happen - whether the // sensors update, testing mode starts, or it goes back to sleep. void loop() { // Reset the watchdog loggerAllVars.watchDogTimer.resetWatchDog(); // Assuming we were woken up by the clock, check if the current time is an // even interval of the logging interval // We're only doing anything at all if the battery is above 3.4V if (loggerAllVars.checkInterval() && getBatteryVoltage() > 3.4) { // Flag to notify that we're in already awake and logging a point Logger::isLoggingNow = true; loggerAllVars.watchDogTimer.resetWatchDog(); // Print a line to show new reading Serial.println(F("------------------------------------------")); // Turn on the LED to show we're taking a reading loggerAllVars.alertOn(); // Power up the SD Card, but skip any waits after power up loggerAllVars.turnOnSDcard(false); loggerAllVars.watchDogTimer.resetWatchDog(); // Start the stream for the modbus sensors // Because RS485 adapters tend to "steal" current from the data pins // we will explicitly start and end the serial connection in the loop. // modbusSerial.begin(9600); // Do a complete update on the "full" array. // This this includes powering all of the sensors, getting updated // values, and turing them back off. // NOTE: The wake function for each sensor should force sensor setup // to run if the sensor was not previously set up. arrayComplete.completeUpdate(); loggerAllVars.watchDogTimer.resetWatchDog(); // End the stream for the modbus sensors // Because RS485 adapters tend to "steal" current from the data pins // we will explicitly start and end the serial connection in the loop. // modbusSerial.end(); #if defined AltSoftSerial_h // Explicitly set the pin modes for the AltSoftSerial pins to make sure // they're low pinMode(5, OUTPUT); // On a Mayfly, pin D5 is the AltSoftSerial Tx pin pinMode(6, OUTPUT); // On a Mayfly, pin D6 is the AltSoftSerial Rx pin digitalWrite(5, LOW); digitalWrite(6, LOW); #endif #if defined ARDUINO_SAMD_ZERO digitalWrite(10, LOW); digitalWrite(11, LOW); #endif // Create a csv data record and save it to the log file loggerAllVars.logToSD(); loggerAllVars.watchDogTimer.resetWatchDog(); // Connect to the network // Again, we're only doing this if the battery is doing well if (getBatteryVoltage() > 3.55) { if (modem.modemWake()) { loggerAllVars.watchDogTimer.resetWatchDog(); if (modem.connectInternet()) { loggerAllVars.watchDogTimer.resetWatchDog(); // Publish data to remotes loggerToGo.publishDataToRemotes(); modem.updateModemMetadata(); loggerAllVars.watchDogTimer.resetWatchDog(); // Sync the clock at noon // NOTE: All loggers have the same clock, pick one if (Logger::markedLocalEpochTime != 0 && Logger::markedLocalEpochTime % 86400 == 43200) { Serial.println(F("Running a daily clock sync...")); loggerAllVars.setRTClock(modem.getNISTTime()); } // Disconnect from the network loggerAllVars.watchDogTimer.resetWatchDog(); modem.disconnectInternet(); } } // Turn the modem off loggerAllVars.watchDogTimer.resetWatchDog(); modem.modemSleepPowerDown(); } // Cut power from the SD card - without additional housekeeping wait loggerAllVars.turnOffSDcard(false); loggerAllVars.watchDogTimer.resetWatchDog(); // Turn off the LED loggerAllVars.alertOff(); // Print a line to show reading ended Serial.println(F("------------------------------------------\n")); // Unset flag Logger::isLoggingNow = false; } // Check if it was instead the testing interrupt that woke us up // Want to enter the testing mode for the "complete" logger so we can see // the data from _ALL_ sensors // NOTE: The testingISR attached to the button at the end of the "setup()" // function turns on the startTesting flag. So we know if that flag is set // then we want to run the testing mode function. if (Logger::startTesting) loggerAllVars.testingMode(); // Call the processor sleep // Only need to do this for one of the loggers loggerAllVars.systemSleep(); } /** End [loop] */