ModularSensors > Pages > ModularSensors > Examples Using ModularSensors > Calculations and Complex Logging > Multiple Time Intervals

Multiple Time Intervals

This is a more complicated example using two different logger instances to log data at two different intervals, in this case, an AM3215 logging every minute, while checking the battery voltage only every 5 minutes. This showcases both how to use two different logging instances and how to use some of the functions to set up your own logging loop rather than using the logData() function.



Unique Features of the Double Logger Example

  • Two different variable arrays and loggers are created and used.
    • The Variables for the arrays are created within the array.
    • There is no variable overlap between the two arrays or loggers.
  • The loop function is expanded into its components rather than using the logData functions.
    • This demonstrates how to write the loop out, without using the logData functions.
    • This shows which functions are required for each of the two loggers and which can be used in common.

To Use this Example

Prepare and set up PlatformIO

  • Create a new PlatformIO project
  • Replace the contents of the platformio.ini for your new project with the platformio.ini file in the examples/double_logger 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 double_logger.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:
// Logger ID, also becomes the prefix for the name of the data file on SD card
const char *LoggerID = "XXXX";

Upload!

  • Test everything at home before deploying out in the wild!

PlatformIO Configuration

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html

[platformio]
description = ModularSensors example using two "loggers" to log at different intervals

[env:mayfly]
monitor_speed = 115200
board = mayfly
platform = atmelavr
framework = arduino
lib_ldf_mode = deep+
lib_ignore =
    RTCZero
    Adafruit NeoPixel
    Adafruit GFX Library
    Adafruit SSD1306
    Adafruit ADXL343
    Adafruit STMPE610
    Adafruit TouchScreen
    Adafruit ILI9341
build_flags =
    -DSDI12_EXTERNAL_PCINT
    -DNEOSWSERIAL_EXTERNAL_PCINT
    -DMQTT_MAX_PACKET_SIZE=240
    -DTINY_GSM_RX_BUFFER=64
    -DTINY_GSM_YIELD_MS=2
lib_deps =
    envirodiy/EnviroDIY_ModularSensors
;  ^^ Use this when working from an official release of the library
;    https://github.com/EnviroDIY/ModularSensors.git#develop
;  ^^ Use this when if you want to pull from the develop branch

The Complete Code

/** =========================================================================
 * @file double_logger.ino
 * @brief Example logging at two different timing intervals
 *
 * @author Sara Geleskie Damiano <sdamiano@stroudcenter.org>
 * @copyright (c) 2017-2022 Stroud Water Research Center (SWRC)
 *                          and the EnviroDIY Development Team
 *            This example is published under the BSD-3 license.
 *
 * Build Environment: Visual Studios Code with PlatformIO
 * Hardware Platform: EnviroDIY Mayfly Arduino Datalogger
 *
 * DISCLAIMER:
 * THIS CODE IS PROVIDED "AS IS" - NO WARRANTY IS GIVEN.
 * ======================================================================= */

// ==========================================================================
//  Defines for TinyGSM
// ==========================================================================
/** 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 libraries required for any data logger
// ==========================================================================
/** Start [includes] */
// The Arduino library is needed for every Arduino program.
#include <Arduino.h>

// EnableInterrupt is used by ModularSensors for external and pin change
// interrupts and must be explicitly included in the main program.
#include <EnableInterrupt.h>

// Include the main header for ModularSensors
#include <ModularSensors.h>
/** End [includes] */


// ==========================================================================
//  Data Logging Options
// ==========================================================================
/** Start [logging_options] */
// The name of this program file
const char* sketchName = "double_logger.ino";
// Logger ID - we're only using one logger ID for both "loggers"
const char* LoggerID = "XXXXX";
// The TWO filenames for the different logging intervals
const char* FileName5min = "Logger_5MinuteInterval.csv";
const char* FileName1min = "Logger_1MinuteInterval.csv";
// Your logger's timezone.
const int8_t timeZone = -5;  // Eastern Standard Time
// 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 = 115200;  // 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_wifi] */
// For the Digi Wifi XBee (S6B)
#include <modems/DigiXBeeWifi.h>
// 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    = -2;    // MCU pin controlling modem power
const int8_t modemStatusPin = 19;    // MCU pin used to read modem status
const bool useCTSforStatus  = true;  // Flag to use the modem CTS pin for status
const int8_t modemResetPin  = 20;    // 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 (-1 if unconnected)

// Network connection information
const char* wifiId  = "xxxxx";  // WiFi access point, unnecessary for GPRS
const char* wifiPwd = "xxxxx";  // WiFi password, unnecessary for GPRS

DigiXBeeWifi modemXBWF(&modemSerial, modemVccPin, modemStatusPin,
                       useCTSforStatus, modemResetPin, modemSleepRqPin, wifiId,
                       wifiPwd);
// Create an extra reference to the modem by a generic name
DigiXBeeWifi modem = modemXBWF;
/** End [xbee_wifi] */


// ==========================================================================
//  Using the Processor as a Sensor
// ==========================================================================
/** Start [processor_sensor] */
#include <sensors/ProcessorStats.h>

// Create the main processor chip "sensor" - for general metadata
const char*    mcuBoardVersion = "v1.1";
ProcessorStats mcuBoard(mcuBoardVersion);
/** End [processor_sensor] */


// ==========================================================================
//  Maxim DS3231 RTC (Real Time Clock)
// ==========================================================================
/** Start [ds3231] */
#include <sensors/MaximDS3231.h>

// Create a DS3231 sensor object
MaximDS3231 ds3231(1);
/** End [ds3231] */


// ==========================================================================
//  AOSong AM2315 Digital Humidity and Temperature Sensor
// ==========================================================================
/** Start [ao_song_am2315] */
#include <sensors/AOSongAM2315.h>

const int8_t I2CPower = sensorPowerPin;  // Power pin (-1 if unconnected)

// Create and return the AOSong AM2315 sensor object
AOSongAM2315 am2315(I2CPower);
/** End [ao_song_am2315] */


// ==========================================================================
//  Creating the Variable Array[s] and Filling with Variable Objects
// ==========================================================================
/** Start [variable_arrays] */
// The variables to record at 1 minute intervals
Variable* variableList_at1min[] = {new AOSongAM2315_Humidity(&am2315),
                                   new AOSongAM2315_Temp(&am2315)};
// Count up the number of pointers in the 1-minute array
int variableCount1min = sizeof(variableList_at1min) /
    sizeof(variableList_at1min[0]);
// Create the 1-minute VariableArray object
VariableArray array1min;

// The variables to record at 5 minute intervals
Variable* variableList_at5min[] = {new MaximDS3231_Temp(&ds3231),
                                   new ProcessorStats_Battery(&mcuBoard),
                                   new ProcessorStats_FreeRam(&mcuBoard)};
// Count up the number of pointers in the 5-minute array
int variableCount5min = sizeof(variableList_at5min) /
    sizeof(variableList_at5min[0]);
// Create the 5-minute VariableArray object
VariableArray array5min;
/** End [variable_arrays] */


// ==========================================================================
//  The Logger Object[s]
// ==========================================================================
/** Start [loggers] */
// Create the 1-minute  logger instance
Logger logger1min;

// Create the 5-minute  logger instance
Logger logger5min;
/** End [loggers] */


// ==========================================================================
//  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);
}
/** 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();

    // 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);

    // Begin the variable array[s], logger[s], and publisher[s]
    array1min.begin(variableCount1min, variableList_at1min);
    array5min.begin(variableCount5min, variableList_at5min);
    logger1min.begin(LoggerID, 1, &array1min);
    logger5min.begin(LoggerID, 5, &array5min);
    logger1min.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin,
                             greenLED);
    logger5min.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin,
                             greenLED);

    // Turn on the modem
    modem.setModemLED(modemLEDPin);

    // Set up the sensors (do this directly on the VariableArray)
    array1min.setupSensors();
    array5min.setupSensors();

    // Print out the current time
    Serial.print(F("Current RTC time is: "));
    Serial.println(Logger::formatDateTime_ISO8601(Logger::getNowUTCEpoch()));
    Serial.print(F("Current localized logger time is: "));
    Serial.println(Logger::formatDateTime_ISO8601(Logger::getNowLocalEpoch()));
    // Connect to the network
    if (modem.connectInternet()) {
        // Synchronize the RTC
        logger1min.setRTClock(modem.getNISTTime());
        modem.updateModemMetadata();
        // Disconnect from the network
        modem.disconnectInternet();
    }
    // Turn off the modem
    modem.modemSleepPowerDown();

    // Give the loggers different file names
    // If we wanted to auto-generate the file name, that could also be done by
    // not calling this function, but in that case if both "loggers" have the
    // same logger id, they will end up with the same filename
    logger1min.setFileName(FileName1min);
    logger5min.setFileName(FileName5min);

    // Setup the logger files.  Specifying true will put a default header at
    // on to the file when it's created.
    // Because we've already called setFileName, we do not need to specify the
    // file name for this function.
    logger1min.turnOnSDcard(
        true);  // true = wait for card to settle after power up
    logger1min.createLogFile(true);  // true = write a new header
    logger5min.createLogFile(true);  // true = write a new header
    logger1min.turnOffSDcard(
        true);  // true = wait for internal housekeeping after write

    Serial.println(F("Logger setup finished!\n"));
    Serial.println(F("------------------------------------------"));
    Serial.println();

    // Call the processor sleep
    // Only need to do this for one of the loggers
    logger1min.systemSleep();
}
/** End [setup] */


// ==========================================================================
//  Arduino Loop Function
// ==========================================================================
/** Start [loop] */
// 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() {
    // Check if the current time is an even interval of the logging interval
    // For whichever logger we call first, use the checkInterval() function.
    if (logger1min.checkInterval()) {
        // Print a line to show new reading
        Serial.println(F("--------------------->111<---------------------"));
        // Turn on the LED to show we're taking a reading
        digitalWrite(greenLED, HIGH);

        // Send power to all of the sensors (do this directly on the
        // VariableArray)
        Serial.print(F("Powering sensors...\n"));
        array1min.sensorsPowerUp();
        logger1min.watchDogTimer.resetWatchDog();
        // Wake up all of the sensors (do this directly on the VariableArray)
        Serial.print(F("Waking sensors...\n"));
        array1min.sensorsWake();
        logger1min.watchDogTimer.resetWatchDog();
        // Update the values from all attached sensors (do this directly on the
        // VariableArray)
        Serial.print(F("Updating sensor values...\n"));
        array1min.updateAllSensors();
        logger1min.watchDogTimer.resetWatchDog();
        // Put sensors to sleep (do this directly on the VariableArray)
        Serial.print(F("Putting sensors back to sleep...\n"));
        array1min.sensorsSleep();
        logger1min.watchDogTimer.resetWatchDog();
        // Cut sensor power (do this directly on the VariableArray)
        Serial.print(F("Cutting sensor power...\n"));
        array1min.sensorsPowerDown();
        logger1min.watchDogTimer.resetWatchDog();

        // Stream the csv data to the SD card
        logger1min.turnOnSDcard(true);
        logger1min.logToSD();
        logger1min.turnOffSDcard(true);
        logger1min.watchDogTimer.resetWatchDog();

        // Turn off the LED
        digitalWrite(greenLED, LOW);
        // Print a line to show reading ended
        Serial.println(F("---------------------<111>---------------------\n"));
    }
    // Check if the already marked time is an even interval of the logging
    // interval For logger[s] other than the first one, use the
    // checkMarkedInterval() function.
    if (logger5min.checkMarkedInterval()) {
        // Print a line to show new reading
        Serial.println(F("--------------------->555<---------------------"));
        // Turn on the LED to show we're taking a reading
        digitalWrite(redLED, HIGH);

        // Send power to all of the sensors (do this directly on the
        // VariableArray)
        Serial.print(F("Powering sensors...\n"));
        array5min.sensorsPowerUp();
        logger1min.watchDogTimer.resetWatchDog();
        // Wake up all of the sensors (do this directly on the VariableArray)
        Serial.print(F("Waking sensors...\n"));
        array5min.sensorsWake();
        logger1min.watchDogTimer.resetWatchDog();
        // Update the values from all attached sensors (do this directly on the
        // VariableArray)
        Serial.print(F("Updating sensor values...\n"));
        array5min.updateAllSensors();
        logger1min.watchDogTimer.resetWatchDog();
        // Put sensors to sleep (do this directly on the VariableArray)
        Serial.print(F("Putting sensors back to sleep...\n"));
        array5min.sensorsSleep();
        logger1min.watchDogTimer.resetWatchDog();
        // Cut sensor power (do this directly on the VariableArray)
        Serial.print(F("Cutting sensor power...\n"));
        array5min.sensorsPowerDown();
        logger1min.watchDogTimer.resetWatchDog();

        // Stream the csv data to the SD card
        logger5min.turnOnSDcard(true);
        logger5min.logToSD();
        logger5min.turnOffSDcard(true);
        logger1min.watchDogTimer.resetWatchDog();

        // Turn off the LED
        digitalWrite(redLED, LOW);
        // Print a line to show reading ended
        Serial.println(F("--------------------<555>---------------------\n"));
    }
    // Once a day, at noon, sync the clock
    if (Logger::markedLocalEpochTime % 86400 == 43200) {
        // Turn on the modem
        modem.modemWake();
        // Connect to the network
        if (modem.connectInternet()) {
            // Synchronize the RTC
            logger1min.setRTClock(modem.getNISTTime());
            // Disconnect from the network
            modem.disconnectInternet();
        }
        // Turn off the modem
        modem.modemSleepPowerDown();
    }

    // Call the processor sleep
    // Only need to do this for one of the loggers
    logger1min.systemSleep();
}
/** End [loop] */