From 87cd0c995e5f6358fa3c11f25295d3a13e01c6fc Mon Sep 17 00:00:00 2001 From: Anthony Aufdenkampe Date: Mon, 5 Apr 2021 21:07:45 -0500 Subject: [PATCH] Update RainCounterIC2 to int32. Works! Updating to a 32-bit count enables use with a reed-switch anemometer (https://github.com/EnviroDIY/TippingBucketRainCounter/issues/4) and also fixes a bug described in https://github.com/EnviroDIY/TippingBucketRainCounter/issues/5. Tested and works, including backward compatibility. --- .../RainCounter_simple_logging.ino | 270 ++++++++++++++++++ src/SensorBase.cpp | 5 + src/SensorBase.h | 6 + src/sensors/RainCounterI2C.cpp | 39 ++- 4 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 sensor_tests/RainCounter_simple_logging/RainCounter_simple_logging.ino diff --git a/sensor_tests/RainCounter_simple_logging/RainCounter_simple_logging.ino b/sensor_tests/RainCounter_simple_logging/RainCounter_simple_logging.ino new file mode 100644 index 000000000..b3400617c --- /dev/null +++ b/sensor_tests/RainCounter_simple_logging/RainCounter_simple_logging.ino @@ -0,0 +1,270 @@ +/** ========================================================================= + * @file RainCounter_simple_logging.ino + * @brief Test improvements to RainCounterI2C, based on simple logging example. + * + * @author Sara Geleskie Damiano + * @author Anthony Aufdenkampe + * @copyright (c) 2017-2020 Stroud Water Research Center (SWRC) + * and the EnviroDIY Development Team + * This example is published under the BSD-3 license. + * + * Build Environment: Atom with PlatformIO Core 5.1.1·Home 3.3.4 + * Modular Sensors v0.28.3 as of April 5, 2021 + * Hardware Platform: EnviroDIY Mayfly Arduino Datalogger + * + * DISCLAIMER: + * THIS CODE IS PROVIDED "AS IS" - NO WARRANTY IS GIVEN. + * ======================================================================= */ + +// ========================================================================== +// Include the libraries required for any data logger +// ========================================================================== +/** Start [includes] */ +// The Arduino library is needed for every Arduino program. +#include + +// EnableInterrupt is used by ModularSensors for external and pin change +// interrupts and must be explicitly included in the main program. +#include + +// To get all of the base classes for ModularSensors, include LoggerBase. +// NOTE: Individual sensor definitions must be included separately. +#include +/** End [includes] */ + + +// ========================================================================== +// Data Logging Options +// ========================================================================== +/** Start [logging_options] */ +// The name of this program file +const char* sketchName = "RainCounter_simple_logging.ino"; +// Logger ID, also becomes the prefix for the name of the data file on SD card +const char* LoggerID = "RainCounter_Tester"; +// How frequently (in minutes) to log data +const uint8_t loggingInterval = 1; +// Your logger's timezone. +const int8_t timeZone = -6; // Eastern Standard Time +// NOTE: Daylight savings time will not be applied! Please use standard time! + +// Set the input and output pins for the logger +// NOTE: Use -1 for pins that do not apply +const int32_t serialBaud = 115200; // Baud rate for debugging +const int8_t greenLED = 8; // Pin for the green LED +const int8_t redLED = 9; // Pin for the red LED +const int8_t buttonPin = 21; // Pin for debugging mode (ie, button pin) +const int8_t wakePin = A7; // MCU interrupt/alarm pin to wake from sleep +// Set the wake pin to -1 if you do not want the main processor to sleep. +// In a SAMD system where you are using the built-in rtc, set wakePin to 1 +const int8_t sdCardPwrPin = -1; // MCU SD card power pin +const int8_t sdCardSSPin = 12; // SD card chip select/slave select pin +const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power +/** End [logging_options] */ + + +// ========================================================================== +// Using the Processor as a Sensor +// ========================================================================== +/** Start [processor_sensor] */ +#include + +// Create the main processor chip "sensor" - for general metadata +const char* mcuBoardVersion = "v0.5b"; +ProcessorStats mcuBoard(mcuBoardVersion); +/** End [processor_sensor] */ + + +// ========================================================================== +// Maxim DS3231 RTC (Real Time Clock) +// ========================================================================== +/** Start [ds3231] */ +#include // Includes wrapper functions for Maxim DS3231 RTC + +// Create a DS3231 sensor object, using this constructor function: +MaximDS3231 ds3231(1); +/** End [ds3231] */ + + +// ========================================================================== +// Settings for Additional Sensors +// ========================================================================== + +// ========================================================================== +// External I2C Rain Tipping Bucket Counter, connected to an anemometer +// ========================================================================== +/** Start [i2c_rain] */ +#include + +const uint8_t RainCounterI2CAddress = 0x08; +// I2C Address for EnviroDIY external tip counter; 0x08 by default +const float depthPerTipEvent = 0.2; // rain depth in mm per tip event + +// Create a Rain Counter sensor object +RainCounterI2C tbi2c(RainCounterI2CAddress, depthPerTipEvent); + +// Create number of tips and rain depth variable pointers for the tipping bucket +Variable* tbi2cTips = + new RainCounterI2C_Tips(&tbi2c, "12345678-abcd-1234-ef00-1234567890ab"); +// Variable* tbi2cDepth = +// new RainCounterI2C_Depth(&tbi2c, "12345678-abcd-1234-ef00-1234567890ab"); +/** End [i2c_rain] */ + + +// ========================================================================== +// Calculated Variables +// ========================================================================== + +// Create the function to give your calculated result. +// The function should take no input (void) and return a float. +// You can use any named variable pointers to access values by way of +// variable->getValue() + +float calculateWindSpeed(void) { + float windSpeed = -9999; // Always safest to start with a bad value + float period = -9999; // seconds between gettting event counts + float frequency = -9999; // average event frequency in Hz + float eventCount = tbi2cTips->getValue(); + if (eventCount != -9999) // make sure both inputs are good + { + period = loggingInterval * 60.0; // in seconds + frequency = eventCount/period; // average event frequency in Hz + windSpeed = frequency * 2.5 * 1.60934; // in km/h, + // 2.5 mph/Hz & 1.60934 kmph/mph and 2.5 mph/Hz conversion factor from + // https://www.store.inspeed.com/Inspeed-Version-II-Reed-Switch-Anemometer-Sensor-Only-WS2R.htm + } + return windSpeed; +} + +// Properties of the calculated variable +// The number of digits after the decimal place +const uint8_t calculatedVarResolution = 3; +// This must be a value from http://vocabulary.odm2.org/variablename/ +const char *calculatedVarName = "windSpeed"; +// This must be a value from http://vocabulary.odm2.org/units/ +const char *calculatedVarUnit = "KilometerPerHour"; +// A short code for the variable +const char *calculatedVarCode = "WindSpeed"; +// The (optional) universallly unique identifier +const char *calculatedVarUUID = "12345678-abcd-1234-ef00-1234567890ab"; + +// Create a calculated variable pointer and return a variable pointer to it +Variable *calculatedWindSpeed = new Variable( + calculateWindSpeed, calculatedVarResolution, calculatedVarName, + calculatedVarUnit, calculatedVarCode, calculatedVarUUID); + + +// ========================================================================== +// Creating the Variable Array[s] and Filling with Variable Objects +// ========================================================================== +/** Start [variable_arrays] */ +Variable* variableList[] = { + new ProcessorStats_SampleNumber(&mcuBoard), + new ProcessorStats_FreeRam(&mcuBoard), + new ProcessorStats_Battery(&mcuBoard), + new MaximDS3231_Temp(&ds3231), + // Additional sensor variables can be added here, by copying the syntax + // for creating the variable pointer (FORM1) from the + // `menu_a_la_carte.ino` example + // The example code snippets in the wiki are primarily FORM2. + tbi2cTips, + calculatedWindSpeed, +}; +// Count up the number of pointers in the array +int variableCount = sizeof(variableList) / sizeof(variableList[0]); + +// Create the VariableArray object +VariableArray varArray; +/** End [variable_arrays] */ + + +// ========================================================================== +// The Logger Object[s] +// ========================================================================== +/** Start [loggers] */ +// Create a logger instance +Logger dataLogger; +/** End [loggers] */ + + +// ========================================================================== +// Working Functions +// ========================================================================== +/** Start [working_functions] */ +// Flashes the LED's on the primary board +void greenredflash(uint8_t numFlash = 4, uint8_t rate = 75) { + for (uint8_t i = 0; i < numFlash; i++) { + digitalWrite(greenLED, HIGH); + digitalWrite(redLED, LOW); + delay(rate); + digitalWrite(greenLED, LOW); + digitalWrite(redLED, HIGH); + delay(rate); + } + digitalWrite(redLED, LOW); +} +/** End [working_functions] */ + + +// ========================================================================== +// Arduino Setup Function +// ========================================================================== +/** Start [setup] */ +void setup() { + // Start the primary serial connection + Serial.begin(serialBaud); + + // Print a start-up note to the first serial port + Serial.print(F("Now running ")); + Serial.print(sketchName); + Serial.print(F(" on Logger ")); + Serial.println(LoggerID); + Serial.println(); + + Serial.print(F("Using ModularSensors Library version ")); + Serial.println(MODULAR_SENSORS_VERSION); + + // Set up pins for the LED's + pinMode(greenLED, OUTPUT); + digitalWrite(greenLED, LOW); + pinMode(redLED, OUTPUT); + digitalWrite(redLED, LOW); + // Blink the LEDs to show the board is on and starting up + greenredflash(); + + // Set the timezones for the logger/data and the RTC + // Logging in the given time zone + Logger::setLoggerTimeZone(timeZone); + // It is STRONGLY RECOMMENDED that you set the RTC to be in UTC (UTC+0) + Logger::setRTCTimeZone(0); + + // Set information pins + dataLogger.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin, + greenLED); + + // Begin the variable array[s], logger[s], and publisher[s] + varArray.begin(variableCount, variableList); + dataLogger.begin(LoggerID, loggingInterval, &varArray); + + // Set up the sensors + Serial.println(F("Setting up sensors...")); + varArray.setupSensors(); + + // Create the log file, adding the default header to it + // Do this last so we have the best chance of getting the time correct and + // all sensor names correct + dataLogger.createLogFile(true); // true = write a new header + + // Call the processor sleep + dataLogger.systemSleep(); +} +/** End [setup] */ + + +// ========================================================================== +// Arduino Loop Function +// ========================================================================== +/** Start [loop] */ +void loop() { + dataLogger.logData(); +} +/** End [loop] */ diff --git a/src/SensorBase.cpp b/src/SensorBase.cpp index 5109e154a..afdab42f6 100644 --- a/src/SensorBase.cpp +++ b/src/SensorBase.cpp @@ -362,6 +362,11 @@ void Sensor::verifyAndAddMeasurementResult(uint8_t resultNumber, float float_val = resultValue; // cast the int16_t to a float verifyAndAddMeasurementResult(resultNumber, float_val); } +void Sensor::verifyAndAddMeasurementResult(uint8_t resultNumber, + int32_t resultValue) { + float float_val = resultValue; // cast the int32_t to a float + verifyAndAddMeasurementResult(resultNumber, float_val); +} void Sensor::averageMeasurements(void) { diff --git a/src/SensorBase.h b/src/SensorBase.h index f3ed18ab5..c9f517448 100644 --- a/src/SensorBase.h +++ b/src/SensorBase.h @@ -347,6 +347,12 @@ class Sensor { * @brief Average the results of all measurements by dividing the sum of * all measurements by the number of measurements taken. */ + void verifyAndAddMeasurementResult(uint8_t resultNumber, + int32_t resultValue); + /** + * @brief Average the results of all measurements by dividing the sum of + * all measurements by the number of measurements taken. + */ void averageMeasurements(void); /** diff --git a/src/sensors/RainCounterI2C.cpp b/src/sensors/RainCounterI2C.cpp index d80292d7b..cee837dc5 100644 --- a/src/sensors/RainCounterI2C.cpp +++ b/src/sensors/RainCounterI2C.cpp @@ -96,18 +96,47 @@ bool RainCounterI2C::setup(void) { bool RainCounterI2C::addSingleMeasurementResult(void) { // intialize values float rain = -9999; // Number of mm of rain - int16_t tips = -9999; // Number of tip events + int32_t tips = -9999; // Number of tip events, increased for anemometer // Get data from external tip counter // if the 'requestFrom' returns 0, it means no bytes were received if (_i2c->requestFrom(static_cast(_i2cAddressHex), - static_cast(2))) { + static_cast(4))) { MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - uint8_t Byte1 = _i2c->read(); // Low byte of data - uint8_t Byte2 = _i2c->read(); // High byte of data + uint8_t SerialBuffer[4]; // Create a byte array of 4 bytes + uint8_t byte_in = 0; // Start iterator for reading Bytes + while (Wire.available()) { // slave may send less than requested + SerialBuffer[byte_in] = Wire.read(); + MS_DBG(F(" SerialBuffer["), byte_in, F("] = "), + SerialBuffer[byte_in]); + byte_in++; // increment by 1 + } + + // Concatenate bytes into uint32_t by bit-shifting + // https://thewanderingengineer.com/2015/05/06/sending-16-bit-and-32-bit-numbers-with-arduino-i2c/# + if ( (SerialBuffer[0] > 0) + ) { + // for Slave with libVersion = v0.1.0, which only sends 1-byte + // NOTE: this can not be falsely selected because it would require + // > 16,777,216 counts from a v0.2.0 slave, which is not possible in 24 hours + MS_DBG(F(" Counted with slave libVersion = v0.1.0")); + tips = SerialBuffer[0]; + } else if ( (SerialBuffer[1] == 0) && + (SerialBuffer[2] == 255) ) { + // for Slave with libVersion = v0.1.0, in which no counts are made + // NOTE: this will be falsely selected if exactly 65535 counts + // were made by a v0.2.0 slave + MS_DBG(F(" No counts with slave libVersion = v0.1.0")); + tips = SerialBuffer[0]; + } else { + // for Slave with libVersion >= v0.2.0 + tips = SerialBuffer[0]; + tips = (tips << 8) | SerialBuffer[1]; + tips = (tips << 8) | SerialBuffer[2]; + tips = (tips << 8) | SerialBuffer[3]; + } - tips = (Byte2 << 8) | (Byte1); // Concatenate tip values rain = static_cast(tips) * _rainPerTip; // Multiply by tip coefficient (0.2 by default)