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
2 const 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
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
8 ; Please visit documentation for the other options and examples
9 ; http://docs.platformio.org/page/projectconf.html
12 description = ModularSensors example with a calculated variable
31 -DSDI12_EXTERNAL_PCINT
32 -DNEOSWSERIAL_EXTERNAL_PCINT
33 -DMQTT_MAX_PACKET_SIZE = 240
34 -DTINY_GSM_RX_BUFFER = 64
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>
7 * @brief Example demonstrating calculated variables.
9 * See [the walkthrough page](@ref example_baro_rho) for detailed instructions.
11 * @m_examplenavigation{example_baro_rho,}
12 * ======================================================================= */
14 // ==========================================================================
16 // NOTE: These only work with TinyGSM.
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 // Data Logging Options
42 // ==========================================================================
43 /** Start [logging_options] */
44 // The name of this program file
45 const char * sketchName = "baro_rho_correction.ino" ;
46 // Logger ID, also becomes the prefix for the name of the data file on SD card
47 const char * LoggerID = "XXXXX" ;
48 // How frequently (in minutes) to log data
49 const uint8_t loggingInterval = 15 ;
50 // Your logger's timezone.
51 const int8_t timeZone = -5 ; // Eastern Standard Time
52 // NOTE: Daylight savings time will not be applied! Please use standard time!
54 // Set the input and output pins for the logger
55 // NOTE: Use -1 for pins that do not apply
56 const int32_t serialBaud = 115200 ; // Baud rate for debugging
57 const int8_t greenLED = 8 ; // Pin for the green LED
58 const int8_t redLED = 9 ; // Pin for the red LED
59 const int8_t buttonPin = 21 ; // Pin for debugging mode (ie, button pin)
60 const int8_t wakePin = 31 ; // MCU interrupt/alarm pin to wake from sleep
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
64 const int8_t sdCardPwrPin = -1 ; // MCU SD card power pin
65 const int8_t sdCardSSPin = 12 ; // SD card chip select/slave select pin
66 const int8_t sensorPowerPin = 22 ; // MCU pin controlling main sensor power
67 /** End [logging_options] */
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>
78 // Create a reference to the serial port for the modem
79 HardwareSerial & modemSerial = Serial1 ; // Use hardware serial if possible
81 const int32_t modemBaud = 9600 ; // SIM800 does auto-bauding by default
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
86 const int8_t modemVccPin = 23 ; // MCU pin controlling modem power
87 const int8_t modemStatusPin = 19 ; // MCU pin used to read modem status
88 const int8_t modemResetPin = -1 ; // MCU pin connected to modem reset pin
89 const int8_t modemSleepRqPin = -1 ; // MCU pin for modem sleep/wake request
90 const int8_t modemLEDPin = redLED ; // MCU pin connected an LED to show modem
93 // Network connection information
94 const char * apn = "xxxxx" ; // APN for GPRS connection
96 // Create the modem object
97 Sodaq2GBeeR6 modem2GB ( & modemSerial , modemVccPin , modemStatusPin , apn );
98 // Create an extra reference to the modem by a generic name
99 Sodaq2GBeeR6 modem = modem2GB ;
101 // Create RSSI and signal strength variable pointers for the modem
103 new Modem_RSSI ( & modem , "12345678-abcd-1234-ef00-1234567890ab" , " RSSI " );
104 Variable * modemSignalPct = new Modem_SignalPercent (
105 & modem , "12345678-abcd-1234-ef00-1234567890ab" , "signalPercent" );
106 /** End [sodaq_2g_bee_r6] */
109 // ==========================================================================
110 // Using the Processor as a Sensor
111 // ==========================================================================
112 /** Start [processor_sensor] */
113 #include <sensors/ProcessorStats.h>
115 // Create the main processor chip "sensor" - for general metadata
116 const char * mcuBoardVersion = "v1.1" ;
117 ProcessorStats mcuBoard ( mcuBoardVersion );
119 // Create sample number, battery voltage, and free RAM variable pointers for the
121 Variable * mcuBoardBatt = new ProcessorStats_Battery (
122 & mcuBoard , "12345678-abcd-1234-ef00-1234567890ab" );
123 Variable * mcuBoardAvailableRAM = new ProcessorStats_FreeRam (
124 & mcuBoard , "12345678-abcd-1234-ef00-1234567890ab" );
125 Variable * mcuBoardSampNo = new ProcessorStats_SampleNumber (
126 & mcuBoard , "12345678-abcd-1234-ef00-1234567890ab" );
127 /** End [processor_sensor] */
130 // ==========================================================================
131 // Maxim DS3231 RTC (Real Time Clock)
132 // ==========================================================================
134 #include <sensors/MaximDS3231.h>
136 // Create a DS3231 sensor object
137 MaximDS3231 ds3231 ( 1 );
139 // Create a temperature variable pointer for the DS3231
140 Variable * ds3231Temp =
141 new MaximDS3231_Temp ( & ds3231 , "12345678-abcd-1234-ef00-1234567890ab" );
145 // ==========================================================================
146 // Bosch BME280 Environmental Sensor
147 // ==========================================================================
149 #include <sensors/BoschBME280.h>
151 const int8_t I2CPower = sensorPowerPin ; // Power pin (-1 if unconnected)
152 uint8_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
156 // Create a Bosch BME280 sensor object
157 BoschBME280 bme280 ( I2CPower , BMEi2c_addr );
159 // Create four variable pointers for the BME280
160 Variable * bme280Humid =
161 new BoschBME280_Humidity ( & bme280 , "12345678-abcd-1234-ef00-1234567890ab" );
162 Variable * bme280Temp =
163 new BoschBME280_Temp ( & bme280 , "12345678-abcd-1234-ef00-1234567890ab" );
164 Variable * bme280Press =
165 new BoschBME280_Pressure ( & bme280 , "12345678-abcd-1234-ef00-1234567890ab" );
167 new BoschBME280_Altitude ( & bme280 , "12345678-abcd-1234-ef00-1234567890ab" );
171 // ==========================================================================
172 // Maxim DS18 One Wire Temperature Sensor
173 // ==========================================================================
175 #include <sensors/MaximDS18.h>
177 const int8_t OneWirePower = sensorPowerPin ; // Power pin (-1 if unconnected)
178 const int8_t OneWireBus = 4 ; // OneWire Bus Pin (-1 if unconnected)
180 // Create a Maxim DS18 sensor object (use this form for a single sensor on bus
181 // with an unknown address)
182 MaximDS18 ds18 ( OneWirePower , OneWireBus );
184 // Create a temperature variable pointer for the DS18
185 Variable * ds18Temp = new MaximDS18_Temp ( & ds18 ,
186 "12345678-abcd-1234-ef00-1234567890ab" );
190 // ==========================================================================
191 // Measurement Specialties MS5803-14BA pressure sensor
192 // ==========================================================================
194 #include <sensors/MeaSpecMS5803.h>
196 const uint8_t MS5803i2c_addr =
197 0x76 ; // The MS5803 can be addressed either as 0x76 (default) or 0x77
198 const int16_t MS5803maxPressure =
199 14 ; // The maximum pressure measurable by the specific MS5803 model
200 const uint8_t MS5803ReadingsToAvg = 1 ;
202 // Create a MeaSpec MS5803 pressure and temperature sensor object
203 MeaSpecMS5803 ms5803 ( I2CPower , MS5803i2c_addr , MS5803maxPressure ,
204 MS5803ReadingsToAvg );
206 // Create pressure and temperature variable pointers for the MS5803
207 Variable * ms5803Press =
208 new MeaSpecMS5803_Pressure ( & ms5803 , "12345678-abcd-1234-ef00-1234567890ab" );
209 Variable * ms5803Temp =
210 new MeaSpecMS5803_Temp ( & ms5803 , "12345678-abcd-1234-ef00-1234567890ab" );
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
222 float 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 ;
230 // Serial.print(F("Water pressure is ")); // for debugging
231 // Serial.println(waterPressure); // for debugging
232 return waterPressure ;
234 // Properties of the calculated water pressure variable
235 const char * waterPressureVarName =
236 "pressureGauge" ; // This must be a value from
237 // http://vocabulary.odm2.org/variablename/
238 const char * waterPressureVarUnit =
239 "millibar" ; // This must be a value from http://vocabulary.odm2.org/units/
240 int waterPressureVarResolution = 3 ;
241 const char * waterPressureUUID = "12345678-abcd-1234-ef00-1234567890ab" ;
242 const char * waterPressureVarCode = "CorrectedPressure" ;
243 // Create the calculated water pressure variable objects and return a variable
245 Variable * calcWaterPress = new Variable (
246 calculateWaterPressure , waterPressureVarResolution , waterPressureVarName ,
247 waterPressureVarUnit , waterPressureVarCode , waterPressureUUID );
248 /** End [calculated_pressure] */
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
254 float 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
261 // Properties of the calculated water depth variable
262 const char * waterDepthVarName =
263 "waterDepth" ; // This must be a value from
264 // http://vocabulary.odm2.org/variablename/
265 const char * waterDepthVarUnit =
266 "millimeter" ; // This must be a value from
267 // http://vocabulary.odm2.org/units/
268 int waterDepthVarResolution = 3 ;
269 const char * waterDepthUUID = "12345678-abcd-1234-ef00-1234567890ab" ;
270 const char * waterDepthVarCode = "CalcDepth" ;
271 // Create the calculated raw water depth variable objects and return a variable
273 Variable * calcRawDepth = new Variable (
274 calculateWaterDepthRaw , waterDepthVarResolution , waterDepthVarName ,
275 waterDepthVarUnit , waterDepthVarCode , waterDepthUUID );
276 /** End [calculated_uncorrected_depth] */
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
281 float 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 ) {
301 // Serial.print(F("Temperature corrected water depth is ")); // for
302 // debugging Serial.println(rhoDepth); // for debugging
305 // Properties of the calculated temperature corrected water depth variable
306 const char * rhoDepthVarName =
307 "waterDepth" ; // This must be a value from
308 // http://vocabulary.odm2.org/variablename/
309 const char * rhoDepthVarUnit =
310 "millimeter" ; // This must be a value from
311 // http://vocabulary.odm2.org/units/
312 int rhoDepthVarResolution = 3 ;
313 const char * rhoDepthUUID = "12345678-abcd-1234-ef00-1234567890ab" ;
314 const char * rhoDepthVarCode = "DensityDepth" ;
315 // Create the temperature corrected water depth variable objects and return a
316 // variable pointer to it
317 Variable * calcCorrDepth = new Variable (
318 calculateWaterDepthTempCorrected , rhoDepthVarResolution , rhoDepthVarName ,
319 rhoDepthVarUnit , rhoDepthVarCode , rhoDepthUUID );
320 /** End [calculated_corrected_depth] */
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
328 Variable * variableList [] = { mcuBoardSampNo , mcuBoardBatt , mcuBoardAvailableRAM ,
329 ds3231Temp , bme280Temp , bme280Humid ,
330 bme280Press , bme280Alt , ms5803Temp ,
331 ms5803Press , ds18Temp , calcWaterPress ,
332 calcRawDepth , calcCorrDepth , modemRSSI ,
334 // Count up the number of pointers in the array
335 int variableCount = sizeof ( variableList ) / sizeof ( variableList [ 0 ]);
337 // Create the VariableArray object
338 VariableArray varArray ( variableCount , variableList );
339 /** End [variable_arrays] */
342 // ==========================================================================
343 // The Logger Object[s]
344 // ==========================================================================
345 /** Start [loggers] */
346 // Create a new logger instance
347 Logger dataLogger ( LoggerID , loggingInterval , & varArray );
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
358 const char * registrationToken =
359 "12345678-abcd-1234-ef00-1234567890ab" ; // Device registration token
360 const char * samplingFeature =
361 "12345678-abcd-1234-ef00-1234567890ab" ; // Sampling feature UUID
363 // Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint
364 #include <publishers/EnviroDIYPublisher.h>
365 EnviroDIYPublisher EnviroDIYPOST ( dataLogger , & modem . gsmClient ,
366 registrationToken , samplingFeature );
367 /** End [publishers] */
370 // ==========================================================================
372 // ==========================================================================
373 /** Start [working_functions] */
374 // Flashes the LED's on the primary board
375 void 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 );
380 digitalWrite ( greenLED , LOW );
381 digitalWrite ( redLED , HIGH );
384 digitalWrite ( redLED , LOW );
387 // Uses the processor sensor to read the battery voltage
388 // NOTE: This will actually return the battery level from the previous update!
389 float getBatteryVoltage () {
390 if ( mcuBoard . sensorValues [ 0 ] == -9999 ) mcuBoard . update ();
391 return mcuBoard . sensorValues [ 0 ];
393 /** End [working_functions] */
396 // ==========================================================================
397 // Arduino Setup Function
398 // ==========================================================================
401 // Start the primary serial connection
402 Serial . begin ( serialBaud );
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 );
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 );
417 // Start the serial connection with the modem
418 modemSerial . begin ( modemBaud );
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
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 );
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 ,
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 ();
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 ();
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
471 // Call the processor sleep
472 Serial . println ( F ( "Putting processor to sleep \n " ));
473 dataLogger . systemSleep ();
478 // ==========================================================================
479 // Arduino Loop Function
480 // ==========================================================================
482 // Use this short loop for simple data logging and sending
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 ();
489 // At moderate voltage, log data but don't send it over the modem
490 else if ( getBatteryVoltage () < 3.55 ) {
491 dataLogger . logData ();
493 // If the battery is good, send the data to the world
495 dataLogger . logDataAndPublish ();