Example publishing only a portion of the logged variables.
Example publishing only a portion of the logged variables.=========================================================================
See the walkthrough page for detailed instructions.
1/** =========================================================================
2 * @example{lineno} data_saving.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 publishing only a portion of the logged variables.
9 * See [the walkthrough page](@ref example_data_saving) for detailed
12 * @m_examplenavigation{example_data_saving,}
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
28// ==========================================================================
29// Include the libraries required for any data logger
30// ==========================================================================
31/** Start [includes] */
32// The Arduino library is needed for every Arduino program.
35// Include the main header for ModularSensors
36#include <ModularSensors.h>
40// ==========================================================================
41// Settings for Additional Serial Ports
42// ==========================================================================
43/** Start [serial_ports] */
44// The modem and a number of sensors communicate over UART/TTL - often called
45// "serial". "Hardware" serial ports (automatically controlled by the MCU) are
46// generally the most accurate and should be configured and used for as many
47// peripherals as possible. In some cases (ie, modbus communication) many
48// sensors can share the same serial port.
50#if !defined(ARDUINO_ARCH_SAMD) && !defined(ATMEGA2560) // For AVR boards
51// Unfortunately, most AVR boards have only one or two hardware serial ports,
52// so we'll set up three types of extra software serial ports to use
54// AltSoftSerial by Paul Stoffregen
55// (https://github.com/PaulStoffregen/AltSoftSerial) is the most accurate
56// software serial port for AVR boards. AltSoftSerial can only be used on one
57// set of pins on each board so only one AltSoftSerial port can be used. Not all
58// AVR boards are supported by AltSoftSerial.
59#include <AltSoftSerial.h>
60AltSoftSerial altSoftSerial;
61#endif // End software serial for avr boards
64#if defined(ARDUINO_ARCH_SAMD)
65#include <wiring_private.h> // Needed for SAMD pinPeripheral() function
68// Set up a 'new' UART using SERCOM1
69// The Rx will be on digital pin 11, which is SERCOM1's Pad #0
70// The Tx will be on digital pin 10, which is SERCOM1's Pad #2
71// NOTE: SERCOM1 is undefinied on a "standard" Arduino Zero and many clones,
72// but not all! Please check the variant.cpp file for you individual
73// board! Sodaq Autonomo's and Sodaq One's do NOT follow the 'standard'
75Uart Serial2(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
76// Hand over the interrupts to the sercom port
77void SERCOM1_Handler() {
82#endif // End hardware serial on SAMD21 boards
83/** End [serial_ports] */
86// ==========================================================================
87// Data Logging Options
88// ==========================================================================
89/** Start [logging_options] */
90// The name of this program file
91const char* sketchName = "data_saving.ino";
92// Logger ID, also becomes the prefix for the name of the data file on SD card
93const char* LoggerID = "XXXXX";
94// How frequently (in minutes) to log data
95const uint8_t loggingInterval = 15;
96// Your logger's timezone.
97const int8_t timeZone = -5; // Eastern Standard Time
98// NOTE: Daylight savings time will not be applied! Please use standard time!
100// Set the input and output pins for the logger
101// NOTE: Use -1 for pins that do not apply
102const int32_t serialBaud = 115200; // Baud rate for debugging
103const int8_t greenLED = 8; // Pin for the green LED
104const int8_t redLED = 9; // Pin for the red LED
105const int8_t buttonPin = 21; // Pin for debugging mode (ie, button pin)
106const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep
107// Mayfly 0.x D31 = A7
108// Set the wake pin to -1 if you do not want the main processor to sleep.
109// In a SAMD system where you are using the built-in rtc, set wakePin to 1
110const int8_t sdCardPwrPin = -1; // MCU SD card power pin
111const int8_t sdCardSSPin = 12; // SD card chip select/slave select pin
112const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power
113/** End [logging_options] */
116// ==========================================================================
117// Wifi/Cellular Modem Options
118// ==========================================================================
119/** Start [sodaq_2g_bee_r6] */
120// For the Sodaq 2GBee R6 and R7 based on the SIMCom SIM800
121// NOTE: The Sodaq GPRSBee doesn't expose the SIM800's reset pin
122#include <modems/Sodaq2GBeeR6.h>
123// Create a reference to the serial port for the modem
124HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible
125const int32_t modemBaud = 9600; // SIM800 does auto-bauding by default
127// Modem Pins - Describe the physical pin connection of your modem to your board
128// NOTE: Use -1 for pins that do not apply
129const int8_t modemVccPin = 23; // MCU pin controlling modem power
130const int8_t modemStatusPin = 19; // MCU pin used to read modem status
131const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem
132 // status (-1 if unconnected)
134// Network connection information
135const char* apn = "xxxxx"; // The APN for the gprs connection
137Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn);
138// Create an extra reference to the modem by a generic name
139Sodaq2GBeeR6 modem = modem2GB;
141// Create RSSI and signal strength variable pointers for the modem
142Variable* modemRSSI = new Modem_RSSI(&modem,
143 "12345678-abcd-1234-ef00-1234567890ab");
144Variable* modemSignalPct =
145 new Modem_SignalPercent(&modem, "12345678-abcd-1234-ef00-1234567890ab");
146/** End [sodaq_2g_bee_r6] */
149// ==========================================================================
150// Using the Processor as a Sensor
151// ==========================================================================
152/** Start [processor_sensor] */
153#include <sensors/ProcessorStats.h>
155// Create the main processor chip "sensor" - for general metadata
156const char* mcuBoardVersion = "v1.1";
157ProcessorStats mcuBoard(mcuBoardVersion);
159// Create sample number, battery voltage, and free RAM variable pointers for the
161Variable* mcuBoardBatt = new ProcessorStats_Battery(
162 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
163Variable* mcuBoardAvailableRAM = new ProcessorStats_FreeRam(
164 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
165Variable* mcuBoardSampNo = new ProcessorStats_SampleNumber(
166 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
167/** End [processor_sensor] */
170// ==========================================================================
171// Maxim DS3231 RTC (Real Time Clock)
172// ==========================================================================
174#include <sensors/MaximDS3231.h>
176// Create a DS3231 sensor object
177MaximDS3231 ds3231(1);
179// Create a temperature variable pointer for the DS3231
180Variable* ds3231Temp =
181 new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab");
185// ==========================================================================
186// Settings shared between Modbus sensors
187// ==========================================================================
188/** Start [modbus_shared] */
189// Create a reference to the serial port for modbus
190#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAMD_ZERO) || \
192HardwareSerial& modbusSerial = Serial2; // Use hardware serial if possible
194AltSoftSerial& modbusSerial = altSoftSerial; // For software serial
197// Define some pins that will be shared by all modbus sensors
198const int8_t rs485AdapterPower =
199 sensorPowerPin; // RS485 adapter power pin (-1 if unconnected)
200const int8_t modbusSensorPower = A3; // Sensor power pin
201const int8_t rs485EnablePin = -1; // Adapter RE/DE pin (-1 if not applicable)
202/** End [modbus_shared] */
205// ==========================================================================
206// Yosemitech Y504 Dissolved Oxygen Sensor
207// ==========================================================================
209#include <sensors/YosemitechY504.h>
211byte y504ModbusAddress = 0x04; // The modbus address of the Y504
212const uint8_t y504NumberReadings = 5;
213// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
216// Create a Yosemitech Y504 dissolved oxygen sensor object
217YosemitechY504 y504(y504ModbusAddress, modbusSerial, rs485AdapterPower,
218 modbusSensorPower, rs485EnablePin, y504NumberReadings);
220// Create the dissolved oxygen percent, dissolved oxygen concentration, and
221// temperature variable pointers for the Y504
223 new YosemitechY504_DOpct(&y504, "12345678-abcd-1234-ef00-1234567890ab");
225 new YosemitechY504_DOmgL(&y504, "12345678-abcd-1234-ef00-1234567890ab");
227 new YosemitechY504_Temp(&y504, "12345678-abcd-1234-ef00-1234567890ab");
231// ==========================================================================
232// Yosemitech Y511 Turbidity Sensor with Wiper
233// ==========================================================================
235#include <sensors/YosemitechY511.h>
237byte y511ModbusAddress = 0x1A; // The modbus address of the Y511
238const uint8_t y511NumberReadings = 5;
239// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
242// Create a Y511-A Turbidity sensor object
243YosemitechY511 y511(y511ModbusAddress, modbusSerial, rs485AdapterPower,
244 modbusSensorPower, rs485EnablePin, y511NumberReadings);
246// Create turbidity and temperature variable pointers for the Y511
248 new YosemitechY511_Turbidity(&y511, "12345678-abcd-1234-ef00-1234567890ab");
250 new YosemitechY511_Temp(&y511, "12345678-abcd-1234-ef00-1234567890ab");
254// ==========================================================================
255// Yosemitech Y514 Chlorophyll Sensor
256// ==========================================================================
258#include <sensors/YosemitechY514.h>
260byte y514ModbusAddress = 0x14; // The modbus address of the Y514
261const uint8_t y514NumberReadings = 5;
262// The manufacturer recommends averaging 10 readings, but we take 5 to
263// minimize power consumption
265// Create a Y514 chlorophyll sensor object
266YosemitechY514 y514(y514ModbusAddress, modbusSerial, rs485AdapterPower,
267 modbusSensorPower, rs485EnablePin, y514NumberReadings);
269// Create chlorophyll concentration and temperature variable pointers for the
271Variable* y514Chloro = new YosemitechY514_Chlorophyll(
272 &y514, "12345678-abcd-1234-ef00-1234567890ab");
274 new YosemitechY514_Temp(&y514, "12345678-abcd-1234-ef00-1234567890ab");
278// ==========================================================================
279// Yosemitech Y520 Conductivity Sensor
280// ==========================================================================
282#include <sensors/YosemitechY520.h>
284byte y520ModbusAddress = 0x20; // The modbus address of the Y520
285const uint8_t y520NumberReadings = 5;
286// The manufacturer recommends averaging 10 readings, but we take 5 to minimize
289// Create a Y520 conductivity sensor object
290YosemitechY520 y520(y520ModbusAddress, modbusSerial, rs485AdapterPower,
291 modbusSensorPower, rs485EnablePin, y520NumberReadings);
293// Create specific conductance and temperature variable pointers for the Y520
295 new YosemitechY520_Cond(&y520, "12345678-abcd-1234-ef00-1234567890ab");
297 new YosemitechY520_Temp(&y520, "12345678-abcd-1234-ef00-1234567890ab");
301// ==========================================================================
302// Creating the Variable Array[s] and Filling with Variable Objects
303// ==========================================================================
304/** Start [variable_arrays] */
305// FORM2: Fill array with already created and named variable pointers
306// We put ALL of the variable pointers into the first array
307Variable* variableList_complete[] = {
308 mcuBoardSampNo, mcuBoardBatt, mcuBoardAvailableRAM,
309 ds3231Temp, y504DOpct, y504DOmgL,
310 y504Temp, y511Turb, y511Temp,
311 y514Chloro, y514Temp, y520Cond,
312 y520Temp, modemRSSI, modemSignalPct};
313// Count up the number of pointers in the array
314int variableCount_complete = sizeof(variableList_complete) /
315 sizeof(variableList_complete[0]);
316// Create the VariableArray object
317VariableArray arrayComplete(variableCount_complete, variableList_complete);
320// Put only the particularly interesting variables into a second array
321// NOTE: We can the same variables into multiple arrays
322Variable* variableList_toGo[] = {y504DOmgL, y504Temp, y511Turb,
323 y514Chloro, y520Cond, modemRSSI};
324// Count up the number of pointers in the array
325int variableCount_toGo = sizeof(variableList_toGo) /
326 sizeof(variableList_toGo[0]);
327// Create the VariableArray object
328VariableArray arrayToGo(variableCount_toGo, variableList_toGo);
329/** End [variable_arrays] */
332// ==========================================================================
333// The Logger Object[s]
334// ==========================================================================
335/** Start [loggers] */
336// Create one new logger instance for the complete array
337Logger loggerAllVars(LoggerID, loggingInterval, &arrayComplete);
339// Create "another" logger for the variables to go out over the internet
340Logger loggerToGo(LoggerID, loggingInterval, &arrayToGo);
344// ==========================================================================
345// Creating Data Publisher[s]
346// ==========================================================================
347/** Start [publishers] */
348// Create a publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal
349// Device registration and sampling feature information can be obtained after
350// registration at https://monitormywatershed.org or https://data.envirodiy.org
351const char* registrationToken =
352 "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token
353const char* samplingFeature =
354 "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID
356// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint
357// This is only attached to the logger with the shorter variable array
358#include <publishers/EnviroDIYPublisher.h>
359EnviroDIYPublisher EnviroDIYPOST(loggerToGo, &modem.gsmClient,
360 registrationToken, samplingFeature);
361/** End [publishers] */
364// ==========================================================================
366// ==========================================================================
367/** Start [working_functions] */
368// Flashes the LED's on the primary board
369void greenredflash(uint8_t numFlash = 4, uint8_t rate = 75) {
370 for (uint8_t i = 0; i < numFlash; i++) {
371 digitalWrite(greenLED, HIGH);
372 digitalWrite(redLED, LOW);
374 digitalWrite(greenLED, LOW);
375 digitalWrite(redLED, HIGH);
378 digitalWrite(redLED, LOW);
381// Reads the battery voltage
382// NOTE: This will actually return the battery level from the previous update!
383float getBatteryVoltage() {
384 if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update();
385 return mcuBoard.sensorValues[0];
387/** End [working_functions] */
390// ==========================================================================
391// Arduino Setup Function
392// ==========================================================================
395// Wait for USB connection to be established by PC
396// NOTE: Only use this when debugging - if not connected to a PC, this
397// could prevent the script from starting
398#if defined(SERIAL_PORT_USBVIRTUAL)
399 while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000)) {
404 // Start the primary serial connection
405 Serial.begin(serialBaud);
407 // Print a start-up note to the first serial port
408 Serial.print(F("Now running "));
409 Serial.print(sketchName);
410 Serial.print(F(" on Logger "));
411 Serial.println(LoggerID);
414 Serial.print(F("Using ModularSensors Library version "));
415 Serial.println(MODULAR_SENSORS_VERSION);
417 // Start the serial connection with the modem
418 modemSerial.begin(modemBaud);
420 // Start the stream for the modbus sensors; all currently supported modbus
421 // sensors use 9600 baud
422 modbusSerial.begin(9600);
424// Assign pins SERCOM functionality for SAMD boards
425// NOTE: This must happen *after* the various serial.begin statements
426#if defined(ARDUINO_ARCH_SAMD)
427#ifndef ENABLE_SERIAL2
428 pinPeripheral(10, PIO_SERCOM); // Serial2 Tx/Dout = SERCOM1 Pad #2
429 pinPeripheral(11, PIO_SERCOM); // Serial2 Rx/Din = SERCOM1 Pad #0
432 // Set up pins for the LED's
433 pinMode(greenLED, OUTPUT);
434 digitalWrite(greenLED, LOW);
435 pinMode(redLED, OUTPUT);
436 digitalWrite(redLED, LOW);
437 // Blink the LEDs to show the board is on and starting up
440 // Set the timezones for the logger/data and the RTC
441 // Logging in the given time zone
442 Logger::setLoggerTimeZone(timeZone);
443 // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0)
444 Logger::setRTCTimeZone(0);
446 // Attach the same modem to both loggers
447 // It is only needed for the logger that will be sending out data, but
448 // attaching it to both allows either logger to control NIST synchronization
449 loggerAllVars.attachModem(modem);
450 loggerToGo.attachModem(modem);
451 modem.setModemLED(modemLEDPin);
452 loggerAllVars.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin,
455 // Set up the connection information with EnviroDIY for both loggers
456 // Doing this for both loggers ensures that the header of the csv will have
458 loggerAllVars.setSamplingFeatureUUID(samplingFeature);
459 loggerToGo.setSamplingFeatureUUID(samplingFeature);
461 // Note: Please change these battery voltages to match your battery
463 // Set up the sensors, except at lowest battery level
464 // Like with the logger, because the variables are duplicated in the arrays,
465 // we only need to do this for the complete array.
466 if (getBatteryVoltage() > 3.4) {
467 Serial.println(F("Setting up sensors..."));
468 arrayComplete.setupSensors();
471 // Sync the clock if it isn't valid or we have battery to spare
472 if (getBatteryVoltage() > 3.55 || !loggerAllVars.isRTCSane()) {
473 // Synchronize the RTC with NIST
474 // This will also set up the modem
475 loggerAllVars.syncRTC();
478 // Create the log file, adding the default header to it
479 // Do this last so we have the best chance of getting the time correct and
480 // all sensor names correct
481 // Writing to the SD card can be power intensive, so if we're skipping
482 // the sensor setup we'll skip this too.
483 if (getBatteryVoltage() > 3.4) {
484 loggerAllVars.turnOnSDcard(
485 true); // true = wait for card to settle after power up
486 loggerAllVars.createLogFile(true); // true = write a new header
487 loggerAllVars.turnOffSDcard(
488 true); // true = wait for internal housekeeping after write
491 // Call the processor sleep
492 Serial.println(F("Putting processor to sleep"));
493 loggerAllVars.systemSleep();
498// ==========================================================================
499// Arduino Loop Function
500// ==========================================================================
502// Use this long loop when you want to do something special
503// Because of the way alarms work on the RTC, it will wake the processor and
504// start the loop every minute exactly on the minute.
505// The processor may also be woken up by another interrupt or level change on a
506// pin - from a button or some other input.
507// The "if" statements in the loop determine what will happen - whether the
508// sensors update, testing mode starts, or it goes back to sleep.
510 // Reset the watchdog
511 loggerAllVars.watchDogTimer.resetWatchDog();
513 // Assuming we were woken up by the clock, check if the current time is an
514 // even interval of the logging interval
515 // We're only doing anything at all if the battery is above 3.4V
516 if (loggerAllVars.checkInterval() && getBatteryVoltage() > 3.4) {
517 // Flag to notify that we're in already awake and logging a point
518 Logger::isLoggingNow = true;
519 loggerAllVars.watchDogTimer.resetWatchDog();
521 // Print a line to show new reading
522 Serial.println(F("------------------------------------------"));
523 // Turn on the LED to show we're taking a reading
524 loggerAllVars.alertOn();
525 // Power up the SD Card, but skip any waits after power up
526 loggerAllVars.turnOnSDcard(false);
527 loggerAllVars.watchDogTimer.resetWatchDog();
529 // Start the stream for the modbus sensors
530 // Because RS485 adapters tend to "steal" current from the data pins
531 // we will explicitly start and end the serial connection in the loop.
532 modbusSerial.begin(9600);
534 // Do a complete update on the "full" array.
535 // This this includes powering all of the sensors, getting updated
536 // values, and turing them back off.
537 // NOTE: The wake function for each sensor should force sensor setup
538 // to run if the sensor was not previously set up.
539 arrayComplete.completeUpdate();
540 loggerAllVars.watchDogTimer.resetWatchDog();
542 // End the stream for the modbus sensors
543 // Because RS485 adapters tend to "steal" current from the data pins
544 // we will explicitly start and end the serial connection in the loop.
547#if defined(AltSoftSerial_h)
548 // Explicitly set the pin modes for the AltSoftSerial pins to make sure
550 pinMode(5, OUTPUT); // On a Mayfly, pin D5 is the AltSoftSerial Tx pin
551 pinMode(6, OUTPUT); // On a Mayfly, pin D6 is the AltSoftSerial Rx pin
552 digitalWrite(5, LOW);
553 digitalWrite(6, LOW);
556#if defined(ARDUINO_SAMD_ZERO)
557 digitalWrite(10, LOW);
558 digitalWrite(11, LOW);
561 // Create a csv data record and save it to the log file
562 loggerAllVars.logToSD();
563 loggerAllVars.watchDogTimer.resetWatchDog();
565 // Connect to the network
566 // Again, we're only doing this if the battery is doing well
567 if (getBatteryVoltage() > 3.55) {
568 if (modem.modemWake()) {
569 loggerAllVars.watchDogTimer.resetWatchDog();
570 if (modem.connectInternet()) {
571 loggerAllVars.watchDogTimer.resetWatchDog();
572 // Publish data to remotes
573 loggerToGo.publishDataToRemotes();
574 modem.updateModemMetadata();
576 loggerAllVars.watchDogTimer.resetWatchDog();
577 // Sync the clock at noon
578 // NOTE: All loggers have the same clock, pick one
579 if (Logger::markedLocalEpochTime != 0 &&
580 Logger::markedLocalEpochTime % 86400 == 43200) {
581 Serial.println(F("Running a daily clock sync..."));
582 loggerAllVars.setRTClock(modem.getNISTTime());
585 // Disconnect from the network
586 loggerAllVars.watchDogTimer.resetWatchDog();
587 modem.disconnectInternet();
590 // Turn the modem off
591 loggerAllVars.watchDogTimer.resetWatchDog();
592 modem.modemSleepPowerDown();
595 // Cut power from the SD card - without additional housekeeping wait
596 loggerAllVars.turnOffSDcard(false);
597 loggerAllVars.watchDogTimer.resetWatchDog();
599 loggerAllVars.alertOff();
600 // Print a line to show reading ended
601 Serial.println(F("------------------------------------------\n"));
604 Logger::isLoggingNow = false;
607 // Check if it was instead the testing interrupt that woke us up
608 // Want to enter the testing mode for the "complete" logger so we can see
609 // the data from _ALL_ sensors
610 // NOTE: The testingISR attached to the button at the end of the "setup()"
611 // function turns on the startTesting flag. So we know if that flag is set
612 // then we want to run the testing mode function.
613 if (Logger::startTesting) loggerAllVars.testingMode();
615 // Call the processor sleep
616 // Only need to do this for one of the loggers
617 loggerAllVars.systemSleep();