Minimizing Cellular Data Use

This is another double logger example, but in this case, both loggers are going at the same interval and the only difference between the loggers is the list of variables. There are two sets of variables, all coming from Yosemitech sensors. Because each sensor outputs temperature and we don't want to waste cellular data sending out multiple nearly identical temperature values, we have one logger that logs every possible variable result to the SD card and another logger that sends only unique results to the EnviroDIY data portal.

The modem used in this example is a SIM800 based Sodaq GPRSBee r6.

The sensors used in this example are Yosemitech Y504 Dissolved Oxygen Sensor, Yosemitech Y511 Turbidity Sensor with Wiper, Yosemitech Y514 Chlorophyll Sensor, and Yosemitech Y520 Conductivity Sensor.



Unique Features of the Data Saving Example

  • Uses AltSoftSerial to create an additional serial port for RS485 communication.
  • All variables are created and named with their parent sensor (as opposed to being created within the variable array).
  • Two different variable arrays and loggers are created and used.
    • Many of the same variables are used in both arrays.
    • Only one of the loggers publishes data.
  • 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.
    • It also shows how to forcibly set serial pins LOW at the start and end of the loop in order to prevent power loss through an RS485 adapter.

To Use this Example

Prepare and set up PlatformIO

  • Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal (http://monitormywatershed.org/)
  • Create a new PlatformIO project
  • Replace the contents of the platformio.ini for your new project with the platformio.ini file in the examples/data_saving 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 data_saving.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:
1// Logger ID, also becomes the prefix for the name of the data file on SD card
2const char *LoggerID = "XXXX";

Set the universally universal identifiers (UUID) for each variable

  • Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal (http://monitormywatershed.org/)
  • For each variable, find the dummy UUID ("12345678-abcd-1234-ef00-1234567890ab") and replace it with the real UUID for the variable.

Upload!

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

PlatformIO Configuration

1; PlatformIO Project Configuration File
2;
3; Build options: build flags, source filter
4; Upload options: custom upload port, speed and extra flags
5; Library options: dependencies, extra library storages
6; Advanced options: extra scripting
7;
8; Please visit documentation for the other options and examples
9; http://docs.platformio.org/page/projectconf.html
10
11[platformio]
12description = ModularSensors example using two "loggers" to save cellular data
13
14[env:mayfly]
15monitor_speed = 115200
16board = mayfly
17platform = atmelavr
18framework = arduino
19lib_ldf_mode = deep+
20lib_ignore =
21 RTCZero
22 Adafruit NeoPixel
23 Adafruit GFX Library
24 Adafruit SSD1306
25 Adafruit ADXL343
26 Adafruit STMPE610
27 Adafruit TouchScreen
28 Adafruit ILI9341
29build_flags =
30 -DSDI12_EXTERNAL_PCINT
31 -DNEOSWSERIAL_EXTERNAL_PCINT
32 -DMQTT_MAX_PACKET_SIZE=240
33 -DTINY_GSM_RX_BUFFER=64
34 -DTINY_GSM_YIELD_MS=2
35lib_deps =
36 envirodiy/EnviroDIY_ModularSensors
37; ^^ Use this when working from an official release of the library
38; https://github.com/EnviroDIY/ModularSensors.git#develop
39; ^^ Use this when if you want to pull from the develop branch
40 https://github.com/PaulStoffregen/AltSoftSerial.git

The Complete Code

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