Sending Data to Monitor My Watershed/EnviroDIY

This sketch reduces menu_a_la_carte.ino to provide an example of how to log to https://monitormywatershed.org/ from two sensors, the BME280 and DS18. To complete the set up for logging to the web portal, the UUIDs for the site and each variable would need to be added to the sketch.

The settings for other data portals were removed from the example.

The modem settings were left unchanged because the sketch will test successfully without modem connection (wait patiently, it takes a few minutes).

This is the example you should use to deploy a logger with a modem to stream live data to the Monitor My Watershed data portal.



Unique Features of the Monitor My Watershed Example

  • A single logger publishes data to the Monitor My Watershed data portal.
  • Uses a cellular Digi XBee or XBee3

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/logging_to_MMW 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 logging_to_MMW.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 menu_a_la_carte with two external sensors logging to Monitor My Watershed
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
31lib_deps =
32 envirodiy/EnviroDIY_ModularSensors
33; ^^ Use this when working from an official release of the library
34; https://github.com/EnviroDIY/ModularSensors.git#develop
35; ^^ Use this when if you want to pull from the develop branch

The Complete Code

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