Calculating Results based on Measured Values

This example demonstrates how to work with calculated variables and calculates water depth by correcting the total pressure measured by a MeaSpec MS5803 with the atmospheric pressure measured by a Bosch BME280 environmental sensor and the temperature measured by a Maxim DS18 temperature probe.

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

The sensors used in this example are a Maxim DS18 temperature probe, a Bosch BME280 environmental sensor, and a Measurement Specialties MS5803-14BA pressure sensor.



Unique Features of the Barometric Correction Example

  • All variables are created and named with their parent sensor (as opposed to being created within the variable array).
  • There are multiple calculated variables created and used.

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/baro_rho_correction 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 baro_rho_correction.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 with a calculated variable
13
14
15[env:mayfly]
16monitor_speed = 115200
17board = mayfly
18platform = atmelavr
19framework = arduino
20lib_ldf_mode = deep+
21lib_ignore =
22 RTCZero
23 Adafruit NeoPixel
24 Adafruit GFX Library
25 Adafruit SSD1306
26 Adafruit ADXL343
27 Adafruit STMPE610
28 Adafruit TouchScreen
29 Adafruit ILI9341
30build_flags =
31 -DSDI12_EXTERNAL_PCINT
32 -DNEOSWSERIAL_EXTERNAL_PCINT
33 -DMQTT_MAX_PACKET_SIZE=240
34 -DTINY_GSM_RX_BUFFER=64
35 -DTINY_GSM_YIELD_MS=2
36lib_deps =
37 envirodiy/EnviroDIY_ModularSensors
38; ^^ Use this when working from an official release of the library
39; https://github.com/EnviroDIY/ModularSensors.git#develop
40; ^^ Use this when if you want to pull from the develop branch

The Complete Code

1/** =========================================================================
2 * @example{lineno} baro_rho_correction.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 demonstrating calculated variables.
8 *
9 * See [the walkthrough page](@ref example_baro_rho) for detailed instructions.
10 *
11 * @m_examplenavigation{example_baro_rho,}
12 * ======================================================================= */
13
14// ==========================================================================
15// Defines for TinyGSM
16// NOTE: These only work with 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// Data Logging Options
42// ==========================================================================
43/** Start [logging_options] */
44// The name of this program file
45const char* sketchName = "baro_rho_correction.ino";
46// Logger ID, also becomes the prefix for the name of the data file on SD card
47const char* LoggerID = "XXXXX";
48// How frequently (in minutes) to log data
49const uint8_t loggingInterval = 15;
50// Your logger's timezone.
51const int8_t timeZone = -5; // Eastern Standard Time
52// NOTE: Daylight savings time will not be applied! Please use standard time!
53
54// Set the input and output pins for the logger
55// NOTE: Use -1 for pins that do not apply
56const int32_t serialBaud = 115200; // Baud rate for debugging
57const int8_t greenLED = 8; // Pin for the green LED
58const int8_t redLED = 9; // Pin for the red LED
59const int8_t buttonPin = 21; // Pin for debugging mode (ie, button pin)
60const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep
61// Mayfly 0.x D31 = A7
62// Set the wake pin to -1 if you do not want the main processor to sleep.
63// In a SAMD system where you are using the built-in rtc, set wakePin to 1
64const int8_t sdCardPwrPin = -1; // MCU SD card power pin
65const int8_t sdCardSSPin = 12; // SD card chip select/slave select pin
66const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power
67/** End [logging_options] */
68
69
70// ==========================================================================
71// Wifi/Cellular Modem Options
72// ==========================================================================
73/** Start [sodaq_2g_bee_r6] */
74// For the Sodaq 2GBee R6 and R7 based on the SIMCom SIM800
75// NOTE: The Sodaq GPRSBee doesn't expose the SIM800's reset pin
76#include <modems/Sodaq2GBeeR6.h>
77
78// Create a reference to the serial port for the modem
79HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible
80
81const int32_t modemBaud = 9600; // SIM800 does auto-bauding by default
82
83// Modem Pins - Describe the physical pin connection of your modem to your board
84// NOTE: Use -1 for pins that do not apply
85// Example pins are for a Sodaq GPRSBee R6 or R7 with a Mayfly
86const int8_t modemVccPin = 23; // MCU pin controlling modem power
87const int8_t modemStatusPin = 19; // MCU pin used to read modem status
88const int8_t modemResetPin = -1; // MCU pin connected to modem reset pin
89const int8_t modemSleepRqPin = -1; // MCU pin for modem sleep/wake request
90const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem
91 // status
92
93// Network connection information
94const char* apn = "xxxxx"; // APN for GPRS connection
95
96// Create the modem object
97Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn);
98// Create an extra reference to the modem by a generic name
99Sodaq2GBeeR6 modem = modem2GB;
100
101// Create RSSI and signal strength variable pointers for the modem
102Variable* modemRSSI =
103 new Modem_RSSI(&modem, "12345678-abcd-1234-ef00-1234567890ab", "RSSI");
104Variable* modemSignalPct = new Modem_SignalPercent(
105 &modem, "12345678-abcd-1234-ef00-1234567890ab", "signalPercent");
106/** End [sodaq_2g_bee_r6] */
107
108
109// ==========================================================================
110// Using the Processor as a Sensor
111// ==========================================================================
112/** Start [processor_sensor] */
113#include <sensors/ProcessorStats.h>
114
115// Create the main processor chip "sensor" - for general metadata
116const char* mcuBoardVersion = "v1.1";
117ProcessorStats mcuBoard(mcuBoardVersion);
118
119// Create sample number, battery voltage, and free RAM variable pointers for the
120// processor
121Variable* mcuBoardBatt = new ProcessorStats_Battery(
122 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
123Variable* mcuBoardAvailableRAM = new ProcessorStats_FreeRam(
124 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
125Variable* mcuBoardSampNo = new ProcessorStats_SampleNumber(
126 &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab");
127/** End [processor_sensor] */
128
129
130// ==========================================================================
131// Maxim DS3231 RTC (Real Time Clock)
132// ==========================================================================
133/** Start [ds3231] */
134#include <sensors/MaximDS3231.h>
135
136// Create a DS3231 sensor object
137MaximDS3231 ds3231(1);
138
139// Create a temperature variable pointer for the DS3231
140Variable* ds3231Temp =
141 new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab");
142/** End [ds3231] */
143
144
145// ==========================================================================
146// Bosch BME280 Environmental Sensor
147// ==========================================================================
148/** Start [bme280] */
149#include <sensors/BoschBME280.h>
150
151const int8_t I2CPower = sensorPowerPin; // Power pin (-1 if unconnected)
152uint8_t BMEi2c_addr = 0x77;
153// The BME280 can be addressed either as 0x77 (Adafruit default) or 0x76 (Grove
154// default) Either can be physically mofidied for the other address
155
156// Create a Bosch BME280 sensor object
157BoschBME280 bme280(I2CPower, BMEi2c_addr);
158
159// Create four variable pointers for the BME280
160Variable* bme280Humid =
161 new BoschBME280_Humidity(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
162Variable* bme280Temp =
163 new BoschBME280_Temp(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
164Variable* bme280Press =
165 new BoschBME280_Pressure(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
166Variable* bme280Alt =
167 new BoschBME280_Altitude(&bme280, "12345678-abcd-1234-ef00-1234567890ab");
168/** End [bme280] */
169
170
171// ==========================================================================
172// Maxim DS18 One Wire Temperature Sensor
173// ==========================================================================
174/** Start [ds18] */
175#include <sensors/MaximDS18.h>
176
177const int8_t OneWirePower = sensorPowerPin; // Power pin (-1 if unconnected)
178const int8_t OneWireBus = 4; // OneWire Bus Pin (-1 if unconnected)
179
180// Create a Maxim DS18 sensor object (use this form for a single sensor on bus
181// with an unknown address)
182MaximDS18 ds18(OneWirePower, OneWireBus);
183
184// Create a temperature variable pointer for the DS18
185Variable* ds18Temp = new MaximDS18_Temp(&ds18,
186 "12345678-abcd-1234-ef00-1234567890ab");
187/** End [ds18] */
188
189
190// ==========================================================================
191// Measurement Specialties MS5803-14BA pressure sensor
192// ==========================================================================
193/** Start [ms5803] */
194#include <sensors/MeaSpecMS5803.h>
195
196const uint8_t MS5803i2c_addr =
197 0x76; // The MS5803 can be addressed either as 0x76 (default) or 0x77
198const int16_t MS5803maxPressure =
199 14; // The maximum pressure measurable by the specific MS5803 model
200const uint8_t MS5803ReadingsToAvg = 1;
201
202// Create a MeaSpec MS5803 pressure and temperature sensor object
203MeaSpecMS5803 ms5803(I2CPower, MS5803i2c_addr, MS5803maxPressure,
204 MS5803ReadingsToAvg);
205
206// Create pressure and temperature variable pointers for the MS5803
207Variable* ms5803Press =
208 new MeaSpecMS5803_Pressure(&ms5803, "12345678-abcd-1234-ef00-1234567890ab");
209Variable* ms5803Temp =
210 new MeaSpecMS5803_Temp(&ms5803, "12345678-abcd-1234-ef00-1234567890ab");
211/** End [ms5803] */
212
213
214// ==========================================================================
215// Calculated Variable[s]
216// ==========================================================================
217/** Start [calculated_pressure] */
218// Create the function to calculate the water pressure
219// Water pressure = pressure from MS5803 (water+baro) - pressure from BME280
220// (baro) The MS5803 reports pressure in millibar, the BME280 in pascal 1 pascal
221// = 0.01 mbar
222float calculateWaterPressure(void) {
223 float totalPressureFromMS5803 = ms5803Press->getValue();
224 float baroPressureFromBME280 = bme280Press->getValue();
225 float waterPressure = totalPressureFromMS5803 -
226 (baroPressureFromBME280)*0.01;
227 if (totalPressureFromMS5803 == -9999 || baroPressureFromBME280 == -9999) {
228 waterPressure = -9999;
229 }
230 // Serial.print(F("Water pressure is ")); // for debugging
231 // Serial.println(waterPressure); // for debugging
232 return waterPressure;
233}
234// Properties of the calculated water pressure variable
235const char* waterPressureVarName =
236 "pressureGauge"; // This must be a value from
237 // http://vocabulary.odm2.org/variablename/
238const char* waterPressureVarUnit =
239 "millibar"; // This must be a value from http://vocabulary.odm2.org/units/
240int waterPressureVarResolution = 3;
241const char* waterPressureUUID = "12345678-abcd-1234-ef00-1234567890ab";
242const char* waterPressureVarCode = "CorrectedPressure";
243// Create the calculated water pressure variable objects and return a variable
244// pointer to it
245Variable* calcWaterPress = new Variable(
246 calculateWaterPressure, waterPressureVarResolution, waterPressureVarName,
247 waterPressureVarUnit, waterPressureVarCode, waterPressureUUID);
248/** End [calculated_pressure] */
249
250/** Start [calculated_uncorrected_depth] */
251// Create the function to calculate the "raw" water depth
252// For this, we're using the conversion between mbar and mm pure water at 4°C
253// This calculation gives a final result in mm of water
254float calculateWaterDepthRaw(void) {
255 float waterDepth = calculateWaterPressure() * 10.1972;
256 if (calculateWaterPressure() == -9999) waterDepth = -9999;
257 // Serial.print(F("'Raw' water depth is ")); // for debugging
258 // Serial.println(waterDepth); // for debugging
259 return waterDepth;
260}
261// Properties of the calculated water depth variable
262const char* waterDepthVarName =
263 "waterDepth"; // This must be a value from
264 // http://vocabulary.odm2.org/variablename/
265const char* waterDepthVarUnit =
266 "millimeter"; // This must be a value from
267 // http://vocabulary.odm2.org/units/
268int waterDepthVarResolution = 3;
269const char* waterDepthUUID = "12345678-abcd-1234-ef00-1234567890ab";
270const char* waterDepthVarCode = "CalcDepth";
271// Create the calculated raw water depth variable objects and return a variable
272// pointer to it
273Variable* calcRawDepth = new Variable(
274 calculateWaterDepthRaw, waterDepthVarResolution, waterDepthVarName,
275 waterDepthVarUnit, waterDepthVarCode, waterDepthUUID);
276/** End [calculated_uncorrected_depth] */
277
278/** Start [calculated_corrected_depth] */
279// Create the function to calculate the water depth after correcting water
280// density for temperature This calculation gives a final result in mm of water
281float calculateWaterDepthTempCorrected(void) {
282 const float gravitationalConstant =
283 9.80665; // m/s2, meters per second squared
284 // First get water pressure in Pa for the calculation: 1 mbar = 100 Pa
285 float waterPressurePa = 100 * calculateWaterPressure();
286 float waterTempertureC = ms5803Temp->getValue();
287 // Converting water depth for the changes of pressure with depth
288 // Water density (kg/m3) from equation 6 from
289 // JonesHarris1992-NIST-DensityWater.pdf
290 float waterDensity = +999.84847 + 6.337563e-2 * waterTempertureC -
291 8.523829e-3 * pow(waterTempertureC, 2) +
292 6.943248e-5 * pow(waterTempertureC, 3) -
293 3.821216e-7 * pow(waterTempertureC, 4);
294 // This calculation gives a final result in mm of water
295 // from P = rho * g * h
296 float rhoDepth = 1000 * waterPressurePa /
297 (waterDensity * gravitationalConstant);
298 if (calculateWaterPressure() == -9999 || waterTempertureC == -9999) {
299 rhoDepth = -9999;
300 }
301 // Serial.print(F("Temperature corrected water depth is ")); // for
302 // debugging Serial.println(rhoDepth); // for debugging
303 return rhoDepth;
304}
305// Properties of the calculated temperature corrected water depth variable
306const char* rhoDepthVarName =
307 "waterDepth"; // This must be a value from
308 // http://vocabulary.odm2.org/variablename/
309const char* rhoDepthVarUnit =
310 "millimeter"; // This must be a value from
311 // http://vocabulary.odm2.org/units/
312int rhoDepthVarResolution = 3;
313const char* rhoDepthUUID = "12345678-abcd-1234-ef00-1234567890ab";
314const char* rhoDepthVarCode = "DensityDepth";
315// Create the temperature corrected water depth variable objects and return a
316// variable pointer to it
317Variable* calcCorrDepth = new Variable(
318 calculateWaterDepthTempCorrected, rhoDepthVarResolution, rhoDepthVarName,
319 rhoDepthVarUnit, rhoDepthVarCode, rhoDepthUUID);
320/** End [calculated_corrected_depth] */
321
322
323// ==========================================================================
324// Creating the Variable Array[s] and Filling with Variable Objects
325// ==========================================================================
326/** Start [variable_arrays] */
327// Fill array with already created and named variable pointers
328Variable* variableList[] = {mcuBoardSampNo, mcuBoardBatt, mcuBoardAvailableRAM,
329 ds3231Temp, bme280Temp, bme280Humid,
330 bme280Press, bme280Alt, ms5803Temp,
331 ms5803Press, ds18Temp, calcWaterPress,
332 calcRawDepth, calcCorrDepth, modemRSSI,
333 modemSignalPct};
334// Count up the number of pointers in the array
335int variableCount = sizeof(variableList) / sizeof(variableList[0]);
336
337// Create the VariableArray object
338VariableArray varArray(variableCount, variableList);
339/** End [variable_arrays] */
340
341
342// ==========================================================================
343// The Logger Object[s]
344// ==========================================================================
345/** Start [loggers] */
346// Create a new logger instance
347Logger dataLogger(LoggerID, loggingInterval, &varArray);
348/** End [loggers] */
349
350
351// ==========================================================================
352// Creating Data Publisher[s]
353// ==========================================================================
354/** Start [publishers] */
355// A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal
356// Device registration and sampling feature information can be obtained after
357// registration at https://monitormywatershed.org or https://data.envirodiy.org
358const char* registrationToken =
359 "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token
360const char* samplingFeature =
361 "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID
362
363// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint
364#include <publishers/EnviroDIYPublisher.h>
365EnviroDIYPublisher EnviroDIYPOST(dataLogger, &modem.gsmClient,
366 registrationToken, samplingFeature);
367/** End [publishers] */
368
369
370// ==========================================================================
371// Working Functions
372// ==========================================================================
373/** Start [working_functions] */
374// Flashes the LED's on the primary board
375void greenredflash(uint8_t numFlash = 4, uint8_t rate = 75) {
376 for (uint8_t i = 0; i < numFlash; i++) {
377 digitalWrite(greenLED, HIGH);
378 digitalWrite(redLED, LOW);
379 delay(rate);
380 digitalWrite(greenLED, LOW);
381 digitalWrite(redLED, HIGH);
382 delay(rate);
383 }
384 digitalWrite(redLED, LOW);
385}
386
387// Uses the processor sensor to read the battery voltage
388// NOTE: This will actually return the battery level from the previous update!
389float getBatteryVoltage() {
390 if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update();
391 return mcuBoard.sensorValues[0];
392}
393/** End [working_functions] */
394
395
396// ==========================================================================
397// Arduino Setup Function
398// ==========================================================================
399/** Start [setup] */
400void setup() {
401 // Start the primary serial connection
402 Serial.begin(serialBaud);
403
404 // Print a start-up note to the first serial port
405 Serial.print(F("Now running "));
406 Serial.print(sketchName);
407 Serial.print(F(" on Logger "));
408 Serial.println(LoggerID);
409 Serial.println();
410
411 Serial.print(F("Using ModularSensors Library version "));
412 Serial.println(MODULAR_SENSORS_VERSION);
413 Serial.print(F("TinyGSM Library version "));
414 Serial.println(TINYGSM_VERSION);
415 Serial.println();
416
417 // Start the serial connection with the modem
418 modemSerial.begin(modemBaud);
419
420 // Set up pins for the LED's
421 pinMode(greenLED, OUTPUT);
422 digitalWrite(greenLED, LOW);
423 pinMode(redLED, OUTPUT);
424 digitalWrite(redLED, LOW);
425 // Blink the LEDs to show the board is on and starting up
426 greenredflash();
427
428 // Set the timezones for the logger/data and the RTC
429 // Logging in the given time zone
430 Logger::setLoggerTimeZone(timeZone);
431 // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0)
432 Logger::setRTCTimeZone(0);
433
434 // Attach the modem and information pins to the logger
435 dataLogger.attachModem(modem);
436 modem.setModemLED(modemLEDPin);
437 dataLogger.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin,
438 greenLED);
439
440 // Begin the logger
441 dataLogger.begin();
442
443 // Note: Please change these battery voltages to match your battery
444 // Set up the sensors, except at lowest battery level
445 if (getBatteryVoltage() > 3.4) {
446 Serial.println(F("Setting up sensors..."));
447 varArray.setupSensors();
448 }
449
450 // Sync the clock if it isn't valid or we have battery to spare
451 if (getBatteryVoltage() > 3.55 || !dataLogger.isRTCSane()) {
452 // Synchronize the RTC with NIST
453 // This will also set up the modem
454 dataLogger.syncRTC();
455 }
456
457 // Create the log file, adding the default header to it
458 // Do this last so we have the best chance of getting the time correct and
459 // all sensor names correct
460 // Writing to the SD card can be power intensive, so if we're skipping
461 // the sensor setup we'll skip this too.
462 if (getBatteryVoltage() > 3.4) {
463 Serial.println(F("Setting up file on SD card"));
464 dataLogger.turnOnSDcard(
465 true); // true = wait for card to settle after power up
466 dataLogger.createLogFile(true); // true = write a new header
467 dataLogger.turnOffSDcard(
468 true); // true = wait for internal housekeeping after write
469 }
470
471 // Call the processor sleep
472 Serial.println(F("Putting processor to sleep\n"));
473 dataLogger.systemSleep();
474}
475/** End [setup] */
476
477
478// ==========================================================================
479// Arduino Loop Function
480// ==========================================================================
481/** Start [loop] */
482// Use this short loop for simple data logging and sending
483void loop() {
484 // Note: Please change these battery voltages to match your battery
485 // At very low battery, just go back to sleep
486 if (getBatteryVoltage() < 3.4) {
487 dataLogger.systemSleep();
488 }
489 // At moderate voltage, log data but don't send it over the modem
490 else if (getBatteryVoltage() < 3.55) {
491 dataLogger.logData();
492 }
493 // If the battery is good, send the data to the world
494 else {
495 dataLogger.logDataAndPublish();
496 }
497}
498/** End [loop] */