diff --git a/.clang-format b/.clang-format index 6e42a7597..f12b4c484 100644 --- a/.clang-format +++ b/.clang-format @@ -2,13 +2,28 @@ Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -3 +ColumnLimit: 80 +DisableFormat: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never + AlignAfterOpenBracket: Align AlignConsecutiveMacros: false -AlignConsecutiveAssignments: true -AlignConsecutiveDeclarations: true +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false AlignEscapedNewlines: Left +# AlignOperands: AlignAfterOperator AlignOperands: false AlignTrailingComments: true + AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true @@ -19,33 +34,40 @@ AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None + AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes + BinPackArguments: true -BinPackParameters: true +BinPackParameters: BinPack + BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: Automatic +BreakArrays: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false +# BreakBinaryOperations: RespectPrecedence +BreakBinaryOperations: Never BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false BreakInheritanceList: BeforeColon BreakStringLiterals: true -ColumnLimit: 80 + CommentPragmas: '^ IWYU pragma:' + CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false + ConstructorInitializerIndentWidth: 4 + ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false -DisableFormat: false + EmptyLineAfterAccessModifier: Leave EmptyLineBeforeAccessModifier: Leave -ExperimentalAutoDetectBinPacking: false + FixNamespaceComments: true ForEachMacros: - foreach @@ -64,6 +86,7 @@ IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '([-_](test|unittest))?$' + IndentAccessModifiers: false IndentCaseBlocks: false IndentCaseLabels: true @@ -72,19 +95,24 @@ IndentGotoLabels: true IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false + +InsertBraces: false +InsertNewlineAtEOF: true InsertTrailingCommas: None -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false + +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: false +MaxEmptyLinesToKeep: 2 + MacroBlockBegin: '' MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 + NamespaceIndentation: None -# ObjCBinPackProtocolList: Auto -ObjCBlockIndentWidth: 2 -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: true + PackConstructorInitializers: CurrentLine + PenaltyBreakAssignment: 25 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 @@ -92,15 +120,20 @@ PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 600 -PenaltyReturnTypeOnItsOwnLine: 50 +PenaltyReturnTypeOnItsOwnLine: 500 + PointerAlignment: Left PointerBindsToType: true QualifierAlignment: Left ReferenceAlignment: Left + ReflowComments: true + SeparateDefinitionBlocks: Leave + SortIncludes: false SortUsingDeclarations: true + SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true @@ -121,7 +154,4 @@ SpacesInLineCommentPrefix: Minimum: 1 SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 -TabWidth: 4 -UseTab: Never --- diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..e6350f5e6 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,87 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +early_access: false +enable_free_tier: true +language: en +tone_instructions: '' +knowledge_base: + learnings: + scope: auto + issues: + scope: auto + jira: + project_keys: [] + linear: + team_keys: [] +chat: + auto_reply: true +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + poem: false + review_status: true + collapse_walkthrough: true + path_filters: + - '!**/dependencies.json' + - '!**/cspell.json' + - '!**/platformio.ini' + path_instructions: + - path: '**/*.md' + instructions: >- + Review for grammar, spelling, and clarity. + Each sentence should be on a new line. + Markdown files should follow rules of markdownlint. + Check c++ code snippets as c++ code. + - path: '**/*.h' + instructions: >- + Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance. + Since this is an Arduino project, never suggest using the standard library (std::). + For memory savings, prefer defines to const variables, and prefer using the F() macro for string literals longer than a single character. + Defines should be in the headers. + Unique functions, defines, and parameters must be documented with Doxygen comments in the header files. + Override functions don't need Doxygen comments in the header files, unless they have unique parameters or behavior that needs to be documented. + Files should be formatted according to the .clang-format file in the repo. + Don't suggest making getter functions const. + - path: '**/*.cpp' + instructions: >- + Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance. + Since this is an Arduino project, never suggest using the standard library (std::). + For memory savings, prefer defines to const variables, and prefer using the F() macro for string literals longer than a single character. + Defines should be in the headers. + Doxygen documentation should be in the header files. + Files should be formatted according to the .clang-format file in the repo. + Don't suggest making getter functions const. + tools: + shellcheck: + enabled: true + ruff: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + disabled_rules: + - EN_UNPAIRED_BRACKETS + disabled_categories: + - TYPOS + - TYPOGRAPHY + - CASING + enabled_only: false + level: default + biome: + enabled: true + hadolint: + enabled: true + auto_review: + enabled: true + ignore_title_keywords: + - WIP + labels: [] + drafts: true + base_branches: + - master + - develop diff --git a/.gitattributes b/.gitattributes index d89fbb64c..1d35033a7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,6 +11,7 @@ # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf +*.bat text eol=crlf # Denote all files that are truly binary and should not be modified. *.png binary @@ -27,8 +28,16 @@ .travis.yml export-ignore id_rsa.enc export-ignore +# Setup scripts +setupGitFilters.bat export-ignore +setupGitFilters.ps1 export-ignore + # Platformio platformio.ini export-ignore # Shell Scripts *.sh export-ignore + +# filters +*.ino filter=smudgePasswords +ModSensorDebugConfig.h filter=disableDebug diff --git a/.gitignore b/.gitignore index e2342cb89..e041784b2 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ archive/ compile_tests/ logger_test*/ ex_one_offs/* +filters/* runDoxygen.bat docs/examples/* docs/examples.dox_x diff --git a/ChangeLog.md b/ChangeLog.md index be1681bf7..5453692ec 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,43 +6,261 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + + + + +- [ChangeLog](#changelog) + - [Unreleased](#unreleased) + - [0.37.0](#0370) + - [0.36.0](#0360) + - [0.35.1](#0351) + - [0.35.0](#0350) + - [\[0.34.1\]](#0341) + - [0.34.0](#0340) + - [0.33.4](#0334) + - [0.33.3](#0333) + - [0.33.2](#0332) + - [0.33.1 - 2022-04-11](#0331---2022-04-11) + - [0.33.0 - 2022-04-01](#0330---2022-04-01) + - [0.32.2 - 2021-11-23](#0322---2021-11-23) + - [0.32.0 - 2021-11-19](#0320---2021-11-19) + - [0.31.2 - 2021-11-03](#0312---2021-11-03) + - [0.31.0 - 2021-11-02](#0310---2021-11-02) + - [0.30.0 - 2021-07-06](#0300---2021-07-06) + - [0.29.1 - 2021-07-01](#0291---2021-07-01) + - [0.29.0 - 2021-05-19](#0290---2021-05-19) + - [0.28.5 - 2021-05-11](#0285---2021-05-11) + - [0.28.4 - 2021-05-05](#0284---2021-05-05) + - [0.28.3 - 2021-03-24](#0283---2021-03-24) + - [\[0.28.01\] - 2021-02-10](#02801---2021-02-10) + - [0.28.0 - 2021-02-10](#0280---2021-02-10) + - [0.27.8 - 2021-01-19](#0278---2021-01-19) + - [0.27.7 - 2021-01-19](#0277---2021-01-19) + - [0.27.6 - 2021-01-19](#0276---2021-01-19) + - [0.27.5 - 2020-12-15](#0275---2020-12-15) + - [0.25.0 - 2020-06-25](#0250---2020-06-25) + - [0.24.1 - 2020-03-02](#0241---2020-03-02) + - [0.23.13 - 2019-09-19](#02313---2019-09-19) + - [0.23.11 - 2019-09-11](#02311---2019-09-11) + - [0.22.5 - 2019-06-24](#0225---2019-06-24) + - [0.21.4 - 2019-05-02](#0214---2019-05-02) + - [0.21.3 - 2019-05-02](#0213---2019-05-02) + - [0.21.2 - 2019-03-19](#0212---2019-03-19) + - [0.21.0 - 2019-03-06](#0210---2019-03-06) + - [0.19.6 - 2019-02-27](#0196---2019-02-27) + - [0.19.3 - 2019-01-15](#0193---2019-01-15) + - [0.19.2 - 2018-12-22](#0192---2018-12-22) + - [0.17.2 - 2018-11-27](#0172---2018-11-27) + - [0.12.2 - 2018-09-25](#0122---2018-09-25) + - [0.11.6 - 2018-05-11](#0116---2018-05-11) + - [0.11.3 - 2018-05-03](#0113---2018-05-03) + - [0.9.0 - 2018-04-17](#090---2018-04-17) + - [0.6.10 - 2018-02-26](#0610---2018-02-26) + - [0.6.9 - 2018-02-26](#069---2018-02-26) + - [0.5.4-beta - 2018-01-18](#054-beta---2018-01-18) + - [\[0.3.0\]-beta - 2017-06-07](#030-beta---2017-06-07) + - [\[0.2.5\]-beta - 2017-05-31](#025-beta---2017-05-31) + - [\[0.2.4\]-beta - 2017-05-17](#024-beta---2017-05-17) + - [\[0.2.2\]-beta - 2017-05-09](#022-beta---2017-05-09) + + + *** ## [Unreleased] ### Changed +#### Individual Sensors + +- **ANBpH** + - **BREAKING** The constructor has changed! + The logging interval has been added as a required parameter for the constructor! + - Changed timing slightly and simplified timing logic. +- **AlphasenseCO2** and **TurnerTurbidityPlus** + - **BREAKING** The constructors for both of these analog-based classes have changed! + Previously the constructor required an enum object for the supported differential channels. + The new constructor requires two different analog channel numbers as inputs for the differential voltage measurement. + If you are using code from a previous version of the library, make sure to update your code to use the new constructor and provide the correct analog channel inputs for the differential voltage measurement. + - Moved resistor settings and calibration values into the following preprocessor defines that can be modified to tweak the library if necessary: + - `ALPHASENSE_CO2_SENSE_RESISTOR_OHM` (defaults to `250.0f`) + - `ALPHASENSE_CO2_MFG_SCALE` (defaults to `312.5f`) + - `ALPHASENSE_CO2_MFG_OFFSET` (defaults to `1250.0f`) + - `ALPHASENSE_CO2_VOLTAGE_MULTIPLIER` (defaults to `1.0f`) + - `TURBIDITY_PLUS_WIPER_TRIGGER_PULSE_MS` (defaults to `50`) + - `TURBIDITY_PLUS_WIPER_ROTATION_WAIT_MS` (defaults to `8000`) + - `TURBIDITY_PLUS_CALIBRATION_EPSILON` (defaults to `1e-4f`) +- **ApogeeSQ212**, **TIADS1x15**, **CampbellOBS3**, and **TurnerCyclops** + - *Potentially breaking* The constructors for all of these analog-based classes have changed! + Previously the I2C address of the ADS1x15 was an optional input parameter which came *before* the optional input parameter for the number of measurements to average. + The input parameter for the I2C address has been *removed* and the input for the number of measurements to average has been moved up in the order! + For users who used the default values, this will have no effect. + For users who provided both a custom I2C address and a custom number of measurements, this will cause a compiler error. + For users who provided a custom I2C address *but not a custom number of measurements* this will cause a *silent failure* because the custom I2C address will be used as the measurement count and the default I2C address will be used. + Users who need a custom I2C address for the ADS1x15 must construct a TIADS1x15Reader object with the correct address and pass a pointer to that object to the constructor. +- **AnalogElecConductivity** + - *Renamed* configuration defines to have the prefix `ANALOGELECCONDUCTIVITY` and moved other defaults to defines. + This affects the following defines: + - `ANALOGELECCONDUCTIVITY_RSERIES_OHMS` (formerly `RSERIES_OHMS_DEF`) + - `ANALOGELECCONDUCTIVITY_KONST` (formerly `SENSOREC_KONST_DEF`) + - `ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO` (formerly a hardcoded value of `0.999f`) +- **EverlightALSPT19** + - Moved the calibration constant between current and lux to the `ALSPT19_UA_PER_1000LUX` preprocessor define. +- **Bosch BMP3xx** + - Changed the default oversampling for both pressure and temperature to 1x (no oversampling) as recommended by the datasheet for weather and environmental monitoring. + +#### All Sensors + +- Simplified the `addSingleMeasurementResult()` function of all sensors to use an internal function to help set the bits and timing values and to quit sooner if the measurement was not started successfully. + - The `verifyAndAddMeasurementResult()` is now consistently used in all sensors and is only called when the sensor successfully returned a measurement response. + - Also removed all places where sensor values were re-set to -9999 after a measurement failed and then that -9999 was sent to the `verifyAndAddMeasurementResult()` function. + These resets were an awkward attempt to deal with bad values before feeding any bad values to the `verifyAndAddMeasurementResult()` function which was previously always called even if the sensor returned junk. + This was probably a hold-over from incorrect implementation and calling of the `clearValues()` function deep in the library history. + - Also made the return from the `addSingleMeasurementResult()` function consistently false for a bad sensor response and true for a good one - where it's possible to tell the difference. + +#### Individual Publishers + +- *Renamed* the EnviroDIYPublisher to MonitorMyWatershedPublisher. + This reflects changes to the website from years ago. + There is a shell file and typedef to maintain backwards compatibility. + +#### All Publishers + +- Renamed `setinitialShortIntervals(#)` to `setStartupMeasurements(#)`. + - The old `setinitialShortIntervals` remains available via compatibility shim in LoggerBase.h, so existing code is unaffected. + +#### Loggers and Variable Arrays + +- Made the enabling and disabling of the watchdog the very first and very last steps of sleep to keep the watchdog enabled through the whole getting ready for bed and waking up process. +- Re-wrote most of the logic for looping variables within the complete update function of the VariableArray. +- Re-wrote some of the logic of the `completeUpdate()` function. + Also added optional arguments to the `completeUpdate()` function to allow users to specify if the sensors should be powered/woken. + - The `updateAllSensors()` function is now deprecated. + Use `completeUpdate(false, false, false, false)` instead. + - Previously the `updateAllSensors()` function asked all sensors to update their values, skipping all power, wake, and sleep steps while the `completeUpdate()` function duplicated that functionality and added the power, wake, and sleep. + The two functions have been consolidated into one function with four arguments, one each for power on, wake, sleep, and power off. + To achieve the same functionality as the old `updateAllSensors()` function (i.e., only updating values), set all the arguments to false. + +#### Library-Wide + +- Bumped several dependencies - including crucial bug fixes to SensorModbusMaster. +- Applied many suggestions from CodeRabbit AI. - Moved outdated examples to a new "Outdated" folder, with a subfolder for the DRWI examples +- When importing TinyGSM for the modem objects, the specific modem client headers are now imported directly rather than importing the TinyGsmClient.h header which defines typedefs for the sub-types. +- Moved the define for the default address used for a TI ADS from multiple individual files to the ModSensorConfig and renamed to `MS_DEFAULT_ADS1X15_ADDRESS`. +- Within ModSensorConfig removed the default `MS_PROCESSOR_ADC_RESOLUTION` for all processors and clarified that the 12-bit default only applies to SAMD processors. + This is *not* breaking because only AVR and SAMD processors were supported anyway. +- Converted as many duplicated constructors as possible into delegating constructors. ### Added +#### New Sensors + +- **NEW SENSOR** Added a new sensor for simple analog voltage using the built-in processor ADC +- **NEW SENSOR** Added support for the TE Connectivity (Meas Specialties) MS5837 + - This is the sensor embedded in the Blue Robotics Bar02 and Bar30 sensors. + +#### New Features for Specific Sensors + +- **TIADS1x15** + - Added support for a secondary hardware I2C instance. + +#### Features for All Sensors + +- **Added support for retrying measurements for all sensors**. + - Each sensor now supports a number of possible retry attempts for when the sensor returns a bad or no value. + The number of retry attempts can be set using the `setMaxRetries(uint8_t)` function. + - The number of retries is independent of the number of measurements to average. + A retry is performed when a sensor doesn't report a value or reports an error value. + If multiple retries are needed, only the result of the final (successful) retry is stored. + When multiple 'measurements to average' are requested, the values of each successful measurement is stored and averaged. + Measurements that return bad values even after retries are still not included in averaging. + - The default number of retry attempts for most sensors is 1. + - The number of retries and the number of attempted measurements can be reset with `resetMeasurementCounts().` +- Made a secondary power pin a property of all sensors. +- Added internal function to run the steps of setting the timing and bits after a measurement. +- Added setter and getter functions for sensor timing variables. + These values should generally be set in the specific sensor constructors and only changed if you know what you're doing. + - `setWarmUpTime(uint32_t warmUpTime_ms)` + - `getWarmUpTime()` + - `setStabilizationTime(uint32_t stabilizationTime_ms)` + - `getStabilizationTime()` + - `setMeasurementTime(uint32_t measurementTime_ms)` + - `getMeasurementTime()` +- Added the functions `Sensor::clearStatus()`,`Sensor::clearPowerStatus()`,`Sensor::clearWakeStatus()`,and `Sensor::clearMeasurementStatus()` which reset some or all of the sensor status bits and related timing variables. +- Added an abstract AnalogVoltageReader class with two concrete classes for analog voltage measurements: ProcessorAnalogReader and TIADS1x15Reader. + All supported analog sensors can now accept a pointer to an object of any concrete subclass of the AnalogVoltageReader class to use for raw voltage measurements. + By default, existing analog sensors will create an internal concrete AnalogVoltageReader subclass instance for whichever ADC type (processor or ADS1x15) the sensor was originally coded to use. + This affects the following classes: + - AlphasenseCO2 + - AnalogElecConductivity + - ApogeeSQ212 + - CampbellOBS3 + - EverlightALSPT19 + - TurnerCyclops + - TurnerTurbidityPlus + +#### Features for Publishers + +- Added setters/getters for the number of startup transmissions. + +#### Library-Wide + +- Added a configuration define for MS_INVALID_VALUE and replaced all occurrences of the standard -9999 with this define. +- Added KnownProcessors.h and moved define values for supported built-in sensors on known processors to that file. + - This affects defines for the built in clocks, ADC defaults, logging buffer, and on-board ALS settings +- Added a new example specific to the [EnviroDIY Monitoring Station Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/). +- Added a variety of private and protected helper functions to simplify code. + ### Removed +- **Potentially Breaking** Constructors for sensor-associated variables that don't include a pointer to the sensor. + You now *must* create the sensor instance before creating the variable and tie the variable to the sensor when creating the variable. + This functionality was never used in any examples and is unlikely to affect any users. +- **Potentially Breaking** All flavors of the variable.begin() functions. + These were not needed since all arguments should be set in the constructor or setters for individual parameters. + There was no functionality needed in a typical Arduino "begin" function - that is, nothing that needed to be performed after the hardware was active. + This functionality was never used in any examples and is unlikely to affect any users. +- Unused `_maxSamplesToAverage` parameter of the VariableArray and the `countMaxToAverage()` function. +- Unnecessary copy doc calls for inherited functions and properties. +- All overrides of the powerUp and powerDown functions that are no longer needed since all sensors have two power pins built in. +- References to the EnviroDIY data portal. +- All defines from example sketches. + - Defining values to be used by TinyGSM and/or the MQTT library here in addition to any defines in ModSensorConfig.h or in a build configuration can lead to One Definition Rule violations because the define values are used when creating the classes from the templates in TinyGSM. + ### Fixed +- Fixed major bug where sensors with two power pins where either was shared with another sensor may be turned off inappropriately when one of the other sensors was turned off. +- Correctly retry NIST sync on XBees when a not-sane timestamp is returned. +- Improved ADC bit-width handling with explicit type casting for safer arithmetic operations. +- Enhanced null-pointer validation and error handling across analog voltage reading paths. + *** ## [0.37.0] > [!note] -> This release has changes to nearly every file in the entire library (ie, hundreds of files). +> This release has changes to nearly every file in the entire library (i.e., hundreds of files). > Many of the changes are spelling and typo fixes found by implementing CSpell code spell checking. > All header files were also modified to include the new library configuration headers. ### Changed -- **BREAKING** Converted the watch-dog classes in to static classes with all static function and a **deleted constructor**. - - Any code that attempted to interact with the watchdog (ie, with a "complex loop") must now call the extendedWatchDog class directly, ie: `extendedWatchDog::resetWatchDog();` rather than `dataLogger.watchDogTimer.resetWatchDog();` +- **BREAKING** Converted the watch-dog classes into static classes with all static function and a **deleted constructor**. + - Any code that attempted to interact with the watchdog (i.e., with a "complex loop") must now call the extendedWatchDog class directly, i.e.: `extendedWatchDog::resetWatchDog();` rather than `dataLogger.watchDogTimer.resetWatchDog();` - **BREAKING** Renamed `markedLocalEpochTime` to `markedLocalUnixTime` to clarify the start of the epoch that we're marking down. - **BREAKING** Renamed `markedUTCEpochTime` to `markedUTCUnixTime` to clarify the start of the epoch that we're marking down. - **Potentially BREAKING:** Changed the requirements for a "sane" timestamp to between 2025 and 2035. - Moved the value for the sane range into two defines: `EARLIEST_SANE_UNIX_TIMESTAMP` and `LATEST_SANE_UNIX_TIMESTAMP` so they can be more easily modified and tracked. -These defines can be set in the ModSensorConfig.h file. + These defines can be set in the ModSensorConfig.h file. - **Potentially BREAKING:** For calculated variables, the calculation function will only be called if `getValue(true)` or `getValueString(true)` is called - that is, the boolean for 'update value' must explicitly be set to true to rerun the calculation function. - Previously, the calculation function was re-run every time `getValue()` or `getValueString()` was called, regardless of the update value parameter. -For calculations that were based on the results of other variables that didn't change, this was fine. -But, for calculations based on new raw readings (ie, calling `analogRead()`) a new value would be returned each time the function was called. -I realized this was a problem for analog values I tried to read that reported correctly in the first round, but were saved as junk in the csv and publishers because a new analog reading was being attempted when the thing I was attempting to read was now powered down. + For calculations that were based on the results of other variables that didn't change, this was fine. + But, for calculations based on new raw readings (i.e., calling `analogRead()`) a new value would be returned each time the function was called. + I realized this was a problem for analog values I tried to read that reported correctly in the first round, but were saved as junk in the csv and publishers because a new analog reading was being attempted when the thing I was attempting to read was now powered down. - The variable array update functions have been modified accordingly. - Verify you have the functionality you expect if you use calculated variables. - Removed the enable/disable wake pin interrupt at every sleep interval in favor of a single attachment during the begin. @@ -53,11 +271,11 @@ I realized this was a problem for analog values I tried to read that reported co - `Logger::setRTClock(UTCEpochSeconds)`; use `loggerClock::setRTClock(ts, utcOffset, epoch)` in new code. - `Logger::isRTCSane()`; use `loggerClock::isRTCSane()` in new code. - `Logger::wakeISR()`; use `loggerClock::rtcISR()` in new code. -- Support timestamps as time_t objects instead of uint32_t where every sensible. +- Support timestamps as time_t objects instead of uint32_t wherever sensible. - The size of a uint32_t is always 32 bits, but the size of the time_t object varies by processor - for some it is 32 bits, for other 64. - Changed the watchdog from a fixed 15 minute reset timer to 2x the logging interval (or at least 5 minutes). - Modified all examples which define a sercom serial port for SAMD21 processors to require the defines for the supported processors. -This should only make a difference for my compilation tests, real users should pick out only the chunks of code they want rather than leave conditional code in place. + This should only make a difference for my compilation tests, real users should pick out only the chunks of code they want rather than leave conditional code in place. - Changed some fill-in-the-blank spots in the menu example to only set the value in a single spot in the code. - Unified all defines related to the resolution of the processor ADC and moved them to the new configuration file. - Applies only to sensors using the built-in processor ADC: @@ -79,22 +297,24 @@ This should only make a difference for my compilation tests, real users should p ### Added -- **CONFIGURATION** Added a two configuration files (ModSensorConfig.h and ModSensorDebugConfig.h) that all files read from to check for configuration-related defines. -This allows Arduino IDE users who are unable to use build flags to more easily configure the library or enable debugging. +- **CONFIGURATION** Added two configuration files (ModSensorConfig.h and ModSensorDebugConfig.h) that all files read from to check for configuration-related defines. + This allows Arduino IDE users who are unable to use build flags to more easily configure the library or enable debugging. It also allows PlatformIO users to avoid the time-consuming re-compile of all their libraries required when changing build flags. - **ALL** library configuration build flags previously in any other header file for the library have been moved into the ModSensorConfig.h file, including ADC, SDI-12, and variable array options. - Added support for caching readings in RAM and sending in batches. -This currently only works on the EnviroDIY/Monitor My Watershed Publisher. -Thank you to [Thomas Watson](https://github.com/tpwrules) for this work. + This currently only works on the EnviroDIY/Monitor My Watershed Publisher. + Thank you to [Thomas Watson](https://github.com/tpwrules) for this work. - Created a new ClockSupport module with the loggerClock and epochStart static classes. - Added support for the Micro Crystal RV-8803-C7 high accuracy, ultra low power Real-Time-Clock Module. - Added support for multiple 'epoch' types starting at January 1, 1970 (UNIX), January 1, 2000 (Arduino and others), January 5, 1980 (GPST), and January 1, 1900 (NIST time and NTP protocols). - This allows you to input the epoch you're using in every single function that deals with a uint32_t or epoch type timestamp. -If no epoch start is given, it is assumed to be UNIX (January 1, 1970). + If no epoch start is given, it is assumed to be UNIX (January 1, 1970). - The supported epochs are given in the enum epochStart. - Storing _buttonPinMode internally. - Added a single define (`MS_OUTPUT`) to use for all outputs from ModularSensors. -- Added support for sending printouts and debugging to two different serial ports. This is useful for devices (like SAMD) that use a built in USB serial port which is turned off when the device sleeps. If `MS_2ND_OUTPUT` is defined, output will go to *both* `MS_2ND_OUTPUT` and to `MS_OUTPUT`. +- Added support for sending printouts and debugging to two different serial ports. + This is useful for devices (like SAMD) that use a built-in USB serial port which is turned off when the device sleeps. + If `MS_2ND_OUTPUT` is defined, output will go to *both* `MS_2ND_OUTPUT` and to `MS_OUTPUT`. - Added example code for flashing boards with a neo-pixel in the menu example. - **NEW SENSOR** Added support for [Geolux HydroCam](https://www.geolux-radars.com/hydrocam) - **NEW SENSOR** Added support for [ANB Sensors pH Sensors](https://www.anbsensors.com/) @@ -125,9 +345,9 @@ If no epoch start is given, it is assumed to be UNIX (January 1, 1970). - **Potentially BREAKING:** Removed support for any functions using the Sodaq "DateTime" class. - **Potentially BREAKING:** Removed ability to have `PRINTOUT`, `MS_DBG`, and `MS_DEEP_DBG` output going to different serial ports - Defines for `STANDARD_SERIAL_OUTPUT`, `DEBUGGING_SERIAL_OUTPUT`, and `DEEP_DEBUGGING_SERIAL_OUTPUT` are all ignored. -Use the single define `MS_OUTPUT` for all outputs. -If `MS_OUTPUT` is not defined, a default will be used (generally Serial or USBSerial). -If you do not want any output, define `MS_SILENT`. + Use the single define `MS_OUTPUT` for all outputs. + If `MS_OUTPUT` is not defined, a default will be used (generally Serial or USBSerial). + If you do not want any output, define `MS_SILENT`. - Removed internal functions for setting file times; replaced with SdFat's dateTimeCallback. - Added python script to run clang-format on all source files. @@ -144,12 +364,11 @@ If you do not want any output, define `MS_SILENT`. - **SEVERE** Sensors that require two or more power pins are treated as only requiring the first one within the variableArray and if the second or further power pin is a primary power pin with any other sensor, then the secondary pin will be turned off with the other sensor completes even if the sensor where that pin is secondary is not finished. - This is a serious issue for sensors that are both slow and require powered secondary communication adapters or relays - like the Geolux HydroCam or the ANB Sensors pH sensors. - - *Possible work-arounds* + - *Possible workarounds* - Wire required adapters to the same pin as that providing primary power. - Wire required adapters such that they are continuously powered. - If you must switch the power to both the sensor and an adapter and either the sensor power or the adapter power are shared with a pin that provides power to any other sensor, call the shared power pin the "sensor" power and the other the "adapter." - *** ## [0.36.0] @@ -183,7 +402,7 @@ If you do not want any output, define `MS_SILENT`. ### Changed - **BREAKING** Switched default clock for SAMD21 from the built-in 32bit RTC to the DS3231. -*This is not be a permanent change.* + *This is not a permanent change.* - Switched to reusable workflows for CI ### Fixed @@ -205,7 +424,7 @@ Fixed clock configuration for SAMD21 - Only polling modem for enabled parameters - INCREASED THE MAXIMUM NUMBER OF VARIABLES FROM A SINGLE SENSOR and implemented an option to set this by build flag. - This will increase memory use for the entire library. -If you are not using the GroPoint sensors which require many variables, I recommend you change this value via the build flag `-D MAX_NUMBER_VARS=8` + If you are not using the GroPoint sensors which require many variables, I recommend you change this value via the build flag `-D MAX_NUMBER_VARS=8` - Allow all WiFi modems to first attempt to connect using existing on-modem saved credentials rather than immediately sending new credentials. - Add further debug printouts to the processor stats @@ -221,7 +440,7 @@ If you are not using the GroPoint sensors which require many variables, I recomm ### Fixed -- Minor bug fixes for XBee Wifi +- Minor bug fixes for XBee WiFi - Handle no SIM card response from SIM7080G (EnviroDIY LTE Bee) - Fixed Keller debugging output. - Fixed file reference for SDFat 2.2.3 @@ -234,14 +453,14 @@ If you are not using the GroPoint sensors which require many variables, I recomm ### Changed -- Incorporated improvements to the XBee Wifi - from [neilh10](https://github.com/EnviroDIY/ModularSensors/commits?author=neilh10) +- Incorporated improvements to the XBee WiFi - from [neilh10](https://github.com/EnviroDIY/ModularSensors/commits?author=neilh10) - #347 -WiFi S6B stability - tears down TCP/IP before going to sleep, doesn't automatically poll for meta data ### Added - Added the ability to enable or disable polling of modem attached variables. -By default, all polling is off, but polling is enabled for a modem sensor when a sensor is created and attached to a modem. -This functionality is inspired from [neilh10](https://github.com/EnviroDIY/ModularSensors/commits?author=neilh10). + By default, all polling is off, but polling is enabled for a modem sensor when a sensor is created and attached to a modem. + This functionality is inspired by [neilh10](https://github.com/EnviroDIY/ModularSensors/commits?author=neilh10). ### Fixed @@ -252,7 +471,7 @@ This functionality is inspired from [neilh10](https://github.com/EnviroDIY/Modul ### Changed - **BREAKING** - Removed support for light sleep on Espressif modules. -**This changes the order of the constructor for the ESP32 and ESP8266!** + **This changes the order of the constructor for the ESP32 and ESP8266!** - The light sleep mode is non-functional anyway, and confusion over the sleep request pin was putting the board in a position not to sleep at all. - Minor tweak to clang-format - Moved all variable initialization to default header values and initializer lists @@ -324,23 +543,23 @@ This functionality is inspired from [neilh10](https://github.com/EnviroDIY/Modul - **Breaking:** Renamed the static `markedEpochTime` variable to `markedLocalEpochTime`. - This was sometimes used in "complex" loops. Code utilizing it will have to be changed. - This is part of the effort to clarify where localized and UTC time are being used. -We recommend a logger's real time clock always be set in UTC and then localized for printing and storing data. + We recommend a logger's real time clock always be set in UTC and then localized for printing and storing data. - **Breaking:** Renamed the function `setNowEpoch(uint32_t)` to `setNowUTCEpoch(uint32_t)`. - Although public, this was never intended to be used externally. - **Breaking:** Renamed the YosemiTech Y550 COD sensor as Y551. See below. - **Potentially Breaking:** Changed the default "button" interrupt pin mode from `INPUT_PULLUP` to `INPUT` and created optional arguments to the `setTestingModePin` and `setLoggerPins` functions to specify the pin mode and pull-up resistor state. - `INPUT` is the proper mode for the Mayfly. -The Mayfly has an external pull *down* on the button pin with the button being active high. -This means having the pull-up resistors on negates the button signal. -The pin mode had been set as `INPUT_PULLUP` for the button, backwards for the Mayfly, since [July of 2017](https://github.com/EnviroDIY/ModularSensors/commit/6bafb0fd149589f71ca6f46b761fe72b1f9523a6). -By some electrical luck, with the 0.x versions of the Mayfly, the external pull-down on the button pin was strong enough to out-weigh the incorrectly activated pull-up resistors and an interrupt was still registered when the button was pressed. -With a different pull-down resistor on the Mayfly 1.x, the button no longer registers with the pull-up resistors active. -So, for most of our users with Mayflies, this will be a ***fix***. -But for anyone using a different board/processor/button configuration that depended on the processor pull-up resistors, this will be a breaking change and they will need to specify the button mode in the `setTestingModePin` or `setLoggerPins` function to return to the previous behavior. + The Mayfly has an external pull *down* on the button pin with the button being active high. + This means having the pull-up resistors on negates the button signal. + The pin mode had been set as `INPUT_PULLUP` for the button, backwards for the Mayfly, since [July of 2017](https://github.com/EnviroDIY/ModularSensors/commit/6bafb0fd149589f71ca6f46b761fe72b1f9523a6). + By some electrical luck, with the 0.x versions of the Mayfly, the external pull-down on the button pin was strong enough to out-weigh the incorrectly activated pull-up resistors and an interrupt was still registered when the button was pressed. + With a different pull-down resistor on the Mayfly 1.x, the button no longer registers with the pull-up resistors active. + So, for most of our users with Mayflies, this will be a ***fix***. + But for anyone using a different board/processor/button configuration that depended on the processor pull-up resistors, this will be a breaking change and they will need to specify the button mode in the `setTestingModePin` or `setLoggerPins` function to return to the previous behavior. - Added a longer warm up time and removed some of the modem set-up to work with the ESP-IDF AT firmware versions >2.0 - Made sure that all example clock synchronization happens at noon instead of midnight. - **Renamed Classes:** Renamed several classes for internal consistency. -These are *not* breaking changes at this time; the old class names are still usable. + These are *not* breaking changes at this time; the old class names are still usable. - Rename class `MPL115A2` to `FreescaleMPL115A2` - Rename class `MPL115A2_Pressure` to `FreescaleMPL115A2_Pressure` - Rename class `MPL115A2_Temp` to `FreescaleMPL115A2_Temp` @@ -375,7 +594,7 @@ These are *not* breaking changes at this time; the old class names are still usa - Restructured SDI-12 slightly to break out the start measurement functionality into a new function. - Modified Decagon 5-TM and Meter Teros 11 to use the SDI-12 get results function rather than addSingleMeasurementResult. -This will allow both sensors to honor the 'non-concurrent' flag, if that is set. + This will allow both sensors to honor the 'non-concurrent' flag, if that is set. Previously, they would not have. - **Documentation:** Migrated to latest version of Doxygen (1.9.2). This required some changes with m.css to properly ignore the doxyfile.xml the current version generates. @@ -384,7 +603,7 @@ Previously, they would not have. - **Board:** Adds 1.0 and 1.1 as valid version numbers for the Mayfly. Does not yet support any new features of those boards. - Add a new parameter (internal variable) to the sensor base class for the number of internally calculated variables. -These are used for values that we would always calculate for a sensor and depend only on the raw results of that single sensor. + These are used for values that we would always calculate for a sensor and depend only on the raw results of that single sensor. This is separate from any calculated variables that are created on-the-fly and depend on multiple other sensors. In many cases, this is 0 and in most of the other cases the value is informational only. For the SDI-12 sensors, I'm actually using this to make sure I'm getting the number of values expected. @@ -450,8 +669,8 @@ Remove support for SoftwareWire for Atlas sensors - **Breaking:** Removed support for SoftwareWire for Atlas sensors. - The only supported version of a bit-banged (software) version of I2C removed inheritance from the core Wire library. -Without inheritance, the parseFloat functions used by the Atlas sensors will not work. -As I think this feature was completely unused for the Atlas sensors and I see no reason to use it with sensors that have completely flexible addressing, I removed it. + Without inheritance, the parseFloat functions used by the Atlas sensors will not work. + As I think this feature was completely unused for the Atlas sensors and I see no reason to use it with sensors that have completely flexible addressing, I removed it. ### Fixed @@ -480,7 +699,7 @@ Create a ModularSensors.h ### Added - Created a ModularSensors.h file to include. -This makes it much easier to install and use the library from the Arduino CLI. + This makes it much easier to install and use the library from the Arduino CLI. - Modified examples to include the ModularSensors.h file - Added continuous integration testing with the Arduino CLI @@ -493,11 +712,11 @@ Duplicate and Rename Hydros 21 ### Added - **Sensor:** Created a new module for the Meter Hydros 21. -This is exactly identical to the Decagon CTD in everything but the name. -The Decagon CTD module still exists and can be used. -No old code needs to be adjusted for this change. -Moving forward, the two can be used interchangeably. -The addition was only made to stop complaints about typing in an older name. + This is exactly identical to the Decagon CTD in everything but the name. + The Decagon CTD module still exists and can be used. + No old code needs to be adjusted for this change. + Moving forward, the two can be used interchangeably. + The addition was only made to stop complaints about typing in an older name. *** @@ -509,8 +728,8 @@ SDI-12 Timing Sensor Customization - Allow each SDI-12 sensor to set the necessary command delay for that sensor. - Per protocol, sensors are allowed to take up to 100ms after receiving a break before being ready to receive a command. -This allows each sensor to specify what delay it needs. -This was added to support conflicting delay needs; the RDO needed a short delay, the newest Meter sensors do not respond properly if the delay is added. + This allows each sensor to specify what delay it needs. + This was added to support conflicting delay needs; the RDO needed a short delay, the newest Meter sensors do not respond properly if the delay is added. - For SDI-12 sensors, add repeated attempts to start a measurement if the first attempt unexpectedly fails. *** @@ -659,7 +878,7 @@ Modem Restructuring ### Changed - Restructured modem so that it no longer operates as a sensor. - Variables tied to the modem are now effectively calculated variables and all values from the modem will be offset by 1 sending cycle (ie, the signal strength posted will always be the strength from the prior send, not the current one). + Variables tied to the modem are now effectively calculated variables and all values from the modem will be offset by 1 sending cycle (i.e., the signal strength posted will always be the strength from the prior send, not the current one). *** @@ -697,7 +916,7 @@ Watchdogs and More - A watch-dog timer has been implemented for both the AVR and SAMD21 (and 51) boards to restart the boards in case of failure during logging - The watch-dog is turned off during sleep to save power, so recovery is only possible if the failure is while the processor is awake. - Added support for Meter Teros 11 soil moisture and temperature sensor -- Implemented a function to verify that UUID's are at least correctly formed and unique - though it does not verify that they are valid. +- Implemented a function to verify that UUIDs are at least correctly formed and unique - though it does not verify that they are valid. - Pushing to the master branch of this repo will now also cause a re-run of the travis script that updates the EnviroDIY "Libraries" repository. - Added debugging variables to modems to track how long they are powered/active. @@ -736,7 +955,7 @@ Modem Simplification - Digi XBee and XBee3 cellular modems of all types running in transparent mode - Digi XBee3 cellular LTE-M modems in bypass mode (this is *in addition* to transparent mode) - Digi XBee cellular 3G global modems in bypass mode (this is *in addition* to transparent mode) - - Digi XBee Wifi S6B modules + - Digi XBee WiFi S6B modules - Sodaq UBee LTE-M (SARA R410M) - Sodaq UBee 3G (SARA U201) - Sodaq 2GBee R6/R7 @@ -827,7 +1046,11 @@ Support for all Atlas Scientific I2C sensors, compiler-safe begin functions - RTD (temperature - Created empty constructors for the logger, publisher, variable array, and variable classes and all of their subclasses. For all classes created a corresponding "begin" function to set internal class object values. - See note for more details: - - The order of input arguments for all variable objects has changed. For variable subclasses (ie, variables from sensors), there is no change to the user. ****For calculated variable objects, all code must be updated!**** Please check the structure in the examples! Older code will compile without error but the variable metadata fields will be incorrectly populated. + - The order of input arguments for all variable objects has changed. + For variable subclasses (i.e., variables from sensors), there is no change to the user. + **For calculated variable objects, all code must be updated!** + Please check the structure in the examples! + Older code will compile without error but the variable metadata fields will be incorrectly populated. - Very preliminary support for SD cards with switchable power ### Removed @@ -841,14 +1064,15 @@ Support for all Atlas Scientific I2C sensors, compiler-safe begin functions ### Known Issues -- Running some I2C sensors on switched power will cause unrecoverable hangs at the first call to any other I2C peripheral (ie, the DS3231 RTC) after sensor power is turned off. This is a hardware problem and is un-fixable within this library. +- Running some I2C sensors on switched power will cause unrecoverable hangs at the first call to any other I2C peripheral (i.e., the DS3231 RTC) after sensor power is turned off. + This is a hardware problem and is un-fixable within this library. - The sensor class and all of its subclasses still require input arguments in the constructor. *** ## [0.19.6] - 2019-02-27 -Modem Improvements & ADS1X15 Generalization +Modem Improvements & ADS1x15 Generalization [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2579301.svg)](https://doi.org/10.5281/zenodo.2579301) @@ -878,7 +1102,7 @@ Bug fix and example re-working ### Fixed -- Fixes bug in sending data to the WikiWatershed / [Monitor My Watershed](https://monitormywatershed.org/) data sharing portal. +- Fixes bug in sending data to [Monitor My Watershed](https://monitormywatershed.org). *** @@ -1146,3 +1370,5 @@ Our first release of the modular sensors library to support easily logging data + + diff --git a/README.md b/README.md index 110051def..1d0d73cb3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ There is extensive documentation available in the [ModularSensors github pages]( - [The EnviroDIY ModularSensors Library](#the-envirodiy-modularsensors-library) - [Supported Sensors](#supported-sensors) - [Data Endpoints](#data-endpoints) - - [Supported Cellular/Wifi Modules:](#supported-cellularwifi-modules) + - [Supported Cellular/WiFi Modules:](#supported-cellularwifi-modules) - [Contributing](#contributing) - [License](#license) - [Acknowledgments](#acknowledgments) @@ -45,6 +45,7 @@ For some generalized information about attaching sensors to an Arduino style boa - [Processor Metrics: battery voltage, free RAM, sample count](https://envirodiy.github.io/ModularSensors/group__sensor__processor.html) - [Maxim DS3231: real time clock](https://envirodiy.github.io/ModularSensors/group__sensor__ds3231.html) +- [Alphasense IRC-A1: CO2 and temperature, via TI ADS1115](https://envirodiy.github.io/ModularSensors/group__sensor__alphasense__co2.html) - [Analog Electrical Conductivity: conductivity](https://envirodiy.github.io/ModularSensors/group__sensor__analog__cond.html) - [ANB Sensors pH Sensor: pH](https://envirodiy.github.io/ModularSensors/group__sensor__anb__ph.html) - [AOSong AM2315: humidity & temperature](https://envirodiy.github.io/ModularSensors/group__sensor__am2315.html) @@ -76,17 +77,20 @@ For some generalized information about attaching sensors to an Arduino style boa - [Nanolevel](https://envirodiy.github.io/ModularSensors/group__sensor__nanolevel.html) - [MaxBotix MaxSonar: water level](https://envirodiy.github.io/ModularSensors/group__sensor__maxbotix.html) - [Maxim DS18: temperature](https://envirodiy.github.io/ModularSensors/group__sensor__ds18.html) -- [Measurement Specialties MS5803: pressure and temperature](https://envirodiy.github.io/ModularSensors/group__sensor__ms5803.html) +- [Measurement Specialties/TE Connectivity MS5803: pressure and temperature](https://envirodiy.github.io/ModularSensors/group__sensor__ms5803.html) +- [Measurement Specialties/TE Connectivity MS5837: pressure and temperature (deployed as Blue Robotics Bar02/Bar30)](https://envirodiy.github.io/ModularSensors/group__sensor__ms5837.html) - Meter Environmental Soil Moisture Probes: soil Ea and volumetric water content - [Meter ECH2O 5TM](https://envirodiy.github.io/ModularSensors/group__sensor__fivetm.html) - [Meter Teros 11](https://envirodiy.github.io/ModularSensors/group__sensor__teros11.html) - [Meter Environmental Hydros 21: conductivity, temperature & depth](https://envirodiy.github.io/ModularSensors/group__sensor__hydros21.html) - [Northern Widget Tally Event Counter: number of events](https://envirodiy.github.io/ModularSensors/group__sensor__tally.html) - [PaleoTerra Redox Sensor: redox potential](https://envirodiy.github.io/ModularSensors/group__sensor__pt__redox.html) +- [Processor Analog Voltage: external voltage using built-in ADC](https://envirodiy.github.io/ModularSensors/group__sensor__processor__analog.html) - [Sensirion SHT40: humidity & temperature](https://envirodiy.github.io/ModularSensors/group__sensor__sht4x.html) - [TI ADS1115: external voltage with support for divided current](https://envirodiy.github.io/ModularSensors/group__sensor__ads1x15.html) - [TI INA219: current, voltage, and power draw](https://envirodiy.github.io/ModularSensors/group__sensor__ina219.html) - [Turner Cyclops-7F: various parameters](https://envirodiy.github.io/ModularSensors/group__sensor__cyclops.html) +- [Turner Turbidity Plus: turbidity, via TI ADS1115](https://envirodiy.github.io/ModularSensors/group__sensor__turbidity__plus.html) - [Vega Puls 21: radar distance](https://envirodiy.github.io/ModularSensors/group__sensor__vega__puls21.html) - [Yosemitech: water quality sensors](https://envirodiy.github.io/ModularSensors/group__yosemitech__group.html) - [Y502-A or Y504-A: Optical DO and Temperature](https://envirodiy.github.io/ModularSensors/group__sensor__y504.html) @@ -106,9 +110,9 @@ For some generalized information about attaching sensors to an Arduino style boa ## Data Endpoints Within ModularSensors, the "dataPublisher" objects add the functionality to send data to remote web services. -The currently supported services are the [Monitor My Watershed data portal](http://data.envirodiy.org/), [ThingSpeak](https://thingspeak.com/), the [Ubidots IoT platform](https://ubidots.com), [Amazon Web Services IoT Core](https://aws.amazon.com/iot-core/), and [Amazon Web Services Simple Storage Service (S3)](https://aws.amazon.com/s3/). +The currently supported services are [Monitor My Watershed](https://monitormywatershed.org), [ThingSpeak](https://thingspeak.com), the [Ubidots IoT platform](https://ubidots.com), [Amazon Web Services IoT Core](https://aws.amazon.com/iot-core), and [Amazon Web Services Simple Storage Service (S3)](https://aws.amazon.com/s3). -- [Monitor My Watershed/EnviroDIY Data Portal](https://envirodiy.github.io/ModularSensors/class_enviro_d_i_y_publisher.html) +- [Monitor My Watershed](https://envirodiy.github.io/ModularSensors/class_monitor_my_watershed_publisher.html) - [ThingSpeak](https://envirodiy.github.io/ModularSensors/class_thing_speak_publisher.html) - [Ubidots IoT platform](https://envirodiy.github.io/ModularSensors/class_ubidots_publisher.html) - [AWS IoT Core](https://envirodiy.github.io/ModularSensors/class_a_w_s___io_t___publisher.html) @@ -116,11 +120,11 @@ The currently supported services are the [Monitor My Watershed data portal](http - [AWS S3](https://envirodiy.github.io/ModularSensors/class_s3_presigned_publisher.html) - The S3 publisher requires you to provide a function that will return an updated pre-signed URL to publish to. - The S3 publisher does **NOT** publish any sensor data by default. -It is intended for publishing images. + It is intended for publishing images. -## Supported Cellular/Wifi Modules: +## Supported Cellular/WiFi Modules: All cellular and wifi support is through the [TinyGSM](https://github.com/vshymanskyy/TinyGSM) library. For information common to all modems and for tables of the proper class, baud rate, and pins to uses, see the [Modem Notes page](https://envirodiy.github.io/ModularSensors/page_modem_notes.html). @@ -133,7 +137,7 @@ For information common to all modems and for tables of the proper class, baud ra - [Digi XBee® Cellular 3G](https://hub.digi.com/support/products/digi-xbee/digi-xbee-cellular-3g/) [*obsolete*] - [Digi XBee® Cellular LTE Cat 1 (Verizon)](https://hub.digi.com/support/products/digi-xbee/digi-xbee-cellular-lte-cat-1/) [*obsolete*] - [Digi XBee® Wi-Fi (S6B)](https://hub.digi.com/support/products/digi-xbee/digi-xbee-wi-fi/) [*obsolete*] -- [Espressif Wifi SoC Modules](https://envirodiy.github.io/ModularSensors/group__modem__espressif.html) +- [Espressif WiFi SoC Modules](https://envirodiy.github.io/ModularSensors/group__modem__espressif.html) - Includes the [ESP8266](https://envirodiy.github.io/ModularSensors/group__modem__esp8266.html), [ESP32, ESP32-C3, ESP32-C2, ESP32-C6, and ESP32-S2](https://envirodiy.github.io/ModularSensors/group__modem__esp32.html) - Requires Espressif modules to be programmed with the [latest AT firmware provided by Espressif](https://github.com/espressif/esp-at). - These Espressif modules are **not** supported as primary processors, only as external communication modules. @@ -150,9 +154,9 @@ For information common to all modems and for tables of the proper class, baud ra Open an [issue](https://github.com/EnviroDIY/ModularSensors/issues) to suggest and discuss potential changes/additions. Feel free to open issues about any bugs you find or any sensors you would like to have added. -If you would like to directly help with the coding development of the library, there are some [tips here](https://envirodiy.github.io/ModularSensors/page_developer_setup.html) on how to set up PlatformIO so you can fork the library and test programs while in the library repo. +If you would like to directly help with the coding development of the library, there are some [tips and instructions here](https://envirodiy.github.io/ModularSensors/page_developer_setup.html) on how to set up PlatformIO so you can fork the library and test programs while in the library repo. Please *take time to familiarize yourself with the [terminology, classes and data structures](https://envirodiy.github.io/ModularSensors/page_library_terminology.html) this library uses*. -This library is built to fully take advantage of Objecting Oriented Programing (OOP) approaches and is larger and more complicated than many Arduino libraries. +This library is built to fully take advantage of Object-Oriented Programming (OOP) approaches and is larger and more complicated than many Arduino libraries. There is doxygen-created documentation on our [github pages](https://envirodiy.github.io/ModularSensors/index.html) and an *enormous* number of comments and debugging printouts in the code itself to help you get going. ## License diff --git a/build-menu-configurations.ps1 b/build-menu-configurations.ps1 deleted file mode 100644 index 523bf4bb0..000000000 --- a/build-menu-configurations.ps1 +++ /dev/null @@ -1,181 +0,0 @@ -$ErrorActionPreference = "Stop" - -mkdir temp -Force -mkdir temp/menu_a_la_carte -Force - -$pioCommand = "pio pkg install --library" -$pioCommand += ';$?' - -$pioResult = Invoke-Expression $pioCommand -if (("$pioResult".EndsWith('False')) -or (-not $pioResult)){ - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Write-Host "PlatformIO Library Installation Failed" -ForegroundColor Red - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Exit 1 -} - -$pioCommand = "pio pkg update" -$pioCommand += ';$?' - -$pioResult = Invoke-Expression $pioCommand -if (("$pioResult".EndsWith('False')) -or (-not $pioResult)){ - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Write-Host "PlatformIO Library Update Failed" -ForegroundColor Red - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Exit 1 -} - -$pioCommand = "pio run --project-conf=""continuous_integration/platformio.ini"" --verbose" -$pioCommand += ';$?' - -$modemFlags = @(` - 'BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT', ` - 'BUILD_MODEM_DIGI_XBEE_LTE_BYPASS', ` - 'BUILD_MODEM_DIGI_XBEE_3G_BYPASS', ` - 'BUILD_MODEM_DIGI_XBEE_WIFI', ` - 'BUILD_MODEM_ESPRESSIF_ESP8266', ` - 'BUILD_MODEM_QUECTEL_BG96', ` - 'BUILD_MODEM_SEQUANS_MONARCH', ` - 'BUILD_MODEM_SIM_COM_SIM800', ` - 'BUILD_MODEM_SIM_COM_SIM7000', ` - 'BUILD_MODEM_SODAQ2_G_BEE_R6', ` - 'BUILD_MODEM_SODAQ_UBEE_R410M', ` - 'BUILD_MODEM_SODAQ_UBEE_U201') - -Foreach ($modemFlag in $modemFlags) -{ - $tempFile = "temp/menu_a_la_carte/main.cpp" - if (Test-Path $tempFile) { - Remove-Item $tempFile - } - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "Modifying source for $modemFlag" -ForegroundColor Green - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - $originalMenu = Get-Content -Path "examples/menu_a_la_carte/menu_a_la_carte.ino" -Encoding UTF8 -Raw - $newHeading = "#define $modemFlag`n#define BUILD_TEST_PRE_NAMED_VARS`n" - $newHeading += $originalMenu - $newHeading | Add-Content -Path $tempFile -Encoding UTF8 - - # Write-Output "First few lines of source" - # Get-Content $tempFile | select -Skip 10 - - $pioResult = Invoke-Expression $pioCommand - if (("$pioResult".EndsWith('False')) -or (-not $pioResult)){ - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Write-Host "PlatformIO Build Failed" -ForegroundColor Red - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Remove-Item –path temp –recurse - Exit 1 - } -} - -$sensorFlags = @(` - 'BUILD_SENSOR_AO_SONG_AM2315', ` - 'BUILD_SENSOR_AO_SONG_DHT', ` - 'BUILD_SENSOR_APOGEE_SQ212', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_CO2', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_DO', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_ORP', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_PH', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_RTD', ` - 'BUILD_SENSOR_ATLAS_SCIENTIFIC_EC', ` - 'BUILD_SENSOR_BOSCH_BME280', ` - 'BUILD_SENSOR_CAMPBELL_OBS3', ` - 'BUILD_SENSOR_DECAGON_ES2', ` - 'BUILD_SENSOR_TIADS1X15', ` - 'BUILD_SENSOR_FREESCALE_MPL115A2', ` - 'BUILD_SENSOR_IN_SITU_RDO', ` - 'BUILD_SENSOR_IN_SITU_TROLL_SDI12A', ` - 'BUILD_SENSOR_KELLER_ACCULEVEL', ` - 'BUILD_SENSOR_KELLER_NANOLEVEL', ` - 'BUILD_SENSOR_MAX_BOTIX_SONAR', ` - 'BUILD_SENSOR_MAXIM_DS18', ` - 'BUILD_SENSOR_MEA_SPEC_MS5803', ` - 'BUILD_SENSOR_DECAGON_5TM', ` - 'BUILD_SENSOR_DECAGON_CTD', ` - 'BUILD_SENSOR_METER_TEROS11', ` - 'BUILD_SENSOR_PALEO_TERRA_REDOX', ` - 'BUILD_SENSOR_RAIN_COUNTER_I2C', ` - 'BUILD_SENSOR_TALLY_COUNTER_I2C', ` - 'BUILD_SENSOR_SENSIRION_SHT4X', ` - 'BUILD_SENSOR_TI_INA219', ` - 'BUILD_SENSOR_TURNER_CYCLOPS', ` - 'BUILD_SENSOR_ANALOG_ELEC_CONDUCTIVITY', ` - 'BUILD_SENSOR_YOSEMITECH_Y504', ` - 'BUILD_SENSOR_YOSEMITECH_Y510', ` - 'BUILD_SENSOR_YOSEMITECH_Y511', ` - 'BUILD_SENSOR_YOSEMITECH_Y514', ` - 'BUILD_SENSOR_YOSEMITECH_Y520', ` - 'BUILD_SENSOR_YOSEMITECH_Y532', ` - 'BUILD_SENSOR_YOSEMITECH_Y533', ` - 'BUILD_SENSOR_YOSEMITECH_Y551', ` - 'BUILD_SENSOR_YOSEMITECH_Y560', ` - 'BUILD_SENSOR_YOSEMITECH_Y4000', ` - 'BUILD_SENSOR_ZEBRA_TECH_D_OPTO') - -Foreach ($sensorFlag in $sensorFlags) -{ - $tempFile = "temp/menu_a_la_carte/main.cpp" - if (Test-Path $tempFile) { - Remove-Item $tempFile - } - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "Modifying source for $sensorFlag" -ForegroundColor Green - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - $originalMenu = Get-Content -Path "examples/menu_a_la_carte/menu_a_la_carte.ino" -Encoding UTF8 -Raw - $newHeading = "#define BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT`n#define BUILD_TEST_PRE_NAMED_VARS`n#define $sensorFlag`n" - $newHeading += $originalMenu - $newHeading | Add-Content -Path $tempFile -Encoding UTF8 - - # # Write-Output "First few lines of source" - # Get-Content $tempFile | select -Skip 10 - - $pioResult = Invoke-Expression $pioCommand - if (("$pioResult".EndsWith('False')) -or (-not $pioResult)){ - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Write-Host "PlatformIO Build Failed" -ForegroundColor Red - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Remove-Item –path temp –recurse - Exit 1 - } -} - -$publisherFlag = @(` - 'BUILD_PUB_ENVIRO_DIY_PUBLISHER', ` - 'BUILD_PUB_DREAM_HOST_PUBLISHER', ` - 'BUILD_PUB_THING_SPEAK_PUBLISHER') - -Foreach ($publisherFlag in $publisherFlags) -{ - $tempFile = "temp/menu_a_la_carte/main.cpp" - if (Test-Path $tempFile) { - Remove-Item $tempFile - } - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "Modifying source for $publisherFlag" -ForegroundColor Green - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Cyan - $originalMenu = Get-Content -Path "examples/menu_a_la_carte/menu_a_la_carte.ino" -Encoding UTF8 -Raw - $newHeading = "#define BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT`n#define BUILD_TEST_PRE_NAMED_VARS`n#define $publisherFlag`n" - $newHeading += $originalMenu - $newHeading | Add-Content -Path $tempFile -Encoding UTF8 - - # Write-Output "First few lines of source" - # Get-Content $tempFile | select -Skip 10 - - $pioResult = Invoke-Expression $pioCommand - if (("$pioResult".EndsWith('False')) -or (-not $pioResult)){ - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Write-Host "PlatformIO Build Failed" -ForegroundColor Red - Write-Host "----------------------------------------------------------------------------" -ForegroundColor Red - Remove-Item –path temp –recurse - Exit 1 - } -} - -Remove-Item –path temp –recurse diff --git a/continuous_integration/dependencies.json b/continuous_integration/dependencies.json index 11c195fd1..2968c8e12 100644 --- a/continuous_integration/dependencies.json +++ b/continuous_integration/dependencies.json @@ -1,5 +1,5 @@ { - "action_cache_version": 38, + "action_cache_version": 49, "dependencies": [ { "name": "EnviroDIY_DS3231", @@ -116,7 +116,7 @@ "name": "Adafruit ADS1X15", "owner": "adafruit", "url": "https://github.com/adafruit/Adafruit_ADS1X15", - "version": "~2.6.0", + "version": "~2.6.2", "note": "Driver for TI's ADS1X15: 12 and 16-bit Differential or Single-Ended ADC with PGA and Comparator.", "authors": [ "Adafruit" @@ -162,7 +162,7 @@ "owner": "adafruit", "library id": "19", "url": "https://github.com/adafruit/DHT-sensor-library", - "version": "~1.4.6", + "version": "~1.4.7", "note": "AOSong DHT Sensor Library by Adafruit", "authors": [ "Adafruit" @@ -234,7 +234,7 @@ "owner": "milesburton", "library id": "54", "url": "https://github.com/milesburton/Arduino-Temperature-Control-Library", - "version": "~4.0.4", + "version": "~4.0.6", "note": "DallasTemperature - Arduino Library for Dallas Temperature ICs (DS18B20, DS18S20, DS1822, DS1820)", "authors": [ "Guil Barros", @@ -277,6 +277,18 @@ "Sara Damiano" ] }, + { + "name": "MS5837", + "owner": "robtillaart", + "library id": "6205", + "url": "https://github.com/RobTillaart/MS5837", + "version": "~0.3.2", + "note": "Arduino library for the MS5837 temperature and pressure sensor and compatibles", + "authors": [ + "Rob Tillaart" + ], + "frameworks": "arduino" + }, { "name": "Tally_Library_I2C", "version": "https://github.com/EnviroDIY/Tally_Library.git#Dev_I2C", @@ -293,7 +305,7 @@ "owner": "envirodiy", "library id": "1824", "url": "https://github.com/EnviroDIY/SensorModbusMaster", - "version": "~1.6.5", + "version": "~1.7.0", "note": "EnviroDIY SensorModbusMaster - Arduino library for communicating via modbus with the Arduino acting as the modbus master.", "authors": [ "Sara Damiano" @@ -305,7 +317,7 @@ "owner": "envirodiy", "library id": "5439", "url": "https://github.com/EnviroDIY/KellerModbus", - "version": "~0.2.5", + "version": "~0.2.7", "note": "Arduino library for communication with Keller pressure and water level sensors via Modbus.", "authors": [ "Anthony Aufdenkampe" @@ -316,7 +328,7 @@ "owner": "envirodiy", "library id": "2078", "url": "https://github.com/EnviroDIY/YosemitechModbus", - "version": "~0.5.2", + "version": "~0.5.4", "note": "Arduino library for communication with Yosemitech sensors via Modbus.", "authors": [ "Sara Damiano", @@ -328,7 +340,7 @@ "name": "GroPointModbus", "owner": "envirodiy", "url": "https://github.com/EnviroDIY/GroPointModbus", - "version": "~0.1.3", + "version": "~0.1.5", "note": "A library to use an Arduino as a master to control and communicate via modbus with GroPoint soil moisture sensors.", "authors": [ "Anthony Aufdenkampe" @@ -350,7 +362,7 @@ "name": "fast_math", "owner": "robtillaart", "url": "https://github.com/RobTillaart/fast_math", - "version": "~0.2.4", + "version": "~0.2.5", "note": "Arduino library for fast math algorithms.", "authors": [ "Rob Tillaart" @@ -362,7 +374,7 @@ "name": "ANBSensorsModbus", "owner": "envirodiy", "url": "https://github.com/EnviroDIY/ANBSensorsModbus", - "version": "~0.2.5", + "version": "~0.4.2", "note": "A library for communicating with pH sensors manufactured by ANB Sensors using Modbus.", "authors": [ "Sara Damiano" diff --git a/continuous_integration/generate_job_matrix.py b/continuous_integration/generate_job_matrix.py index 45d4fc56a..56cf426ca 100644 --- a/continuous_integration/generate_job_matrix.py +++ b/continuous_integration/generate_job_matrix.py @@ -2,7 +2,6 @@ # %% import json import shutil -import re from typing import List from platformio.project.config import ProjectConfig import re @@ -30,6 +29,7 @@ workspace_path = os.path.abspath(os.path.realpath(workspace_dir)) print(f"Workspace Path: {workspace_path}") + # %% # The examples directory examples_dir = "./examples/" @@ -102,12 +102,24 @@ pio_to_acli = json.load(f) # Find all of the non-menu examples -non_menu_examples = [ - f - for f in os.listdir(examples_path) - if os.path.isdir(os.path.join(examples_path, f)) - and f not in [".history", "logger_test", "archive", "tests", menu_example_name] -] +non_menu_examples = [] +for root, subdirs, files in os.walk(examples_path): + folder_name = os.path.basename(root) + if folder_name in { + ".history", + "logger_test", + "archive", + "tests", + menu_example_name, + }: + subdirs.clear() # Prevent os.walk from descending into excluded directories + continue + for filename in files: + file_path = os.path.join(root, filename) + if filename == f"{folder_name}.ino": + non_menu_examples.append(os.path.realpath(root)) + if use_verbose: + print(f"::debug::\t- example: {filename} (full path: {file_path})") # %% # read configurations based on existing files and environment variables @@ -305,18 +317,23 @@ def snake_to_camel(snake_str): for compiler, command_list in zip( compilers, [arduino_ex_commands, pio_ex_commands] ): - if example == "data_saving": - # skip this one until I get it updated - pass - else: - command_list.extend( - create_logged_command( - compiler=compiler, - group_title=example, - code_subfolder=example, - pio_env=pio_env, - ) + # Skip examples that need to be updated or don't apply + example_name = os.path.basename(example).lower() + if "data_saving" in example_name: + continue # skip until updated + if "mayfly" in example_name and pio_env != "mayfly": + continue # skip mayfly examples on non-mayfly builds + if "drwi" in example_name and pio_env not in ["mayfly", "stonefly"]: + continue # skip drwi examples on non-EnviroDIY processor builds + + command_list.extend( + create_logged_command( + compiler=compiler, + group_title=example, + code_subfolder=example, + pio_env=pio_env, ) + ) arduino_job_matrix.append( { @@ -424,7 +441,7 @@ def extend_pio_config(added_envs): "NO_SENSORS", ] all_publisher_flags = [ - "BUILD_PUB_ENVIRO_DIY_PUBLISHER", + "BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER", ] diff --git a/continuous_integration/platformio.ini b/continuous_integration/platformio.ini index ee2c745ed..38079a19a 100644 --- a/continuous_integration/platformio.ini +++ b/continuous_integration/platformio.ini @@ -14,7 +14,7 @@ boards_dir = ${platformio.core_dir}/boards [env] framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_compat_mode = soft build_flags = -Wall diff --git a/cspell.json b/cspell.json index b486606fa..07552df7f 100644 --- a/cspell.json +++ b/cspell.json @@ -79,6 +79,7 @@ "_TURB_", "Acculevel", "Adafruit", + "Adalogger", "ADCSRA", "Alconox", "Alphasense", @@ -124,6 +125,7 @@ "Dragino", "DreamHost", "DRWI", + "Duemilanove", "eeprom", "ENABLEINTERRUPT", "enviro_diy", @@ -141,6 +143,7 @@ "formazin", "Freescale", "FREQM", + "FTDI", "GCKL", "GCKLGEN", "GCLK", @@ -158,7 +161,7 @@ "IMEI", "IMSI", "INSITU", - "kilaohms", + "kiloohms", "LOGGERBASE", "LOGGERMODEM", "LTEBee", @@ -168,16 +171,22 @@ "Mbili", "MCLK", "menusnip", + "MGPL", "microeinstein", "microeinsteins", "micromoles", "microsiemen", "microsiemens", + "microvolts", + "milliamp", + "milliwatt", + "milliwatts", "Modbus", "MODSENSORCONFIG", "MODSENSORDEBUGGER", "MODSENSORINTERRUPTS", "MODULARSENSORS", + "MOSI", "MQTT", "Nanolevel", "NDIR", @@ -220,10 +229,13 @@ "RSSI", "RUNSTBY", "SAMD", + "Sanguino", + "SARAR4", "sdamiano", "SDCARD", "SDHC", "Seeed", + "Seeeduino", "Sensirion", "Sequans", "SERCOM", @@ -252,8 +264,11 @@ "Testato", "ThingSpeak", "tiads1x15", + "Tillaart", "TINYGSM", "TINYUSB", + "TurbHigh", + "TurbLow", "u_blox", "u-blox", "ubee", @@ -263,6 +278,7 @@ "USBCON", "USBE", "USBVIRTUAL", + "USCM", "USGS", "UUID", "VEGAPULS", @@ -282,5 +298,11 @@ "µsec" ], "ignoreWords": [], - "ignorePaths": ["platformio.ini", "*doxyfile*", ".vscode"] + "ignorePaths": [ + "platformio.ini", + "*doxyfile*", + ".vscode", + "library.json", + "**/lib/**" + ] } diff --git a/docs/DoxygenLayout.xml b/docs/DoxygenLayout.xml index f9a2e60e3..5829aea9c 100644 --- a/docs/DoxygenLayout.xml +++ b/docs/DoxygenLayout.xml @@ -15,7 +15,6 @@ - diff --git a/docs/FAQ/Arduino-Streams.md b/docs/FAQ/Arduino-Streams.md index 8ba7f8436..ee5b72526 100644 --- a/docs/FAQ/Arduino-Streams.md +++ b/docs/FAQ/Arduino-Streams.md @@ -112,7 +112,7 @@ enableInterrupt(rx_pin, neoSSerial1ISR, CHANGE); ## Neutered SoftwareSerial [The EnviroDIY modified version of SoftwareSerial](https://github.com/EnviroDIY/SoftwaterSerial_ExternalInts) removes direct interrupt control from the SoftwareSerial library, making it dependent on another interrupt library, but able to be compiled with ModularSensors. -This is, _by far_, the _least_ stable serial port option and should only be used on sensors that are not very picky about the quality of the serial stream or that only require one-way communication (ie, only posting data rather than needing to receive commands). +This is, _by far_, the _least_ stable serial port option and should only be used on sensors that are not very picky about the quality of the serial stream or that only require one-way communication (i.e., only posting data rather than needing to receive commands). To use the EnviroDIY modified version of SoftwareSerial: diff --git a/docs/FAQ/Code-Debugging.md b/docs/FAQ/Code-Debugging.md index 49fc5fc77..d99e74cd1 100644 --- a/docs/FAQ/Code-Debugging.md +++ b/docs/FAQ/Code-Debugging.md @@ -8,7 +8,8 @@ When using PlatformIO or other build systems that allow you to define build flag - `-D MS_SERIAL_OUTPUT=SerialX` to define a serial or other stream to use for all printout, debugging or otherwise - `-D MS_2ND_OUTPUT=SerialX` to define a second serial to output identical debugging output to - - This is very helpful for boards that use a built-in USB adapter. Assigning a secondary output that can be attached to a secondary TTL-to-USB adapter can make debugging much easier. + - This is very helpful for boards that use a built-in USB adapter. + Assigning a secondary output that can be attached to a secondary TTL-to-USB adapter can make debugging much easier. - `-D MS_SILENT` to suppress *ALL* output. There is also a debugging - and sometimes deep debugging - define for each component. @@ -16,7 +17,7 @@ The debugging flags are generally named as MS_xxx_DEBUG`, where xxxxx is the nam ## Arduino IDE -For intense _code_ debugging for any individual component of the library (sensor communication, modem communication, variable array functions, etc), open the source file header (\*.h), for that component. +For intense **code** debugging for any individual component of the library (sensor communication, modem communication, variable array functions, etc), open the source file header (\*.h) for that component. Find the line `// #define MS_xxx_DEBUG`, where xxxxx is the name of the header file to debug - in all caps with spaces removed. Remove the two comment slashes from that line. Then recompile and upload your code. diff --git a/docs/FAQ/Power-Parasites.md b/docs/FAQ/Power-Parasites.md index f0754c968..1c2513b51 100644 --- a/docs/FAQ/Power-Parasites.md +++ b/docs/FAQ/Power-Parasites.md @@ -4,22 +4,20 @@ When deploying a logger out into the wild and depending on only battery or solar This library assumes that the main power/Vcc supply to each sensor can be turned on by setting its powerPin high and off by setting its powerPin low. For most well-designed sensors, this should stop all power draw from the sensor. Real sensors, unfortunately, aren't as well designed as one might hope and some sensors (and particularly RS485 adapters) can continue to suck power from by way of high or floating data pins. -For most sensors, this library attempts to set all data pins low when sending the sensors and then logger to sleep. +For most sensors, this library attempts to set all data pins low when putting the sensors and then the logger to sleep. -If you are still seeing "parasitic" power draw, here are some work-arounds you can try: +If you are still seeing "parasitic" power draw, here are some workarounds you can try: - For sensors (and adapters) drawing power over a serial line: - Write-out your entire loop function. -(Don't just use `logData()`.) + (Don't just use `logData()`.) - Add a `SerialPortName.begin(BAUD);` statement to the beginning of your loop, before `sensorsPowerUp()`. - After `sensorsPowerDown()` add `SerialPortName.end(BAUD);`. - After "ending" the serial communication, explicitly set your Rx and Tx pins low using `digitalWrite(#, LOW);`. - For sensors drawing power over I2C: - Many (most?) boards have external pull-up resistors on the hardware I2C/Wire pins which cannot be disconnected from the main power supply. -This means I2C parasitic power draw is best solved via hardware, not software. + This means I2C parasitic power draw is best solved via hardware, not software. - Use a specially designed I2C isolator - Use a generic opto-isolator or other type of isolator on both the SCL and SDA lines - - In this future, this library _may_ offer the option of using software I2C, which would allow you to use the same technique as is currently usable to stop serial parasitic draw. -Until such an update happens, however, hardware solutions are required. - -The ["data_saving"](@todo add link to loop of data saving example) example shows setting ending a serial stream and setting pins low to prevent an RS485 adapter from drawing power during sleep. + - In the future, this library _may_ offer the option of using software I2C, which would allow you to use the same technique as is currently usable to stop serial parasitic draw. + Until such an update happens, however, hardware solutions are required. diff --git a/docs/FAQ/Processor-Compatibility.md b/docs/FAQ/Processor-Compatibility.md index 4c302ed2d..f35a4a391 100644 --- a/docs/FAQ/Processor-Compatibility.md +++ b/docs/FAQ/Processor-Compatibility.md @@ -7,39 +7,163 @@ - [Processor Compatibility](#processor-compatibility) + - [Processor Configuration and Adding Support](#processor-configuration-and-adding-support) + - [KnownProcessors.h Documentation](#knownprocessorsh-documentation) + - [Adding New Processor Support](#adding-new-processor-support) + - [Board-Specific Parameters](#board-specific-parameters) + - [Configuration Override](#configuration-override) - [AtMega1284p (EnviroDIY Mayfly, Sodaq Mbili, Mighty 1284)](#atmega1284p-envirodiy-mayfly-sodaq-mbili-mighty-1284) + - [Specific Supported AtMega1284p Boards](#specific-supported-atmega1284p-boards) + - [AtMega1284p Processor Information](#atmega1284p-processor-information) - [AtSAMD21 (Arduino Zero, Adafruit Feather M0, Sodaq Autonomo)](#atsamd21-arduino-zero-adafruit-feather-m0-sodaq-autonomo) + - [Specific Supported AtSAMD21 Boards](#specific-supported-atsamd21-boards) + - [AtSAMD21 Processor Information](#atsamd21-processor-information) + - [AtSAMD51 (Adafruit Feather M4, EnviroDIY Stonefly)](#atsamd51-adafruit-feather-m4-envirodiy-stonefly) + - [Specific Supported AtSAMD51 Boards](#specific-supported-atsamd51-boards) + - [AtSAMD51 Processor Information](#atsamd51-processor-information) - [AtMega2560 (Arduino Mega)](#atmega2560-arduino-mega) + - [Specific Supported AtMega2560 Boards](#specific-supported-atmega2560-boards) + - [AtMega2560 Processor Information](#atmega2560-processor-information) - [AtMega644p (Sanguino)](#atmega644p-sanguino) - - [AtMega328p (Arduino Uno, Duemilanove, LilyPad, Mini, Seeeduino Stalker, etc)](#atmega328p-arduino-uno-duemilanove-lilypad-mini-seeeduino-stalker-etc) + - [Specific Supported AtMega644p Boards](#specific-supported-atmega644p-boards) + - [AtMega644p Processor Information](#atmega644p-processor-information) + - [AtMega328p (Arduino Uno, Seeeduino Stalker, etc)](#atmega328p-arduino-uno-seeeduino-stalker-etc) + - [Specific Supported AtMega328p Boards](#specific-supported-atmega328p-boards) + - [AtMega328p Processor Information](#atmega328p-processor-information) - [AtMega32u4 (Arduino Leonardo/Micro, Adafruit Flora/Feather, etc)](#atmega32u4-arduino-leonardomicro-adafruit-florafeather-etc) + - [Specific Supported AtMega32u4 Boards](#specific-supported-atmega32u4-boards) + - [AtMega32u4 Processor Information](#atmega32u4-processor-information) - [Unsupported Processors](#unsupported-processors) +## Processor Configuration and Adding Support + +The specific processors supported by the ModularSensors library are defined in the [KnownProcessors.h](../src/sensors/KnownProcessors.h) file. This file contains compiler defines for board names, operating voltages, battery monitoring pins, and other board-specific parameters. + +### KnownProcessors.h Documentation + +The [KnownProcessors.h](../src/sensors/KnownProcessors.h) file defines board-specific parameters for optimal configuration: + +**Basic Board Information:** + +- `LOGGER_BOARD`: Pretty text name for the board +- `OPERATING_VOLTAGE`: Board operating voltage in volts +- `BATTERY_PIN`: Analog pin for battery voltage monitoring +- `BATTERY_MULTIPLIER`: Voltage divider multiplier for battery measurements +- Built-in ambient light sensor parameters (for compatible boards) + +**ADC Configuration:** + +- `MS_PROCESSOR_ADC_RESOLUTION`: ADC resolution in bits (10 for AVR, 12 for SAMD) + - Only add if the default of 10 for a AVR board or 12 for a SAMD board does not apply +- `MS_PROCESSOR_ADC_REFERENCE_MODE`: Voltage reference (DEFAULT, AR_DEFAULT, AR_EXTERNAL, etc.) + - Only add if the reference mode will not be `DEFAULT` + +**Memory Management:** + +- `MS_LOG_DATA_BUFFER_SIZE`: Log buffer size in bytes (optimized per board's memory) + - Only add if something atypical for this processor is needed + +**Clock Configuration:** + +Only add one of these if the clock chip is integrated into the same circuit board the main processor! + +- `MS_USE_DS3231`: Use DS3231 external RTC +- `MS_USE_RV8803`: Use RV8803 external RTC +- `MS_USE_RTC_ZERO`: Use internal RTC (SAMD21 boards only) + +### Adding New Processor Support + +To add support for a new board of a supported processor type: + +1. **Identify the processor type**: Determine if your board uses an AtMega1284p, AtSAMD21, AtSAMD51, AtMega2560, AtMega644p, AtMega328p, or AtMega32u4 processor. + +2. **Find the Arduino board define**: Use your board's Arduino IDE board definition to identify the compiler define (e.g., `ARDUINO_AVR_UNO` for Arduino Uno). + +3. **Add to KnownProcessors.h**: Add a new `#elif defined()` section with your board's define and set the appropriate parameters: + +```cpp +#elif defined(YOUR_BOARD_DEFINE) +#define LOGGER_BOARD "Your Board Name" +#define OPERATING_VOLTAGE 3.3 // or 5.0 +#define BATTERY_PIN A0 // or -1 if not available +#define BATTERY_MULTIPLIER 2.0 // or -1 if not available + +// ADC defaults; if and only if needed +#ifndef MS_PROCESSOR_ADC_RESOLUTION +#define MS_PROCESSOR_ADC_RESOLUTION 10 // 10 for AVR, 12 for SAMD +#endif +#ifndef MS_PROCESSOR_ADC_REFERENCE_MODE +#define MS_PROCESSOR_ADC_REFERENCE_MODE DEFAULT // or AR_DEFAULT for SAMD +#endif + +// Log buffer size - adjust for board's memory capacity; if and only if needed +#ifndef MS_LOG_DATA_BUFFER_SIZE +#define MS_LOG_DATA_BUFFER_SIZE 1024 // Adjust based on available RAM +#endif + +// Built in clock; if and only if integrated into the board +#ifndef MS_USE_DS3231 // Choose appropriate RTC for your board +#define MS_USE_DS3231 +#endif +``` + +4. **Submit contribution**: Create a pull request with your additions to help other users with the same board. + +### Board-Specific Parameters + +Each board entry should specify: + +- **Operating voltage**: Typically 3.3V or 5V +- **Battery monitoring**: Pin and voltage divider information if available +- **Built-in sensors**: Light sensor configuration for boards that include them +- **Clock selection**: Appropriate RTC type for the board's capabilities + - External RTC (DS3231, RV8803) for most AVR boards + - Internal RTC (RTC_ZERO) for SAMD21/SAMD51 boards +- **ADC configuration**: Resolution and reference voltage optimized for the processor +- **Memory management**: Log buffer size appropriate for the board's available RAM + +### Configuration Override + +All board-specific defaults can be overridden in [ModSensorConfig.h](../src/ModSensorConfig.h) if needed: + +- Users can uncomment and modify clock, ADC, or buffer size settings +- Build flags can also be used with PlatformIO for custom configurations +- Arduino IDE users should modify ModSensorConfig.h as build flags aren't available + +___ + ## AtMega1284p (EnviroDIY Mayfly, Sodaq Mbili, Mighty 1284) The [EnviroDIY Mayfly](https://envirodiy.org/mayfly/) _is_ the test board for this library. _Everything_ is designed to work with this processor. +### Specific Supported AtMega1284p Boards + +- **EnviroDIY Mayfly** (`ARDUINO_AVR_ENVIRODIY_MAYFLY`) +- **SODAQ Mbili** (`ARDUINO_AVR_SODAQ_MBILI`) + +### AtMega1284p Processor Information + [Datasheet Summary](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega1284P-Datasheet-Summary.pdf) [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega1284P-Datasheet.pdf) - If using a non-Mayfly 1284p board, an external DS3231 or DS3232 RTC is required and the interrupt pin from the clock must be connected to the MCU. -This RTC is already built into the Mayfly and the interrupt is connected at either pin A7 (default) or D10 (with solder jumper SJ1). + This RTC is already built into the Mayfly and the interrupt is connected at either pin A7 (default) or D10 (with solder jumper SJ1). - There is a single SPI port on pins 14 (MISO), 15 (SCK), and 13 (MOSI) on a Mayfly/Mbili or pins 6 (MISO), 7 (SCK), and 5 (MOSI) on a Mighty 1284 or other AtMega1284p. -Chip select/slave select is pin 12 on a Mayfly and card detect can be set to pin 18 with solder jumper 10. -CS/SS and CD pins may vary for other boards. + Chip select/slave select is pin 12 on a Mayfly and card detect can be set to pin 18 with solder jumper 10. + CS/SS and CD pins may vary for other boards. - There is a single I2C (Wire) interface on pins 17 (SDA) and 16 (SCL). - This processor has two built-in hardware TTL serial ports, Serial and Serial1 - - On most boards, Serial is connected to the FDTI chip for USB communication with the computer. -On both the Mayfly and the Mbili Serial1 is wired to the "Bee" sockets for communication with the modem. + - On most boards, Serial is connected to the FTDI chip for USB communication with the computer. + On both the Mayfly and the Mbili Serial1 is wired to the "Bee" sockets for communication with the modem. - AltSoftSerial can be used on pins 5 (Tx) and 6 (Rx) on the Mayfly or Mbili. -Pin 4 should not be used while using AltSoftSerial on the Mayfly or Mbili. + Pin 4 should not be used while using AltSoftSerial on the Mayfly or Mbili. - Unfortunately, the Rx and Tx pins are on different Grove plugs on both the Mayfly and the Mbili making AltSoftSerial somewhat inconvenient to use. - AltSoftSerial can be used on pins 13 (Tx) and 14 (Rx) on the Mighty 1284 and other 1284p boards. -Pin 12 should not be used while using AltSoftSerial on the Mighty 1284. + Pin 12 should not be used while using AltSoftSerial on the Mighty 1284. - Any digital pin can be used with NeoSWSerial, SoftwareSerial_ExtInts, or SDI-12. ___ @@ -48,36 +172,66 @@ ___ Fully supported +### Specific Supported AtSAMD21 Boards + +- **SODAQ ExpLoRer** (`ARDUINO_SODAQ_EXPLORER`) +- **SODAQ Autonomo** (`ARDUINO_SODAQ_AUTONOMO`) +- **SODAQ ONE Beta** (`ARDUINO_SODAQ_ONE_BETA`) +- **SODAQ ONE** (`ARDUINO_SODAQ_ONE`) +- **Adafruit Feather M0 Express** (`ARDUINO_SAMD_FEATHER_M0_EXPRESS`) +- **Adafruit Feather M0** (`ARDUINO_SAMD_FEATHER_M0`) +- **Arduino Zero** (`ARDUINO_SAMD_ZERO`) + +### AtSAMD21 Processor Information + [Datasheet Summary](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-SAMD21-Datasheet-Summary.pdf) [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-SAMD21-Datasheet.pdf) -- This processor has an internal real time clock (RTC) and does not require a DS3231 to be installed. -The built-in RTC is not as accurate as the DS3231, however, and should be synchronized more frequently to keep the time correct. -The processor clock will also reset if the system battery dies because unlike most external RTC's, there is no coin battery backing up the clock. -At this time, the AtSAMD21 is only supported using the internal clock, but support with a more accurate external RTC is planned. +- This processor has an internal real time clock (RTC) and does not **require** an external RTC, though it can be used with one. + The built-in RTC is not as accurate as the DS3231, however, and should be synchronized more frequently to keep the time correct. + The processor clock will also reset if the system battery dies because unlike most external RTCs, there is no coin battery backing up the clock. + At this time, the AtSAMD21 is only supported using the internal clock, but support with a more accurate external RTC is planned. - This processor has built-in USB drivers. -Most boards connect the USB pins to a mini or microUSB connector for the computer connection. -Depending on the software core of the board, you send data to the USB port as either "USBSerial" or simply "Serial". + Most boards connect the USB pins to a mini or microUSB connector for the computer connection. + Depending on the software core of the board, you send data to the USB port as either "USBSerial" or simply "Serial". - Most variants have 2 hardware TTL serial ports ("Serial" on pins 30 (TX) and 31 (RX) and "Serial1" on pins 0 (TX) and 1 (RX)) configured by default. - On an Arduino Zero "Serial" goes to the EDBG programming port. - On a Sodaq Autonomo "Serial1" goes to the "Bee" port. - On an Adafruit Feather M0 only "Serial1" is configured, "Serial" will go to the native USB port. - Most variants have one SPI port configured by default (likely pins 22 (MISO), 23 (MOSI), and 24 (SCK)). -Chip select/slave select and card detect pins vary by board. + Chip select/slave select and card detect pins vary by board. - Most variants have one I2C (Wire) interface configured by default (likely pins 20 (SDA) and 21 (SCL)). - There are up to _6_ total "sercom" ports hard which can be configured for either hardware serial, SPI, or I2C (wire) communication on this processor. -See for more instructions on how to configure these ports, if you need them. -There are also examples for an Adafruit feather found in the menu a la carte example: + See for more instructions on how to configure these ports, if you need them. + There are also examples for an Adafruit feather found in the menu a la carte example: - AltSoftSerial is not supported on the AtSAMD21. - SoftwareSerial_ExtInts is not supported on the AtSAMD21. - NeoSWSerial is not supported at all on the AtSAMD21. - Any digital pin can be used with SDI-12. - Because the USB controller is built into the processor, your USB serial connection will close as soon as the processor goes to sleep. -If you need to debug, I recommend using a serial port monitor like [Tera Term](https://ttssh2.osdn.jp/index.html.en) which will automatically renew its connection with the serial port when it connects and disconnects. -Otherwise, you will have to rely on lights on your alert pin or your modem to verify the processor is waking/sleeping properly. + If you need to debug, I recommend using a serial port monitor like [Tera Term](https://teratermproject.github.io/index-en.html) which will automatically renew its connection with the serial port when it connects and disconnects. + Otherwise, you will have to rely on lights on your alert pin or your modem to verify the processor is waking/sleeping properly. - There are also some oddities with debugging on the SAMD21 where turning on some of the debugging code will cause the native USB to fail (and the board appear to be bricked). -Turn off the debugging and double-tap to reset and reprogram if this happens. + Turn off the debugging and double-tap to reset and reprogram if this happens. + +___ + +## AtSAMD51 (Adafruit Feather M4, EnviroDIY Stonefly) + +Fully supported with similar characteristics to AtSAMD21 but with enhanced performance. + +### Specific Supported AtSAMD51 Boards + +- **EnviroDIY Stonefly** (`ENVIRODIY_STONEFLY_M4`) +- **Adafruit Feather M4** (`ARDUINO_FEATHER_M4`) +- **Feather M4 CAN** (`ARDUINO_FEATHER_M4_CAN`) +- **Adafruit Feather M4 Adalogger** (`ADAFRUIT_FEATHER_M4_ADALOGGER`) +- **Adafruit Grand Central** (`ADAFRUIT_GRAND_CENTRAL_M4`) + +### AtSAMD51 Processor Information + +These boards share similar capabilities with the AtSAMD21 processor boards including built-in USB, internal RTC, and multiple hardware serial ports, but with increased processing power and memory. ___ @@ -85,13 +239,21 @@ ___ Should be fully functional, but untested. -- An external DS3231 or DS3232 RTC is required. +### Specific Supported AtMega2560 Boards + +- **Arduino Mega ADK** (`ARDUINO_AVR_ADK`) +- **Arduino Mega** (`ARDUINO_AVR_MEGA`) +- **Arduino Mega 2560** (`ARDUINO_AVR_MEGA2560`) + +### AtMega2560 Processor Information + +- An external RTC (DS3231, DS3232, or RV8803) is required. - There is a single SPI port on pins 50 (MISO), 52 (SCK), and 51 (MOSI). -Chip select/slave select is on pin 53. + Chip select/slave select is on pin 53. - There is a single I2C (Wire) interface on pins 20 (SDA) and 21 (SCL). - This processor has 4 built-in hardware serial ports Serial, which is connected to the FTDI chip for USB communication with the computer, Serial1 on pins 19 (RX) and 18 (TX), Serial2 on pins 17 (RX) and 16 (TX), and Serial3 on pins 15 (RX) and 14 (TX). - If you still need more serial ports, AltSoftSerial can be used on pins 46 (Tx) and 48 (Rx). -Pins 44 and 45 cannot be used while using AltSoftSerial on the AtMega2560. + Pins 44 and 45 cannot be used while using AltSoftSerial on the AtMega2560. - Pins 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), and A15 (69) can be used with NeoSWSerial, SoftwareSerial_ExtInts, or SDI-12. ___ @@ -100,31 +262,52 @@ ___ Should be fully functional, but untested. -- An external DS3231 or DS3232 RTC is required. +### Specific Supported AtMega644p Boards + +- **SODAQ Ndogo** (`ARDUINO_AVR_SODAQ_NDOGO`) +- **SODAQ Tatu** (`ARDUINO_AVR_SODAQ_TATU`) +- **SODAQ Moja** (`ARDUINO_AVR_SODAQ_MOJA`) + +### AtMega644p Processor Information + +- An external RTC (DS3231, DS3232, or RV8803) is required. - This processor has two built-in hardware serial ports, Serial and Serial1. -On most boards, Serial is connected to the FDTI chip for USB communication with the computer. + On most boards, Serial is connected to the FTDI chip for USB communication with the computer. - There is a single I2C (Wire) interface on pins 17 (SDA) and 16 (SCL). - There is a single SPI port on pins 6 (MISO), 7 (SCK), and 5 (MOSI). -Chip select/slave select and card detect pins vary by board. + Chip select/slave select and card detect pins vary by board. - AltSoftSerial can be used on pins 13 (Tx) and 14 (Rx). -Pin 12 cannot be used while using AltSoftSerial on the AtMega644p. + Pin 12 cannot be used while using AltSoftSerial on the AtMega644p. - Any digital pin can be used with NeoSWSerial, SoftwareSerial_ExtInts, or SDI-12. ___ -## AtMega328p (Arduino Uno, Duemilanove, LilyPad, Mini, Seeeduino Stalker, etc) +## AtMega328p (Arduino Uno, Seeeduino Stalker, etc) All functions are supported, but processor doesn't have sufficient power to use all of the functionality of the library. You will only be able to use a small number of sensors at one time and may not be able to log data. -- An external DS3231 or DS3232 RTC is required. +### Specific Supported AtMega328p Boards + +- **Adafruit Feather 328p** (`ARDUINO_AVR_FEATHER328P`) +- **Arduino BT** (`ARDUINO_AVR_BT`) +- **Arduino Duemilanove** (`ARDUINO_AVR_DUEMILANOVE`) +- **Arduino Ethernet** (`ARDUINO_AVR_ETHERNET`) +- **Arduino Fio** (`ARDUINO_AVR_FIO`) +- **Arduino Mini 05** (`ARDUINO_AVR_MINI`) +- **Arduino Nano** (`ARDUINO_AVR_NANO`) +- **Arduino Uno** (`ARDUINO_AVR_UNO`) + +### AtMega328p Processor Information + +- An external RTC (DS3231, DS3232, or RV8803) is required. - There is a singe SPI ports on pins 12 (MISO), 13 (SCK), and 11 (MOSI). -Chip select/slave select is pin 10 on an Uno. -SS/CS and CD pins may vary for other boards. + Chip select/slave select is pin 10 on an Uno. + SS/CS and CD pins may vary for other boards. - There is a single I2C (Wire) interface on pins A4 (SDA) and A5 (SCL). - This processor only has a single hardware serial port, Serial, which is connected to the FTDI chip for USB communication with the computer. - AltSoftSerial can be used on pins 9 (Tx) and 8 (Rx). -Pin 10 cannot be used while using AltSoftSerial on the AtMega328p. + Pin 10 cannot be used while using AltSoftSerial on the AtMega328p. - Any digital pin can be used with NeoSWSerial, SoftwareSerial_ExtInts, or SDI-12. ___ @@ -134,22 +317,33 @@ ___ All functions are supported, but processor doesn't have sufficient power to use all of the functionality of the library. You will only be able to use a small number of sensors at one time and may not be able to log data. +### Specific Supported AtMega32u4 Boards + +- **Adafruit Feather 32u4** (`ARDUINO_AVR_FEATHER32U4`) +- **Arduino Esplora** (`ARDUINO_AVR_ESPLORA`) +- **Arduino Gemma** (`ARDUINO_AVR_GEMMA`) +- **Arduino Leonardo** (`ARDUINO_AVR_LEONARDO`) +- **Arduino Micro** (`ARDUINO_AVR_MICRO`) +- **Arduino Yun** (`ARDUINO_AVR_YUN`) + +### AtMega32u4 Processor Information + [Datasheet Summary](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega16U4-32U4-Datasheet-Summary.pdf) [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega16U4-32U4-Datasheet.pdf) -- An external DS3231 or DS3232 RTC is required. +- An external RTC (DS3231, DS3232, or RV8803) is required. - There is a single SPI port on pins 14 (MISO), 15 (SCK), and 16 (MOSI). -Chip select/slave select and card detect pins vary by board. + Chip select/slave select and card detect pins vary by board. - There is a single I2C (Wire) interface on pins 2 (SDA) and 3 (SCL). - This processor has one hardware serial port, Serial, which can _only_ be used for USB communication with a computer - There is one additional hardware serial port, Serial1, which can communicate with any serial device. - AltSoftSerial can be used on pins 5 (Tx) and 13 (Rx). - Only pins 8, 9, 10, 11, 14, 15, and 16 can be used with NeoSWSerial, SoftwareSerial_ExtInts, or SDI-12. -(And pins 14, 15, and 16 will be eliminated if you are using any SPI devices (like an SD card).) + (And pins 14, 15, and 16 will be eliminated if you are using any SPI devices (like an SD card).) - Because the USB controller is built into the processor, your USB serial connection will close as soon as the processor goes to sleep. -If you need to debug, I recommend using a serial port monitor like Tera Term which will automatically renew its connection with the serial port when it connects and disconnects. -Otherwise, you will have to rely on lights on your alert pin or your modem to verify the processor is waking/sleeping properly. + If you need to debug, I recommend using a serial port monitor like [Tera Term](https://teratermproject.github.io/index-en.html) which will automatically renew its connection with the serial port when it connects and disconnects. + Otherwise, you will have to rely on lights on your alert pin or your modem to verify the processor is waking/sleeping properly. ___ @@ -158,18 +352,18 @@ ___ - **ESP8266/ESP32** - Supported _only_ as a communications module (modem) with the default AT command firmware, not supported as an independent controller - **AtSAM3X (Arduino Due)** - Unsupported at this time due to clock and sleep issues. - There is one SPI port on pins 74 (MISO), 76 (MOSI), and 75 (SCK). -Pins 4, 10 and pin 52 can be used for CS/SS. + Pins 4, 10 and pin 52 can be used for CS/SS. - There are I2C (Wire) interfaces on pins 20 (SDA) and 21 (SCL) and 70 (SDA1) and 71 (SCL1). - This processor has one hardware serial port, USBSerial, which can _only_ be used for USB communication with a computer - There are three additional 3.3V TTL serial ports: Serial1 on pins 19 (RX) and 18 (TX); Serial2 on pins 17 (RX) and 16 (TX), Serial3 on pins 15 (RX) and 14 (TX). -Pins 0 and 1 are also connected to the corresponding pins of the ATmega16U2 USB-to-TTL Serial chip, which is connected to the USB debug port. + Pins 0 and 1 are also connected to the corresponding pins of the ATmega16U2 USB-to-TTL Serial chip, which is connected to the USB debug port. - AltSoftSerial is not directly supported on the AtSAM3X. - SoftwareSerial_ExtInts is not supported on the AtSAM3X. - SDI-12 is not supported on the AtSAM3X - Any digital pin can be used with SDI-12. - [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-AM3X-SAM3A-Datasheet.pdf) - **ATtiny** - Unsupported. -This chip has too little processing power and far too few pins and communication ports to ever use this library. + This chip has too little processing power and far too few pins and communication ports to ever use this library. - [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATtiny25-45-85-Datasheet.pdf) - **Teensy 2.x/3.x** - Unsupported - **STM32** - Unsupported diff --git a/docs/For-Developers/Developer-Setup.md b/docs/For-Developers/Developer-Setup.md index 72ac68cd6..106f4c694 100644 --- a/docs/For-Developers/Developer-Setup.md +++ b/docs/For-Developers/Developer-Setup.md @@ -1,17 +1,68 @@ # Developer Setup + + + + + + +- [Developer Setup](#developer-setup) + - [Git Filter Setup](#git-filter-setup) + - [PlatformIO Setup](#platformio-setup) + - [Debugging](#debugging) + + + If you want to fork this repository and work with it, you'll need to set PlatformIO up a bit differently than you would to merely use this library. First, fork this repository into your own GitHub space. Clone it to your local computer. +## Git Filter Setup + +This repository uses Git filters to manage sensitive credentials and debug configurations. +After cloning the repository, run one of the following setup scripts to configure the necessary Git filter drivers: + +**Windows Command Prompt:** + +```batch +setupGitFilters.bat +``` + +**PowerShell:** + +```powershell +.\setupGitFilters.ps1 +``` + +**Manual Setup:** + +If you prefer to configure the filters manually, run these Git commands from the repository root (requires PowerShell): + +```powershell +git config --local filter.smudgePasswords.clean "powershell -ExecutionPolicy Bypass -File filters/cleanPasswords.ps1" +git config --local filter.smudgePasswords.smudge "powershell -ExecutionPolicy Bypass -File filters/smudgePasswords.ps1" +git config --local filter.disableDebug.clean "powershell -ExecutionPolicy Bypass -File filters/cleanDebugConfig.ps1" +git config --local filter.disableDebug.smudge "powershell -ExecutionPolicy Bypass -File filters/smudgeDebugConfig.ps1" +``` + +These filters provide the following functionality: + +- **smudgePasswords** - Manages placeholder credentials in `.ino` example files during development and commits +- **disableDebug** - Automatically manages debug defines in `ModSensorDebugConfig.h` to keep them disabled in commits but enabled locally + +## PlatformIO Setup + +The Arduino IDE is not a good tool for library development. +Use PlatformIO in VSCode instead. + Open the folder you've cloned this repo into with VSCode. Have PlatformIO create a new project for you, but instead of allowing it to create a new folder, select the folder you've already cloned this repo into. -Create a new source program to work with in a new directory. +Create a new source program to work with in a new directory (i.e., a tests directory). This is the directory you should reference in the `src_dir` line of your platformio.ini. _Also add this directory to your .gitignore file_ so you can test and play with your code without publishing personal passwords or other messiness to the web. -I recommend you start with one of the programs in the compile_tests folder rather than one of the examples because the compiler test programs are _much_ more extensive and include all sensors and many modems in them. +I recommend you start with the menu a la carte example for development since it already contains all features and is tested for proper compilation with continuous integration tools. Set your platformio.ini configuration file up like this: @@ -30,30 +81,38 @@ src_dir = your_directory/your_source_code ; Default baud for all examples monitor_speed = 115200 framework = arduino -; To run code checks; cppcheck and clangtidy must be installed -check_tool = cppcheck, clangtidy -check_patterns = +; To run code checks, clangtidy must be installed +check_tool = clangtidy +check_src_filters = src extras examples check_flags = - cppcheck: --enable=all, --inline-suppr clangtidy: --checks=-* +check_skip_packages = yes ; deep search for dependencies, evaluating preprocessor conditionals -lib_ldf_mode = deep+ +lib_ldf_mode = deep +lib_compat_mode = soft ; look for the library directory lib_extra_dirs = . ; We have to ignore these folders or PlatformIO will double count all the dependencies lib_ignore = .git + .github + .history .pio .vscode - doc + archive + continuous_integration + docs examples - sensor_tests extras - Adafruit NeoPixel + ex_one_offs + filters + sensor_tests + tests Adafruit GFX Library + Adafruit SH110X Adafruit SSD1306 Adafruit ADXL343 Adafruit STMPE610 @@ -63,31 +122,41 @@ lib_ignore = ; source code and won't read the dependencies from the library.json like a ; typical user would lib_deps = - envirodiy/EnviroDIY_DS3231 - arduino-libraries/RTCZero - greygnome/EnableInterrupt - greiman/SdFat - vshymanskyy/TinyGSM - knolleary/PubSubClient - adafruit/Adafruit BusIO - adafruit/Adafruit Unified Sensor - https://github.com/soligen2010/Adafruit_ADS1X15.git - adafruit/Adafruit AM2315 - adafruit/Adafruit BME280 Library - adafruit/DHT sensor library - adafruit/Adafruit INA219 - adafruit/Adafruit MPL115A2 - adafruit/Adafruit SHT4x Library - MartinL1/BMP388_DEV - paulstoffregen/OneWire - milesburton/DallasTemperature - envirodiy/SDI-12 - northernwidget/MS5803 - https://github.com/EnviroDIY/Tally_Library.git#Dev_I2C - envirodiy/SensorModbusMaster - envirodiy/KellerModbus - envirodiy/YosemitechModbus - https://github.com/EnviroDIY/StreamDebugger.git + envirodiy/EnviroDIY_DS3231@^1.3.6 + arduino-libraries/RTCZero@^1.6.0 + sparkfun/SparkFun Qwiic RTC RV8803 Arduino Library@^1.2.10 + greygnome/EnableInterrupt@^1.1.0 + greiman/SdFat@=2.3.0 + TinyGSM=https://github.com/EnviroDIY/TinyGSM + knolleary/PubSubClient@^2.8 + adafruit/Adafruit BusIO@^1.17.4 + adafruit/Adafruit Unified Sensor@^1.1.15 + adafruit/Adafruit ADS1X15@^2.6.2 + adafruit/Adafruit AM2315@^2.2.3 + adafruit/Adafruit BME280 Library@^2.3.0 + MartinL1/BMP388_DEV@^1.0.11 + adafruit/DHT sensor library@^1.4.7 + adafruit/Adafruit INA219@^1.2.3 + adafruit/Adafruit MPL115A2@^2.0.2 + adafruit/Adafruit SHT4x Library@^1.0.5 + paulstoffregen/OneWire@^2.3.8 + milesburton/DallasTemperature@^4.0.6 + envirodiy/SDI-12@^2.3.2 + SDI-12_ExtInts=https://github.com/EnviroDIY/Arduino-SDI-12#ExtInts + northernwidget/MS5803@^0.1.2 + Tally_Library_I2C=https://github.com/EnviroDIY/Tally_Library.git#Dev_I2C + envirodiy/SensorModbusMaster@^1.7.0 + envirodiy/KellerModbus@^0.2.7 + envirodiy/YosemitechModbus@^0.5.4 + envirodiy/GroPointModbus@^0.1.5 + envirodiy/GeoluxCamera@^0.1.3 + robtillaart/fast_math@^0.2.4 + envirodiy/ANBSensorsModbus@^0.4.2 + robtillaart/MS5837@^0.3.2 + StreamDebugger=https://github.com/EnviroDIY/StreamDebugger.git + NeoSWSerial=https://github.com/SRGDamia1/NeoSWSerial.git + AltSoftSerial=https://github.com/PaulStoffregen/AltSoftSerial.git + SoftwareWire=https://github.com/Testato/SoftwareWire.git#v1.5.1 ; The directories for the ModularSensors library source code src_filter = +<*> @@ -100,13 +169,10 @@ src_filter = +<../../src/utils> ; Some common build flags build_flags = + -Wall + -Wextra -D SDI12_EXTERNAL_PCINT -D NEOSWSERIAL_EXTERNAL_PCINT - -D MQTT_MAX_PACKET_SIZE=240 - -D TINY_GSM_RX_BUFFER=64 - -D TINY_GSM_YIELD_MS=2 -extra_scripts = pre:pioScripts/generate_compile_commands.py -targets = compiledb [env:mayfly] ; Find your COM port, enter it here, and remove the semicolon at the start of the line @@ -126,41 +192,16 @@ lib_ignore = ${env.lib_ignore} RTCZero Adafruit Zero DMA Library + SDI-12_ExtInts + Adafruit TinyUSB Library + ESP8266SdFat ; Any extra build flags you want build_flags = ${env.build_flags} - -D STANDARD_SERIAL_OUTPUT=Serial - -D DEBUGGING_SERIAL_OUTPUT=Serial - -D DEEP_DEBUGGING_SERIAL_OUTPUT=Serial -; when I'm uploading frequently, I disable verification of the write -upload_flags = - -V - - -[env:adafruit_feather_m0] -; Find your COM port, enter it here, and remove the semicolon at the start of the line -; upload_port = COM## -; monitor_port = COM## -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino -; SAMD boards need RTCZero for the real time clock and sleeping -lib_deps = - ${env.lib_deps} - RTCZero -; Most of the software serial libraries won't compile. -; Use the SERCOM's; they're better anyway -lib_ignore = - ${env.lib_ignore} - SoftwareSerial_ExtInts - AltSoftSerial - NeoSWSerial - SoftwareWire -build_flags = - ${env.build_flags} -build_unflags = -D USE_TINYUSB ``` +## Debugging + While you're working on development, there is _extensive_ debugging text built into this library. More on that is in the [Code Debugging](https://github.com/EnviroDIY/ModularSensors/wiki/Code-Debugging) page. In fact, there is _so much_ debugging that turning it on universally through a build flag will cause the program to be too big to fit on a Mayfly and will likely crash a SAMD board's on-board USB drivers. diff --git a/docs/Further-Reading/Modem-Notes.md b/docs/Further-Reading/Modem-Notes.md index ae0e6f949..ad6d0f4e1 100644 --- a/docs/Further-Reading/Modem-Notes.md +++ b/docs/Further-Reading/Modem-Notes.md @@ -29,7 +29,7 @@ If you are having trouble, please see the pages for the specific modems and the | Digi XBee3 LTE Cat 1 AT&T (Telit LE866A1-NA) | DigiXBeeCellularTransparent | | Digi XBee S6B WiFi | DigiXBeeWifi | | Espressif ESP8266 | EspressifESP8266 | -| Espressif ESP32 | EspressifESP32 | +| Espressif ESP32 | EspressifESP32 | | Quectel BG96 | QuectelBG96 | | Mikroe LTE IOT 2 Click (_BG96_)¹ | QuectelBG96 | | Dragino NB IOT Bee (_BG96_)¹ | QuectelBG96 | @@ -165,7 +165,7 @@ You should set the argument `useCTSforStatus` to `false` in the bee constructor 7 I _strongly_ recommend running two new wires along the back of the Mayfly to connect pin 5 of the XBee socket to pin A4 and pin 18 of the XBee socket to A3. This will enable you to use A4 as the reset pin and A3 as the sleep request pin. -With those connections made, the Dragino BG96 becomes the _**only**_ LTE module that can be run using only the 500mA regulator on the Mayfly (ie, without a separate battery connection for the modem). +With those connections made, the Dragino BG96 becomes the _**only**_ LTE module that can be run using only the 500mA regulator on the Mayfly (i.e., without a separate battery connection for the modem). 8 This module is no longer produced or sold. @@ -195,7 +195,8 @@ Here are the pin numbers to use for modules that can be attached directly to an | Sodaq UBee LTE-M (u-blox SARA R410M) | 23 | 19 | A53 | 20 | | Sodaq UBee 3G (u-blox SARA U201) | 23 | 19 | A53 | 20 | -¹ This assumes you have not changed solder jumper 18. If you have switched SJ18 to connect bee pin one directly to 3.3V, use -1. +¹ This assumes you have not changed solder jumper 18. +If you have switched SJ18 to connect bee pin one directly to 3.3V, use -1. ² The Digi XBee reports ON/SLEEP_N on pin 13, but this is not connected to a Mayfly pin by default. You can use the XBee's `CTS` pin (pin 12) which is connected to Mayfly pin 19 by default and set the argument `useCTSforStatus` to `true` in the bee constructor. diff --git a/docs/Further-Reading/SAMD-Clocks.md b/docs/Further-Reading/SAMD-Clocks.md index 5f993421e..1b702ef63 100644 --- a/docs/Further-Reading/SAMD-Clocks.md +++ b/docs/Further-Reading/SAMD-Clocks.md @@ -40,16 +40,15 @@ ## Terms -Essentially every microprocessor or computer needs a consistent way of their own speed of operation so they can communicate with internal components and external devices. - -An *[oscillator](https://en.wikipedia.org/wiki/Electronic_oscillator)* is a circuit that makes an oscillating signal - ie, it switches back and forth between to states at a consistent rate. +Essentially every microprocessor or computer needs a consistent way to track its own speed of operation so it can communicate with internal components and external devices. +An *[oscillator](https://en.wikipedia.org/wiki/Electronic_oscillator)* is a circuit that makes an oscillating signal - i.e., it switches back and forth between two states at a consistent rate. The oscillator works like a metronome. An oscillator alone does not keep track of time; it ticks, but it doesn't count how many ticks have passed. SAMD processors use these types of oscillators: - [crystal oscillators](https://en.wikipedia.org/wiki/Crystal_oscillator) - which are tiny pieces of quartz that vibrate under current. -This is just like the crystals in a quartz watch. + This is just like the crystals in a quartz watch. - Digital frequency locked loops (DFLL) and fractional digital phase locked loops (FDPLL) - these [phase locked loops (PLL)](https://wirelesspi.com/how-a-frequency-locked-loop-fll-works/) use a reference clock (like the external crystal) to create a consistent (faster) output frequency. - Ultra-low-power oscillators - circuits which generate the same frequency vibrations as a crystal power but using lower power consumption to get a less consistent signal. @@ -430,6 +429,7 @@ void NVIC_DisableIRQ (IRQn_Type IRQn) Functions are implemented as inline code. From the SAMD51 datasheet, here are the interrupt line mapping numbers for interrupts in the NVIC + ### [Datasheet Table 10-1 Interrupt Line Mapping](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-DA8CB38A-18D7-4512-965B-BB439142B281.html?hl=icpr#GUID-DA8CB38A-18D7-4512-965B-BB439142B281__TABLE_CYS_KLX_S5) @@ -883,7 +883,6 @@ From the SAMD51 datasheet, here are the interrupt line mapping numbers for inter
SDHC1 - SD/MMC Host Controller 1All SDHC1 Interrupts136disabled
- ## Exception and Interrupt Handlers Default exception handler functions are defined in startup_samd21.c. @@ -930,7 +929,6 @@ They're defined as “weak” functions, so you can override the default impleme ## NVIC Interrupts Defined in Other Popular Libraries - diff --git a/docs/Further-Reading/Sleep-Configurations.md b/docs/Further-Reading/Sleep-Configurations.md index 5b2e6d3e8..b8811710c 100644 --- a/docs/Further-Reading/Sleep-Configurations.md +++ b/docs/Further-Reading/Sleep-Configurations.md @@ -35,19 +35,19 @@ All boards start their bedtime routine with these steps. - Enable the wake ISR on the RTC wake pin - Stop the I2C (Wire) library - **WARNING:** After stopping I2C, we can no longer communicate with and I2C based RTCs! -Any calls to get the current time, change the alarm settings, reset the alarm flags, or any other event that involves communication with the RTC will fail! + Any calls to get the current time, change the alarm settings, reset the alarm flags, or any other event that involves communication with the RTC will fail! - For an AVR board, this disables the two-wire pin functionality and turns off the internal pull-up resistors. - // For a SAMD board, this only disables the I2C sercom and does nothing with - // the pins. The Wire.end() function does **NOT** force the pins low. + - For a SAMD board, this only disables the I2C sercom and does nothing with the pins. + - The Wire.end() function does **NOT** force the pins low. - Force the I2C pins to `LOW` - This only works if the SDA and SCL pins are defined in a boards pins.h file. -Not all boards define the SDA and SCL pins and those that do only define it for their "main" I2C/TWI interface + Not all boards define the SDA and SCL pins and those that do only define it for their "main" I2C/TWI interface - I2C devices have a nasty habit of stealing power from the SCL and SDA pins; this prevents that. - **WARNING:** Any calls to the I2C/Wire library when pins are forced low will cause an endless board hang. - Disable the watch-dog timer - If it is enabled the watchdog timer will wake the board every ~8 seconds checking if the board has been inactive too long and needs to be reset. - - We have to chose between allowing the watchdog to save us in a hand during sleep and saving power. -We've chosen to save power. + - We have to choose between allowing the watchdog to save us in a hand during sleep and saving power. + We've chosen to save power. After this, the different processor types have different steps to finish preparing and finally falling asleep. @@ -59,9 +59,9 @@ But all processors finish their wake routine with these steps - Re-enable the watch-dog timer - Restart the I2C (Wire) interface - Disable any unnecessary timeouts in the Wire library - - These waits would be caused by a readBytes or parseX being called on wire after the Wire buffer has emptied. -The default stream functions - used by wire - wait a timeout period after reading the end of the buffer to see if an interrupt puts something into the buffer. -In the case of the Wire library, that will never happen and the timeout period is a useless delay. + - These waits would be caused by a readBytes or parseX being called on Wire after the Wire buffer has emptied. + The default stream functions - used by Wire - wait a timeout period after reading the end of the buffer to see if an interrupt puts something into the buffer. + In the case of the Wire library, that will never happen and the timeout period is a useless delay. - Detach RTC interrupt the from the wake pin - Disable the RTC interrupt @@ -87,18 +87,18 @@ The 5 sleep modes are: ### Steps in Putting an AVR board to sleep -After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep) AVR boards finish their bedtime routine with these steps. +After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep), AVR boards finish their bedtime routine with these steps: -- Disable the onboard USB if it exists (ie, for a Leonardo) +- Disable the onboard USB if it exists (i.e., for a Leonardo) - Freeze the USB clock, turn off the USB PLL, and then disable the USB. - Set the sleep mode to SLEEP_MODE_PWR_DOWN. - Temporarily disables interrupts, so no mistakes are made when writing to the processor registers. - Disable the processor ADC, (This must be disabled before the board will power down.) - Turn off the brown-out detector, if possible. -- Disable all power-reduction modules (ie, the processor module clocks). +- Disable all power-reduction modules (i.e., the processor module clocks). - NOTE: This only shuts down the various clocks on the processor via the power reduction register! -It does NOT actually disable the modules themselves or set the pins to any particular state! -This means that the I2C/Serial/Timer/etc pins will still be active and powered unless they are turned off prior to calling this function. + It does NOT actually disable the modules themselves or set the pins to any particular state! + This means that the I2C/Serial/Timer/etc pins will still be active and powered unless they are turned off prior to calling this function. - Set the sleep enable bit. - Wait until the serial ports have finished transmitting. - This isn't very important on AVR boards, but it's good practice. @@ -106,12 +106,12 @@ This means that the I2C/Serial/Timer/etc pins will still be active and powered u ### Steps in Resuming Activity for an AVR board -*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards) AVR boards start their wake routine with these steps. +*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards), AVR boards start their wake routine with these steps: - Temporarily disables interrupts, so no mistakes are made when writing to the processor registers. -- Re-enable all power modules (ie, the processor module clocks) +- Re-enable all power modules (i.e., the processor module clocks) - NOTE: This only re-enables the various clocks on the processor! -The modules may need to be re-initialized after the clocks re-start. + The modules may need to be re-initialized after the clocks re-start. - Clear the SE (sleep enable) bit. - Re-enable the processor ADC - Re-enables interrupts @@ -126,32 +126,35 @@ SAMD51 boards have multiple sleep configurations. > Modular Sensors uses **STANDBY** sleep mode for the SAMD51. The STANDBY mode is the lowest power configuration while keeping the state of the logic and the content of the RAM. -The HIBERNATE, BACKUP, and OFF modes do not retain RAM and a full reset occurs on wake. The watchdog timer also does not run in any sleep setting deeper than STANDBY. +The HIBERNATE, BACKUP, and OFF modes do not retain RAM and a full reset occurs on wake. +The watchdog timer also does not run in any sleep setting deeper than STANDBY. - Idle - PM_SLEEPCFG_SLEEPMODE_IDLE_Val = 0x2 - - The CPU is stopped. Synchronous clocks are stopped except when requested. The logic is retained. + - The CPU is stopped. + Synchronous clocks are stopped except when requested. + The logic is retained. - Wake-Up Sources: - Synchronous: interrupt generated on synchronous (APB or AHB) clock. - Asynchronous: interrupt generated on generic clock, external clock, or external event. - Standby - PM_SLEEPCFG_SLEEPMODE_STANDBY_Val = 0x4 - The CPU is stopped as well as the peripherals. -The logic is retained, and power domain gating can be used to fully or partially turn off the PDSYSRAM power domain. + The logic is retained, and power domain gating can be used to fully or partially turn off the PDSYSRAM power domain. - Wake-Up Sources: - Synchronous interrupt only for peripherals configured to run in standby. - Asynchronous: interrupt generated on generic clock, external clock, or external event. - Hibernate - PM_SLEEPCFG_SLEEPMODE_HIBERNATE_Val = 0x5 - PDCORESW power domain is turned OFF. -The backup power domain is kept powered to allow few features to run (RTC, 32KHz clock sources, and wake-up from external pins). -The PDSYSRAM power domain can be retained according to software configuration. + The backup power domain is kept powered to allow few features to run (RTC, 32KHz clock sources, and wake-up from external pins). + The PDSYSRAM power domain can be retained according to software configuration. - Wake-Up Sources: - Hibernate reset detected by the RSTC - Backup - PM_SLEEPCFG_SLEEPMODE_BACKUP_Val = 0x6 - Only the backup domain is kept powered to allow few features to run (RTC, 32KHz clock sources, and wake-up from external pins). -The PDBKUPRAM power domain can be retained according to software configuration. + The PDBKUPRAM power domain can be retained according to software configuration. - Wake-Up Sources: - Backup reset detected by the RSTC - Off @@ -186,8 +189,8 @@ RESETN is a dedicated pin. > [!NOTE] > You can disable pin tri-state by calling `Logger::disablePinTristate(true)`. -> You can re-enable pin tri-strate by calling `Logger::disablePinTristate(false)`. -> No pin modes are **not** changed when the `disablePinTristate()` function is called, only when the `systemSleep()` function is called. +> You can re-enable pin tri-state by calling `Logger::disablePinTristate(false)`. +> Pin modes are not changed when `disablePinTristate()` is called; they are only changed when `systemSleep()` is called. To prevent power draw by any external pins during sleep, Modular Sensors sets all pins except the RTC interrupt pins to "tri-state." Tri-state means that for *all* pins: @@ -205,7 +208,7 @@ To prevent power draw by any external pins during sleep, Modular Sensors sets al > [!NOTE] > You can disable disabling peripherals by calling `Logger::disablePeripheralShutdown(true)`. -> You can re-enable pin tri-strate by calling `Logger::disablePeripheralShutdown(false)`. +> You can re-enable peripheral shutdown by calling `Logger::disablePeripheralShutdown(false)`. > No peripheral settings are changed when the `disablePeripheralShutdown()` function is called, only when the `systemSleep()` function is called. To decrease power use during sleep on the SAMD51, Modular Sensors explicitly disconnects all unused peripherals from the various clocks and and clock sources to prevent them from "[sleepwalking](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-FA7D618C-0F98-4A2C-9D24-669C4A3E3CA3.html)". @@ -220,7 +223,7 @@ Some notes on what can and cannot be disabled: - We CAN disable the EIC controller timer (4) because the controller clock source is set to OSCULP32K. - We cannot disable the SERCOM peripheral timers for sleep because they're only reset with a begin(speed, config), which we do not call within the Modular Sensors library. -We force users to call the begin in their sketch so they can choose both the exact type of stream and the baud rate. + We force users to call the begin in their sketch so they can choose both the exact type of stream and the baud rate. - We cannot disable the ADC peripheral timers because they're only set in the init for the ADC at startup. - We CAN disable all of the timer clocks because they're reset every time they're used by SDI-12 (and others) @@ -230,13 +233,13 @@ The numbers of all disabled peripherals are: - 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 38, 39, 42, 43, 44, 45, 46, 47 -@see [The SAMD clock file](@ref samd51_clock_other_libraries) for a list of which peripherals each of these numbers pertain to. +See [The SAMD clock file](@ref samd51_clock_other_libraries) for a list of which peripherals each of these numbers pertain to. ### Steps in Putting an SAMD51 board to sleep -After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep) SAMD51 boards finish their bedtime routine with these steps. +After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep), SAMD51 boards finish their bedtime routine with these steps: -- Detach any USB devices (ie, the built in USB drivers for communication with a PC) +- Detach any USB devices (i.e., the built-in USB drivers for communication with a PC) - This is skipped if the TinyUSB library is called for some reason. - Force all pins except the RTC wake and button pins to go to minimum power draw levels (tri-state) - Configure GCLK7 to be disconnected from an oscillator source. @@ -248,20 +251,20 @@ After completing the [steps for putting all boards to sleep](#steps-for-putting- - Set the sleep mode configuration to use STANDBY mode. - Wait for the sleep mode setting to take - From datasheet 18.6.3.3: A small latency happens between the store instruction and actual writing of the SLEEPCFG register due to bridges. -Software must ensure that the SLEEPCFG register reads the desired value before executing a WFI instruction. + Software must ensure that the SLEEPCFG register reads the desired value before executing a WFI instruction. - Configure standby mode to retain all system RAM and disable fast wake. - Wait for all the board to be ready to sleep. - - From datasheet 18.6.3.3: After power-up, the MAINVREG low power mode takes some time to stabilize. O -Once stabilized, the INTFLAG.SLEEPRDY bit is set. -Before entering Standby, Hibernate or Backup mode, software must ensure that the INTFLAG.SLEEPRDY bit is set. -SRGD Note: I believe this only applies at power-on, but it's probably not a bad idea to check that the flag has been set. + - From datasheet 18.6.3.3: After power-up, the MAINVREG low power mode takes some time to stabilize. + Once stabilized, the INTFLAG.SLEEPRDY bit is set. + Before entering Standby, Hibernate or Backup mode, software must ensure that the INTFLAG.SLEEPRDY bit is set. + SRGD Note: I believe this only applies at power-on, but it's probably not a bad idea to check that the flag has been set. - Call the data sync barrier (`__DSB();`) function to ensure outgoing memory accesses complete. - Call wait for interrupts (`__WFI();`) to begin sleeping. - [See this link for tips on failing to sleep.](https://www.eevblog.com/forum/microcontrollers/crashing-through-__wfi/) ### Steps in Resuming Activity for a SAMD51 board -*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards) SAMD51 boards start their wake routine with these steps. +*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards), SAMD51 boards start their wake routine with these steps: - Re-attach the USB for PC communication - Re-set the pin modes for the RTC wake pin, SD card SS pin, SD card power pin, button pin, and LED pin. @@ -283,9 +286,9 @@ The pin configurations for the SAMD21 are identical to those described above for ### Steps in Putting an SAMD21 board to sleep -After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep) SAMD21 boards finish their bedtime routine with these steps. +After completing the [steps for putting all boards to sleep](#steps-for-putting-all-boards-to-sleep), SAMD21 boards finish their bedtime routine with these steps: -- Detach any USB devices (ie, the built in USB drivers for communication with a PC) +- Detach any USB devices (i.e., the built-in USB drivers for communication with a PC) - This is skipped if the TinyUSB library is called for some reason. - Force all pins except the RTC wake and button pins to go to minimum power draw levels (tri-state) - Wait for all serial ports to finish transmitting @@ -295,7 +298,7 @@ After completing the [steps for putting all boards to sleep](#steps-for-putting- - Disable the systick interrupt. - See . - Due to a hardware bug on the SAMD21, the SysTick interrupts become active before the flash has powered up from sleep, causing a hard fault. -To prevent this the SysTick interrupts are disabled before entering sleep mode. + To prevent this the SysTick interrupts are disabled before entering sleep mode. - Set the sleep mode configuration to use STANDBY mode. - Call the data sync barrier (`__DSB();`) function to ensure outgoing memory accesses complete. - Call wait for interrupts (`__WFI();`) to begin sleeping. @@ -303,7 +306,7 @@ To prevent this the SysTick interrupts are disabled before entering sleep mode. ### Steps in Resuming Activity for a SAMD21 board -*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards) SAMD21 boards start their wake routine with these steps. +*Before* completing the [steps on wake for all boards](#steps-on-wake-for-all-boards), SAMD21 boards start their wake routine with these steps: - Re-enable the systick interrupt - Re-attach the USB for PC communication diff --git a/docs/Getting-Started/Getting-Started.md b/docs/Getting-Started/Getting-Started.md index 2a7db7f80..be120fb6e 100644 --- a/docs/Getting-Started/Getting-Started.md +++ b/docs/Getting-Started/Getting-Started.md @@ -50,7 +50,7 @@ That home page can be accessed from the PlatformIO menu. Before you can use this library, you'll need to install it and all of its [dependencies](https://github.com/EnviroDIY/ModularSensors/wiki/Library-Dependencies) so your compiler in the IDE can find them. Because this library has a large number of dependencies, I, again, _very, **very** strongly_ suggest using [PlatformIO](https://platformio.org/). If you use PlatformIO, the library will automatically be installed when you list it in your dependencies in your project's platformio.ini file. -If you really must use the Arduino IDE, this library and all is dependencies can be downloaded in one large zip file [here](https://github.com/EnviroDIY/Libraries/blob/master/libraries.zip?raw=true). +If you really must use the Arduino IDE, this library and all its dependencies can be downloaded in one large zip file downloadable with each Modular Sensors release. ## Setting the Clock @@ -67,20 +67,20 @@ But, for safety, I suggest you set the clock separately. NOTE: These steps are only for AVR boards, for those of you using a SAMD board, the on-board processor RTC is used instead of the DS3231. - Attach the DS3231 to your main board - they'll talk over I2C. -If you're using a Mayfly, the DS3231 is built into the board and you don't need to do anything. + If you're using a Mayfly, the DS3231 is built into the board and you don't need to do anything. - Put a coin battery in the supplemental power slot for the DS3231 (or you'll lose the time as soon as you unplug). -The Mayfly has this battery shot right next to the clock chip. -Every other DS3231 breakout I've seen has a similar way to power the chip. -On the Mayfly, the (+) side of the battery (with the words on it) goes up. + The Mayfly has this battery slot right next to the clock chip. + Every other DS3231 breakout I've seen has a similar way to power the chip. + On the Mayfly, the (+) side of the battery (with the words on it) goes up. - Create a new PlatformIO project from the PlatformIO menu or home page. -Pick whatever board you'll be working with from the drop down. + Pick whatever board you'll be working with from the drop down. For a new project, it's easiest to let PlatformIO set everything up in a new folder. - Once PlatformIO sets up the new project, find and open the newly created platformio.ini file. -It should be a short file with one `[platformio]` section and one `[env]` section for the board you selected earlier. + It should be a short file with one `[platformio]` section and one `[env]` section for the board you selected earlier. - In the `[platformio]` section add this line: `src_dir = .pio/libdeps/mayfly/EnviroDIY_DS3231/examples/PCsync` - In the `[env]` section add this line: `lib_deps = EnviroDIY_DS3231` - Upload to your board. -You shouldn't have to open or modify the program at all. + You shouldn't have to open or modify the program at all. - Download and run this tiny clock-sync program: - Your clock should be set! @@ -89,30 +89,27 @@ You shouldn't have to open or modify the program at all. The set-up in for your logger program PlatformIO is pretty simple: - Create another new PlatformIO project from the PlatformIO menu or home page. -Pick whatever board you'll be working with. + Pick whatever board you'll be working with. Again, it's easiest to let PlatformIO set everything up in a new folder. - Find and open the newly created platformio.ini file in your directory. -In the `[env]` section add these lines: + In the `[env]` section add these lines: - It is important that your configuration has the lib_ldf_mode and build flags set as show below. -Without this, the library won't compile. + Without this, the library won't compile. ```ini lib_deps = EnviroDIY_ModularSensors -lib_ldf_mode = deep+ +lib_ldf_mode = deep build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 ``` - Download the "ino" file for whatever example you think will be most similar to what you'll be doing. -Put the ino into the src directory of your project. + Put the ino into the src directory of your project. - Delete main.cpp in that folder. - Do a test build before changing the example just to make sure it compiles. -Note: before compiling the first time, PlatformIO has to download the library and is dependencies so be patient. -The download only happens once. + Note: before compiling the first time, PlatformIO has to download the library and its dependencies so be patient. + The download only happens once. - If the build succeeds, you're ready to move on. ## Modifying the Examples @@ -128,26 +125,20 @@ The examples currently available are: - [menu_a_la_carte](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/menu_a_la_carte) - This shows most of the functions of the library at once. -It has code in it for every possible sensor and modem and for both AVR and SAMD boards. + It has code in it for every possible sensor and modem and for both AVR and SAMD boards. It is also over 1500 lines long. - [single_sensor](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/single_sensor) - This shows making use of the unified set of commands to print data from a MaxBotix ultrasonic range finder to the serial port. -It also shows creating a calculated variable which is the water depth. + It also shows creating a calculated variable which is the water depth. - [simple_logging](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/simple_logging) - This shows how to log data a simple sample count and battery voltage to a SD card. - [logging_to_ThingSpeak](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/logging_to_ThingSpeak) - This uses an ESP8266 to send data to ThingSpeak. -It also includes a Meter Hydros 21 (formerly know as a Decagon CTD) and a Campbell OBS3+. + It also includes a Meter Hydros 21 (formerly known as a Decagon CTD) and a Campbell OBS3+. - [baro_rho_correction](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/baro_rho_correction) - This example demonstrates how to work with calculated variables and calculates water depth by correcting the total pressure measured by a Measurement Specialties MS5803 with the atmospheric pressure measured by a Bosch BME280 environmental sensor and the temperature measured by a Maxim DS18 temperature probe. - [double_logger](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/double_logger) - This is a more complicated example using two different logger instances to log data at two different intervals, in this case, an AM3215 logging every minute, while checking the battery voltage only every 5 minutes. -This showcases both how to use two different logging instances and how to use some of the functions to set up your own logging loop rather than using the logData() function. -- [data_saving](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/) - - 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. -This example also shows how to stop power draw from an RS485 adapter with automatic flow detection. ## Deploying your Station @@ -156,8 +147,6 @@ There are [video instructions](https://www.envirodiy.org/videos/) on the EnviroD - - diff --git a/docs/Getting-Started/Library-Dependencies.md b/docs/Getting-Started/Library-Dependencies.md deleted file mode 100644 index b01b02680..000000000 --- a/docs/Getting-Started/Library-Dependencies.md +++ /dev/null @@ -1,46 +0,0 @@ -# Library Dependencies - -> [!WARNING] -> This page is frequently out of date. Please see the library.json or dependencies.json and example_dependencies.json for the most up-to-date library references! - -In order to support multiple functions and sensors, there are quite a lot of sub-libraries that this library depends on. -_Even if you do not use the modules, you must have all of the dependencies installed for the library itself to properly compile._ -Please check the [library.json](https://github.com/EnviroDIY/ModularSensors/blob/master/library.json) file for more details on the versions required of each library. -If you are using [PlatformIO](https://platformio.org), you can list "EnviroDIY_ModularSensors" in the `lib_deps` section of your platformio.ini file and all of these libraries will be installed automatically. -If using the "standard" Arduino IDE, you must install each of these libraries individually, or in a bundle from the [EnviroDIY Libraries](https://github.com/EnviroDIY/Libraries) meta-repository. - -- [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) - Administrates and handles pin change interrupts, allowing the logger to sleep and save battery. -This also controls the interrupts for the versions of SoftwareSerial and SDI-12 linked below that have been stripped of interrupt control. -Because we use this library, _you must always add the line `#include ` to the top of your sketch._ -- AVR sleep library - This is for low power sleeping for AVR processors. -(This library is built in to the Arduino and PlatformIO IDEs.) -- [EnviroDIY DS-3231](https://github.com/EnviroDIY/Sodaq_DS3231) - For real time clock control -- [RTCZero library](https://github.com/arduino-libraries/RTCZero) - This real time clock control and low power sleeping on SAMD processors. -(This library may be built in to the Arduino IDE.) -NOTE: If using an AVR board, you must explicitly _ignore_ this library when compiling with PlatformIO or you will have compiler errors. -- [SdFat library](https://github.com/greiman/SdFat) - This enables communication with the SD card. -- [TinyGSM library](https://github.com/vshymanskyy/TinyGSM) - This provides internet (TCP/IP) connectivity. -- [PubSubClient](https://github.com/knolleary/pubsubclient) - For MQTT connectivity -- [Adafruit ADS1X15 library](https://github.com/adafruit/Adafruit_ADS1X15) - For high-resolution analog to digital conversion. - - NOTE: As of version 0.36.0 the standard Adafruit library should be used, *NOT* the Soligen2010 fork. -- [EnviroDIY Arduino SDI-12 library](https://github.com/EnviroDIY/Arduino-SDI-12/tree/ExtInts) - For control of SDI-12 based sensors. -This modified version is needed so there are no pin change interrupt conflicts with the SoftwareSerial library or the software pin change interrupt library used to wake the processor. -- [SensorModbusMaster](https://github.com/EnviroDIY/SensorModbusMaster) - for easy communication with Modbus devices. -- [OneWire](https://github.com/PaulStoffregen/OneWire) - This enables communication with Maxim/Dallas OneWire devices. -- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library) - for communication with the DS18 line of Maxim/Dallas OneWire temperature probes. -- [Adafruit BusIO](https://github.com/adafruit/Adafruit_BusIO) - a dependency of several other Adafruit libraries, used to unify commands for fetching data via SPI and I2C. -- [Adafruit Unified Sensor Driver](https://github.com/adafruit/Adafruit_Sensor) - a dependency of several other Adafruit libraries, used to unify sensor data return types. -- [Adafruit AM2315 library](https://github.com/adafruit/Adafruit_AM2315) - for the AOSong AM2315 temperature and humidity sensor. -- [Adafruit DHT library](https://github.com/adafruit/DHT-sensor-library) - for other AOSong temperature and humidity sensors. -- [Adafruit BME280 library](https://github.com/adafruit/Adafruit_BME280_Library) - for the Bosch BME280 environmental sensor. -- [Adafruit INA219 library](https://github.com/adafruit/Adafruit_INA219) - for the INA219 current/voltage sensor. -- [Adafruit MPL115A2 library](https://github.com/adafruit/Adafruit_MPL115A2) - for the Freescale Semiconductor MPL115A2 barometer. -- [Adafruit SHT4x library](https://github.com/adafruit/Adafruit_SHT4X) - for the Senserion SHT40 temperature and humidity sensor. This sensor is built into the EnviroDIY Mayfly and Stonefly. -- [YosemitechModbus](https://github.com/EnviroDIY/YosemitechModbus) - for all Yosemitech modbus environmental sensors. -- [Northern Widget MS5803 Library](https://github.com/NorthernWidget/MS5803) - for the TE Connectivity MEAS MS5803 pressure sensor -- [EnviroDIY KellerModbus Library](https://github.com/EnviroDIY/KellerModbus) - for all Keller modbus pressure and water level sensors. -- [EnviroDIY GroPointModbus Library](https://github.com/EnviroDIY/GroPointModbus.git) - For GroPoint soil moisture probes. -- [BMP388_DEV](https://registry.platformio.org/libraries/martinl1/BMP388_DEV) - for communication with the Bosch BMP388 barometer - - WARNING: The repository for this library has been removed from GitHub. The library itself is still available in the PlatformIO and Arduino library registries. -- [Tally Library](https://github.com/EnviroDIY/Tally_Library.git#Dev_I2C) - For the Project Tally Event sensor - - NOTE: Use the `Dev_I2C` feature branch diff --git a/docs/doxyfile b/docs/doxyfile index 5a6c44c80..fc78aa063 100644 --- a/docs/doxyfile +++ b/docs/doxyfile @@ -1116,7 +1116,10 @@ EXCLUDE_PATTERNS = */aws_iot_config*.h EXCLUDE_SYMBOLS = MS_*_DEBUG \ MS_DEBUGGING_STD \ - MS_DEBUGGING_DEEP + MS_DEBUGGING_DEEP \ + MS_USE_RV8803 \ + MS_USE_DS3231 \ + MS_USE_RTC_ZERO # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/docs/mcss-conf.py b/docs/mcss-conf.py index 1242ec741..f469d571a 100644 --- a/docs/mcss-conf.py +++ b/docs/mcss-conf.py @@ -45,10 +45,6 @@ # ), # ], ), - ( - "Library Dependencies", - "page_library_dependencies", - ), ( "Physical Dependencies", "page_physical_dependencies", diff --git a/examples/AWS_IoT_Core/AWS_IoT_Core.ino b/examples/AWS_IoT_Core/AWS_IoT_Core.ino index 265cbc10f..926c288ac 100644 --- a/examples/AWS_IoT_Core/AWS_IoT_Core.ino +++ b/examples/AWS_IoT_Core/AWS_IoT_Core.ino @@ -26,21 +26,6 @@ * @m_examplenavigation{example_aws_iot_core,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -#ifndef MQTT_MAX_PACKET_SIZE -#define MQTT_MAX_PACKET_SIZE 1024 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -81,7 +66,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -114,8 +99,8 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem status // Network connection information -const char* wifiId = "xxxxx"; // The WiFi access point -const char* wifiPwd = "xxxxx"; // The password for connecting to WiFi +const char* wifiId = "YourWiFiSSID"; // The WiFi access point +const char* wifiPwd = "YourWiFiPassword"; // The WiFi password // Create the loggerModem object EspressifESP32 modemESP(&modemSerial, modemVccPin, modemResetPin, wifiId, @@ -160,8 +145,8 @@ const int8_t alsData = A8; // The ALS PT-19 data pin #else const int8_t alsData = A4; // The ALS PT-19 data pin #endif -const int8_t alsSupply = 3.3; // The ALS PT-19 supply power voltage -const int8_t alsResistance = 10; // The ALS PT-19 loading resistance (in kΩ) +const float alsSupply = 3.3f; // The ALS PT-19 supply power voltage +const int8_t alsResistance = 10; // The ALS PT-19 loading resistance (in kΩ) const uint8_t alsNumberReadings = 10; // Create a Everlight ALS-PT19 sensor object @@ -279,7 +264,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ diff --git a/examples/AWS_IoT_Core/ReadMe.md b/examples/AWS_IoT_Core/ReadMe.md index 182297934..f7dafcab6 100644 --- a/examples/AWS_IoT_Core/ReadMe.md +++ b/examples/AWS_IoT_Core/ReadMe.md @@ -39,31 +39,31 @@ TODO ### Set your AWS IoT Core Endpoint -In line 60, find and replace the text `YOUR_ENDPOINT-ats.iot.YOUR_REGION.amazonaws.com` with your real endpoint. +Find and replace the text `YOUR_ENDPOINT-ats.iot.YOUR_REGION.amazonaws.com` with your real endpoint. Make sure there are quotation marks around the endpoint string, as there are in the example. This must be the same value you used in the AWS_IoT_SetCertificates sketch. ### Set your Thing Name -In line 62, find and replace the text `YOUR_THING_NAME` with your assigned thing name. +Find and replace the text `YOUR_THING_NAME` with your assigned thing name. Make sure there are quotation marks around the name string, as there are in the example. This must be the same value you used in the AWS_IoT_SetCertificates sketch. ### Set your Sampling Feature (Site) ID -In line 70, find and replace the text `YOUR_SAMPLING_FEATURE_ID` with your assigned sampling feature ID or UUID. +Find and replace the text `YOUR_SAMPLING_FEATURE_ID` with your assigned sampling feature ID or UUID. Make sure there are quotation marks around the name string, as there are in the example. ### Set your WiFi Credentials -In lines 117-118, find and replace the text `xxxxx` with your wifi name (SSID) and your wifi password. +Find and replace the text `YourWiFiSSID` with your WiFi name (SSID) and `YourWiFiPassword` with your WiFi password. Make sure there are quotation marks around the name string, as there are in the example. ### Set your Variable UUIDs -In lines 191-224, find and replace the text `"12345678-abcd-1234-ef00-1234567890ab` with the UUID's for each of your variables, if they have UUIDS. +In the `Start [variable_arrays]` section, find and replace the text `"12345678-abcd-1234-ef00-1234567890ab"` with the UUIDs for each of your variables, if they have UUIDs. Make sure there are quotation marks around the name string, as there are in the example. -If you do not have UUID's for your variables, delete the string entirely, leaving empty quotes (`""`). +If you do not have UUIDs for your variables, delete the string entirely, leaving empty quotes (`""`). _______ diff --git a/examples/AWS_IoT_Core/platformio.ini b/examples/AWS_IoT_Core/platformio.ini index 9083a4eb6..bccba6959 100644 --- a/examples/AWS_IoT_Core/platformio.ini +++ b/examples/AWS_IoT_Core/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel diff --git a/examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino b/examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino index be408b56a..6aa31340b 100644 --- a/examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino +++ b/examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino @@ -15,6 +15,9 @@ * @m_examplenavigation{example_envirodiy_monitoring_kit,} * ======================================================================= */ +// The Arduino library is needed for every Arduino program. +#include + // ========================================================================== // Configuration for the EnviroDIY Monitoring Station Kit // ========================================================================== @@ -30,11 +33,11 @@ // Add your network information here. // APN for cellular connection -#define CELLULAR_APN "add_your_cellular_apn" +#define CELLULAR_APN "YourAPN" // WiFi access point name -#define WIFI_ID "your_wifi_ssid" +#define WIFI_ID "YourWiFiSSID" // WiFi password (WPA2) -#define WIFI_PASSWD "your_wifi_password" +#define WIFI_PASSWD "YourWiFiPassword" /** End [configuration] */ @@ -44,7 +47,7 @@ // ========================================================================== /** Start [logging_options] */ // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -54,11 +57,11 @@ const int8_t timeZone = -5; // Eastern Standard Time // ========================================================================== -// UUID's and Registration Tokens for Monitor My Watershed +// UUIDs and Registration Tokens for Monitor My Watershed // ========================================================================== /** Start [monitor_mw_uuids] */ -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. // To get the list, click the "View token UUID list" button on the upper right // of the site page. @@ -110,26 +113,10 @@ const char* samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // Sampli /** End [monitor_mw_uuids] */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 256 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== /** Start [includes] */ -// The Arduino library is needed for every Arduino program. -#include - // Include the main header for ModularSensors #include /** End [includes] */ @@ -148,7 +135,7 @@ const char* sketchName = "EnviroDIY_Monitoring_Kit.ino"; 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) uint8_t buttonPinMode = INPUT; // mode for debugging pin const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep uint8_t wakePinMode = INPUT_PULLUP; // mode for wake pin @@ -178,9 +165,7 @@ Logger dataLogger(LoggerID, samplingFeature, loggingInterval); // Create a reference to the serial port for the modem HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible -const int32_t modemBaud = 115200; // Communication speed of the modem -// NOTE: This baud rate too fast for the Mayfly. We'll slow it down in the -// setup. +int32_t modemBaud = 57600; // Communication speed of the modem // Modem Pins - Describe the physical pin connection of your modem to your board // NOTE: Use -1 for pins that do not apply @@ -236,13 +221,13 @@ SIMComSIM7080 modem = modem7080; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion, 5); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -332,7 +317,7 @@ Variable* variableList[] = { // Count up the number of pointers in the array int variableCount = sizeof(variableList) / sizeof(variableList[0]); -// Create the VariableArray object and attach the UUID's +// Create the VariableArray object and attach the UUIDs VariableArray varArray(variableCount, variableList, UUIDs); /** End [variables_separate_uuids] */ // ========================================================================== @@ -343,9 +328,9 @@ VariableArray varArray(variableCount, variableList, UUIDs); // A Publisher to Monitor My Watershed // ========================================================================== /** Start [monitor_my_watershed_publisher] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken); /** End [monitor_my_watershed_publisher] */ #endif @@ -374,7 +359,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Uses the processor sensor object to read the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == -9999 || + if (mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == MS_INVALID_VALUE || mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == 0) { mcuBoard.update(); } @@ -414,7 +399,7 @@ void setup() { /** Start [setup_serial_begins] */ // Start the serial connection with the modem #if defined(USE_CELLULAR_BEE) || defined(USE_WIFI_BEE) - PRINTOUT(F("Starting modem connection at"), modemBaud, F(" baud")); + PRINTOUT(F("Starting modem connection at"), modemBaud, F("baud")); modemSerial.begin(modemBaud); #endif @@ -442,7 +427,7 @@ void setup() { PRINTOUT(F("Setting logging interval to"), loggingInterval, F("minutes")); dataLogger.setLoggingInterval(loggingInterval); PRINTOUT(F("Setting number of initial 1 minute intervals to 10")); - dataLogger.setinitialShortIntervals(10); + dataLogger.setStartupMeasurements(10); // Attach the variable array to the logger PRINTOUT(F("Attaching the variable array")); dataLogger.setVariableArray(&varArray); @@ -489,19 +474,13 @@ void setup() { F("baud. This will fail if the baud is mismatched..")); modemSerial.begin(modemBaud); modem.modemWake(); // NOTE: This will also set up the modem - // WARNING: PLEASE REMOVE AUTOBAUDING FOR PRODUCTION CODE! + // WARNING: PLEASE REMOVE BAUD RATE DETECTION/FORCING FOR PRODUCTION CODE! if (!modem.gsmModem.testAT()) { - PRINTOUT(F("Attempting autobauding..")); - uint32_t foundBaud = TinyGsmAutoBaud(modemSerial); - if (foundBaud != 0 || (modemBaud > 57600 && F_CPU == 8000000L)) { - PRINTOUT(F("Got modem response at baud of"), foundBaud, - F("Firing an attempt to change the baud rate to"), - modemBaud); - modem.gsmModem.sendAT(GF("+UART_DEF="), modemBaud, F(",8,1,0,0")); - modem.gsmModem.waitResponse(); - modemSerial.end(); - modemSerial.begin(modemBaud); - } + // If the AT command test fails, chances are it's a baud rate issue. Try + // to detect the baud rate and force the baud rate to the correct one. + PRINTOUT(F("Attempting to force the modem baud rate.")); + modem.gsmModem.forceModemBaud(modemSerial, + static_cast(modemBaud)); } /** End [setup_esp] */ #endif @@ -512,23 +491,14 @@ void setup() { modem.setModemResetLevel(HIGH); // ModuleFun Bee inverts the signal PRINTOUT(F("Waking modem and setting Cellular Carrier Options...")); modem.modemWake(); // NOTE: This will also set up the modem - // WARNING: PLEASE REMOVE AUTOBAUDING FOR PRODUCTION CODE! + // WARNING: PLEASE REMOVE BAUD RATE DETECTION/FORCING FOR PRODUCTION CODE! if (!modem.gsmModem.testAT()) { - PRINTOUT(F("Attempting autobauding..")); - uint32_t foundBaud = TinyGsmAutoBaud(modemSerial); - if (foundBaud != 0 && !(F_CPU <= 8000000L && foundBaud >= 115200) && - !(F_CPU <= 16000000L && foundBaud > 115200)) { - PRINTOUT(F("Got modem response at baud of"), foundBaud, - F("Firing an attempt to change the baud rate to"), - modemBaud); - modem.gsmModem.setBaud( - modemBaud); // Make sure we're *NOT* auto-bauding! - modem.gsmModem.waitResponse(); - modemSerial.end(); - modemSerial.begin(modemBaud); - } + // If the AT command test fails, chances are it's a baud rate issue. Try + // to detect the baud rate and force the baud rate to the correct one. + PRINTOUT(F("Attempting to force the modem baud rate.")); + modem.gsmModem.forceModemBaud(modemSerial, + static_cast(modemBaud)); } - modem.gsmModem.setBaud(modemBaud); // Make sure we're *NOT* auto-bauding! modem.gsmModem.setNetworkMode(38); // set to LTE only // 2 Automatic // 13 GSM only @@ -538,7 +508,7 @@ void setup() { // 1 CAT-M // 2 NB-IoT // 3 CAT-M and NB-IoT - /** End [setup_sim7080] */ +/** End [setup_sim7080] */ #endif /** Start [setup_clock] */ diff --git a/examples/EnviroDIY_Monitoring_Kit/ReadMe.md b/examples/EnviroDIY_Monitoring_Kit/ReadMe.md index 9158c2371..ccd02fddb 100644 --- a/examples/EnviroDIY_Monitoring_Kit/ReadMe.md +++ b/examples/EnviroDIY_Monitoring_Kit/ReadMe.md @@ -3,9 +3,9 @@ Example sketch to be used with the [EnviroDIY Monitoring Station Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/). This example uses the sensors and equipment included with (or recommended for) the [EnviroDIY Monitoring Station Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/). -It includes code for a Mayfly 1.x, a [Meter Hydros 21](https://metergroup.com/products/hydros-21/) and either a [SIM7080G-based EnviroDIY LTEbee](https://www.envirodiy.org/product/envirodiy-lte-bee/) or an [EnviroDIY ESP32 Bee](https://www.envirodiy.org/product/envirodiy-esp32-bee-wifi-bluetooth/) for communication. -This examples also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. -The results are saved to the SD card and posted to the Monitor My Watershed data portal. +It includes code for a Mayfly 1.x, a [Meter Hydros 21](https://metergroup.com/products/hydros-21/) and either a [SIM7080G-based EnviroDIY LTE Bee](https://www.envirodiy.org/product/envirodiy-lte-bee/) or an [EnviroDIY ESP32 Bee](https://www.envirodiy.org/product/envirodiy-esp32-bee-wifi-bluetooth/) for communication. +This example also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. +The results are saved to the SD card and posted to Monitor My Watershed. > [!NOTE] > The Meter Hydros 21 is **not** included in the [EnviroDIY Monitoring Station Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/) and must be purchased separately from Meter Group or one of their distributors. @@ -16,12 +16,16 @@ The exact hardware configuration used in this example: - [EnviroDIY SIM7080 LTE Bee](https://www.envirodiy.org/product/envirodiy-lte-bee/) (with Hologram SIM card) **OR** [EnviroDIY ESP32 Bee](https://www.envirodiy.org/product/envirodiy-esp32-bee-wifi-bluetooth/) - Hydros21 CTD sensor -The EnviroDIY LTE SIM7080 module includes 2 antennas in the package. The small thin one is the cellular antenna, and should be connected to the socket labeled "CELL". The thicker block is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. +The SIM7080-based EnviroDIY LTE Bee module includes two antennas in the package. +The small, thin one is the cellular antenna, and should be connected to the socket labeled "CELL". +The thicker block is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. +ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. -The included cell antenna works best in high-signal-strength areas. For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 -from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100) +The included cell antenna works best in high-signal-strength areas. +For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100). -Users purchasing a new Hydros21 CTD sensor will need to change the SDI-12 address of the sensor in order to use this sketch. Full instructions for using this sketch as part of a monitoring station can be found in the EnviroDIY Monitoring Station Manual. +Users purchasing a new Hydros21 CTD sensor will need to change the SDI-12 address of the sensor in order to use this sketch. +Full instructions for using this sketch as part of a monitoring station can be found in the EnviroDIY Monitoring Station Manual. _______ @@ -40,7 +44,7 @@ _______ - [Set the logger ID](#set-the-logger-id) - [Set the logging interval](#set-the-logging-interval) - [Set the time zone](#set-the-time-zone) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -73,12 +77,12 @@ Customize the sketch for the version of the kit that you have: cellular, wifi, o #### Select the Connection Type -In lines 28 and 29, select no more than one of the "bee" types that you will be using. +In the configuration section, select no more than one of the "bee" types that you will be using. -- Activate the modem you wish to use by _removing_ any slashes (```//```) before the bee module you will use. - - The line should start with ```#define``` -- Add two slashes (```//```) in front of the modem you are NOT using. -- If you are not using any internet connection, put two slashes (```//```) in front of both lines. +- Activate the modem you wish to use by _removing_ any slashes (`//`) before the bee module you will use. + - The line should start with `#define`. +- Add two slashes (`//`) in front of the modem you are NOT using. +- If you are not using any internet connection, put two slashes (`//`) in front of both lines. ```cpp #define USE_WIFI_BEE @@ -87,10 +91,10 @@ In lines 28 and 29, select no more than one of the "bee" types that you will be #### Add Connection Info -Replace the ```your_..``` with the appropriate APN or SSID and password for your network. +Replace `YourAPN` or both `YourWiFiSSID` and `YourWiFiPassword` with the appropriate APN or SSID and password for your network. Your APN is assigned by your SIM card provider. -If you are using a Hologram SIM card (recommended with the kit) the APN is ```"hologram"```. +If you are using a Hologram SIM card (recommended with the kit), the APN is `hologram`. The SSID is the name of the wifi network. @@ -102,16 +106,16 @@ You can leave the configuration for the connection type you're not using as is. ```cpp // APN for cellular connection -#define CELLULAR_APN "add_your_cellular_apn" +#define CELLULAR_APN "YourAPN" // WiFi access point name -#define WIFI_ID "your_wifi_ssid" +#define WIFI_ID "YourWiFiSSID" // WiFi password (WPA2) -#define WIFI_PASSWD "your_wifi_password" +#define WIFI_PASSWD "YourWiFiPassword" ``` ### Set Data Logging Options -Customize your data logging options in lines 42 to 53 of the example. +Customize your data logging options in the `Data Logging Options` section of the example. #### Set the logger ID @@ -119,7 +123,7 @@ We recommend using your logger's serial number as the logger ID. ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` #### Set the logging interval @@ -144,11 +148,11 @@ Please use standard time! const int8_t timeZone = -5; // Eastern Standard Time ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable - Go back to the web page for your site on [Monitor My Watershed](http://monitormywatershed.org/) - Find and click the white "View Token UUID List" button above the small map on your site page. -- Paste the copied UUIDs into your sketch, _replacing_ lines 91-106. +- Paste the copied UUIDs into your sketch, _replacing_ the text between `Beginning of Token UUID List` and `End of Token UUID List`. ```cpp // --------------------- Beginning of Token UUID List --------------------- @@ -174,7 +178,7 @@ const char* samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // Sampli ``` - VERY CAREFULLY check the order of the UUIDs that you have copied in. -The UUIDs _**MUST**_ be in the following order: + The UUIDs _**MUST**_ be in the following order: - Specific conductance (Meter_Hydros21_Cond) - Water depth (Meter_Hydros21_Depth) - Temperature (Meter_Hydros21_Temp) diff --git a/examples/EnviroDIY_Monitoring_Kit/platformio.ini b/examples/EnviroDIY_Monitoring_Kit/platformio.ini index 8881915dc..cc6cc0662 100644 --- a/examples/EnviroDIY_Monitoring_Kit/platformio.ini +++ b/examples/EnviroDIY_Monitoring_Kit/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 57600 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/DRWI_2G.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/DRWI_2G.ino index 1371dd8eb..047c0aa06 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/DRWI_2G.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/DRWI_2G.ino @@ -11,18 +11,6 @@ * @m_examplenavigation{example_drwi_2g,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -42,7 +30,7 @@ // The name of this program file const char* sketchName = "DRWI_CitSci.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -54,7 +42,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -85,7 +73,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status (-1 if unconnected) // Network connection information -const char* apn = "hologram"; // The APN for the gprs connection +const char* apn = "YourAPN"; // The APN for the gprs connection Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn); // Create an extra reference to the modem by a generic name @@ -96,13 +84,13 @@ Sodaq2GBeeR6 modem = modem2GB; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v0.5b"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -124,7 +112,6 @@ MaximDS3231 ds3231(1); const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected) const uint8_t OBS3NumberReadings = 10; -const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC // Campbell OBS 3+ *Low* Range Calibration in Volts const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range] @@ -133,7 +120,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - ADSi2c_addr, OBS3NumberReadings); + OBS3NumberReadings); // Campbell OBS 3+ *High* Range Calibration in Volts @@ -144,7 +131,7 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NumberReadings); /** End [obs3] */ @@ -180,13 +167,13 @@ Variable* variableList[] = { new Modem_RSSI(&modem), new Modem_SignalPercent(&modem)}; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. To get the list, click the "View // token UUID list" button on the upper right of the site page. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** /* clang-format off */ @@ -226,10 +213,10 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -253,7 +240,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md index eccc55ddf..dd477d0f8 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md @@ -1,6 +1,7 @@ # DRWI 2G Sites -This code was used for DRWI monitoring stations in 2016-2022. The 2G GPRSbee cellular boards no longer function in the USA, so this code should not be used and is only provided to archival and reference purposes. +This code was used for DRWI monitoring stations in 2016-2022. +The 2G GPRSbee cellular boards no longer function in the USA, so this code should not be used and is only provided for archival and reference purposes. The exact hardware configuration used in this example: @@ -23,7 +24,7 @@ _______ - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - [Set the logger ID](#set-the-logger-id) - [Set the calibration coefficients for the Campbell OBS3+](#set-the-calibration-coefficients-for-the-campbell-obs3) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -39,7 +40,7 @@ _______ ### Prepare and set up PlatformIO -- Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal () +- Register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org) - Create a new PlatformIO project - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/DRWI_CitSci/platformio.ini) file in the examples/DRWI_CitSci 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. @@ -51,18 +52,19 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID. + For most DRWI installations, the logger ID was assigned by the Stroud Water Research Center before the station was built. ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Set the calibration coefficients for the Campbell OBS3+ - The OBS3+ ships with a calibration certificate; you need this sheet! -- Change _**all**_ of the the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. -Use numbers from the side of the calibration sheet that shows the calibration in _**volts**_. +- Change _**all**_ of the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. + Use numbers from the side of the calibration sheet that shows the calibration in _**volts**_. - The sketch will not compile if these values are not entered properly. - Do not change any values except those that are `0.000E+00` and `1.000E+00`! @@ -88,9 +90,9 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, OBSHigh_C, ADSi2c_addr, OBS3numberReadings); ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable -- Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal () +- Go back to the web page for your site on [Monitor My Watershed](https://monitormywatershed.org) - Find and click the white "View Token UUID List" button above the small map on your site page - **VERY CAREFULLY** check that the variables are in exactly the same order as in the variable array: @@ -101,13 +103,13 @@ Variable* variableList[] = { ``` - If any of the variables are in a different order on the web page than in your code **reorder the variables in your code to match the website**. -- After you are completely certain that you have the order right in the variable section of your code use the teal "Copy" button on the website to copy the section of code containing all of the UUID's. +- After you are completely certain that you have the order right in the variable section of your code, use the teal "Copy" button on the website to copy the section of code containing all the UUIDs. - Paste the code from the website into your program in this section below the variable array ```cpp // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** /* clang-format off */ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/platformio.ini b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/platformio.ini index 5fb25f094..da1f78fb1 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/platformio.ini +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/platformio.ini @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/DRWI_DigiLTE.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/DRWI_DigiLTE.ino index eb0826a08..034f48609 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/DRWI_DigiLTE.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/DRWI_DigiLTE.ino @@ -12,18 +12,6 @@ * @m_examplenavigation{example_drwi_digilte,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -43,7 +31,7 @@ // The name of this program file const char* sketchName = "DRWI_DigiLTE.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -55,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time const int32_t serialBaud = 57600; // 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -91,7 +79,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status (-1 if unconnected) // Network connection information -const char* apn = "hologram"; // The APN for the gprs connection +const char* apn = "YourAPN"; // The APN for the gprs connection DigiXBeeCellularTransparent modemXBCT(&modemSerial, modemVccPin, modemStatusPin, useCTSforStatus, modemResetPin, @@ -104,13 +92,13 @@ DigiXBeeCellularTransparent modem = modemXBCT; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v0.5b"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -132,7 +120,6 @@ MaximDS3231 ds3231(1); const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected) const uint8_t OBS3NumberReadings = 10; -const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC // Campbell OBS 3+ *Low* Range Calibration in Volts const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range] @@ -141,7 +128,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - ADSi2c_addr, OBS3NumberReadings); + OBS3NumberReadings); // Campbell OBS 3+ *High* Range Calibration in Volts @@ -152,7 +139,7 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NumberReadings); /** End [obs3] */ @@ -188,14 +175,14 @@ Variable* variableList[] = { new Modem_SignalPercent(&modem), }; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. // To get the list, click the "View token UUID list" button on the upper right // of the site page. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list ABOVE if necessary to match! // Do not change the order of the variables in the section below. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** @@ -246,10 +233,10 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -273,7 +260,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md index 153b4e735..e8a5c79d4 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md @@ -1,6 +1,9 @@ # DRWI Digi LTE Sites -This example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. It includes a Meter Hydros21 CTD (formerly know as a Decagon), a Campbell OBS3+ (Turbidity), and a Digi XBee3 LTE-M cellular board for communication. The Digi LTE module also required the use of a EnviroDIY LTEBee Adapter board (discontinued in 2021). The Digi LTE modules are no longer recommended for use and have been replace by the EnviroDIY LTEBee in all DRWI-SWRC-managed stations. +This example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. +It includes a Meter Hydros21 CTD (formerly known as a Decagon), a Campbell OBS3+ (Turbidity), and a Digi XBee3 LTE-M cellular board for communication. +The Digi LTE module also required the use of an EnviroDIY LTE Bee Adapter board (discontinued in 2021). +The Digi LTE modules are no longer recommended for use and have been replaced by the EnviroDIY LTE Bee in all DRWI-SWRC-managed stations. The exact hardware configuration used in this example: @@ -23,7 +26,7 @@ _______ - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - [Set the logger ID](#set-the-logger-id) - [Set the calibration coefficients for the Campbell OBS3+](#set-the-calibration-coefficients-for-the-campbell-obs3) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -40,7 +43,7 @@ _______ ### Prepare and set up PlatformIO -- Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal () +- Register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org) - Create a new PlatformIO project - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/DRWI_DigiLTE/platformio.ini) file in the examples/DRWI_DigiLTE folder on GitHub. @@ -53,19 +56,20 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID. + For most DRWI installations, the logger ID was assigned by the Stroud Water Research Center before the station was built. ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Set the calibration coefficients for the Campbell OBS3+ - The OBS3+ ships with a calibration certificate; you need this sheet! -- Change _**all**_ of the the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. -Use numbers from the side of the calibration sheet that shows the calibration in _**volts**_. +- Change _**all**_ of the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. + Use numbers from the side of the calibration sheet that shows the calibration in _**volts**_. - The sketch will not compile if these values are not entered properly. - Do not change any values except those that are `0.000E+00` and `1.000E+00`! @@ -91,9 +95,9 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, OBSHigh_C, ADSi2c_addr, OBS3numberReadings); ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable -- Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal () +- Go back to the web page for your site on [Monitor My Watershed](https://monitormywatershed.org) - Find and click the white "View Token UUID List" button above the small map on your site page - **VERY CAREFULLY** check that the variables are in exactly the same order as in the variable array: @@ -105,13 +109,13 @@ Variable* variableList[] = { ``` - If any of the variables are in a different order on the web page than in your code **reorder the variables in your code to match the website**. -- After you are completely certain that you have the order right in the variable section of your code use the teal "Copy" button on the website to copy the section of code containing all of the UUID's. +- After you are completely certain that you have the order right in the variable section of your code, use the teal "Copy" button on the website to copy the section of code containing all the UUIDs. - Paste the code from the website into your program in this section below the variable array ```cpp // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** /* clang-format off */ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/DRWI_Mayfly1.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/DRWI_Mayfly1.ino index dad595122..09bf5e7db 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/DRWI_Mayfly1.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/DRWI_Mayfly1.ino @@ -18,18 +18,6 @@ * @m_examplenavigation{example_drwi_mayfly1,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -49,7 +37,7 @@ // The name of this program file const char* sketchName = "DRWI_Mayfly1.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -61,7 +49,7 @@ const int8_t timeZone = -5; // Eastern Standard Time const int32_t serialBaud = 57600; // 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x, 1.x D31 = A7 const int8_t sdCardPwrPin = -1; // MCU SD card power pin @@ -96,7 +84,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // Network connection information const char* apn = - "hologram"; // APN connection name, typically Hologram unless you have a + "YourAPN"; // APN connection name, typically Hologram unless you have a // different provider's SIM card. Change as needed // Create the modem object @@ -110,13 +98,13 @@ SIMComSIM7080 modem = modem7080; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -195,14 +183,14 @@ Variable* variableList[] = { new Modem_SignalPercent(&modem), // Percent full scale (EnviroDIY_LTEB_SignalPercent) }; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. // To get the list, click the "View token UUID list" button on the upper right // of the site page. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list ABOVE if necessary to match! // Do not change the order of the variables in the section below. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** @@ -252,10 +240,10 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -279,7 +267,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md index 2c2d733d7..45aa520bd 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md @@ -2,7 +2,11 @@ Example sketch for using the EnviroDIY SIM7080G LTE cellular module with an EnviroDIY Mayfly Data Logger. -This example uses the sensors and equipment used by most groups participating in the DRWI (Delaware River Watershed Initiative) Citizen Science project with the Stroud Water Research Center. It includes a Meter Hydros 21 (CTD) and a SIM7080G-based EnviroDIY LTEbee for communication. This examples also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. The results are saved to the SD card and posted to the Monitor My Watershed data portal. Only to be used with newer Mayfly v1.0 and v1.1 boards. +This example uses the sensors and equipment used by most groups participating in the DRWI (Delaware River Watershed Initiative) Citizen Science project with the Stroud Water Research Center. +It includes a Meter Hydros 21 (CTD) and a SIM7080G-based EnviroDIY LTE Bee for communication. +This example also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. +The results are saved to the SD card and posted to Monitor My Watershed. +This example is only to be used with newer Mayfly v1.0 and v1.1 boards. The exact hardware configuration used in this example: @@ -10,16 +14,19 @@ The exact hardware configuration used in this example: - EnviroDIY SIM7080 LTE module (with Hologram SIM card) - Hydros21 CTD sensor -An EnviroDIY LTE SIM7080 module can be used with the older Mayfly v0.5b boards if you change line 101 (for modemVccPin) from 18 to -1. +The SIM7080-based EnviroDIY LTE Bee module can be used with the older Mayfly v0.5b boards if you change the modemVccPin from 18 to -1. This is because the Mayfly v1.x board has a separate 3.3v regulator to power the Bee socket and is controlled by turning pin 18 on or off. Mayfly v0.5b has the Bee socket constantly powered, therefore using "-1" is the proper setting for that line of code. -The EnviroDIY LTE SIM7080 module includes 2 antennas in the package. The small thin one is the cellular antenna, and should be connected to the socket labeled "CELL". The thicker block is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. +The SIM7080-based EnviroDIY LTE Bee module includes two antennas in the package. +The small, thin one is the cellular antenna, and should be connected to the socket labeled "CELL". +The thicker block is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. +ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. -The included cell antenna works best in high-signal-strength areas. For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 -from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100) +For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100). -Users purchasing a new Hydros21 CTD sensor will need to change the SDI-12 address of the sensor in order to use this sketch. Full instructions for using this sketch as part of a monitoring station can be found in the EnviroDIY Monitoring Station Manual. +Users purchasing a new Hydros21 CTD sensor will need to change the SDI-12 address of the sensor in order to use this sketch. +Full instructions for using this sketch as part of a monitoring station can be found in the EnviroDIY Monitoring Station Manual. _______ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino index f269711ce..19162aeb4 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino @@ -15,18 +15,6 @@ * @m_examplenavigation{example_drwi_mayfly1_wifi,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 256 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -46,7 +34,7 @@ // The name of this program file const char* sketchName = "DRWI_Mayfly1_WiFi.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -58,7 +46,7 @@ const int8_t timeZone = -5; // Eastern Standard Time const int32_t serialBaud = 57600; // 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x, 1.x D31 = A7 const int8_t sdCardPwrPin = -1; // MCU SD card power pin @@ -89,8 +77,8 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status // Network connection information -const char* wifiId = "xxxxx"; // WiFi access point name -const char* wifiPwd = "xxxxx"; // WiFi password (WPA2) +const char* wifiId = "YourWiFiSSID"; // WiFi access point name +const char* wifiPwd = "YourWiFiPassword"; // WiFi password (WPA2) // Create the modem object EspressifESP32 modemESP(&modemSerial, modemVccPin, modemResetPin, wifiId, @@ -103,13 +91,13 @@ EspressifESP32 modem = modemESP; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -172,14 +160,14 @@ Variable* variableList[] = { new Modem_SignalPercent(&modem), // Percent full scale (EnviroDIY_LTEB_SignalPercent) }; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. // To get the list, click the "View token UUID list" button on the upper right // of the site page. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list ABOVE if necessary to match! // Do not change the order of the variables in the section below. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** @@ -227,10 +215,10 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -254,7 +242,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } @@ -308,7 +296,6 @@ void setup() { // Begin the logger dataLogger.begin(); - EnviroDIYPost.begin(dataLogger, registrationToken, samplingFeature); // Note: Please change these battery voltages to match your battery // Set up the sensors, except at lowest battery level diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md index d6279c4e6..638d62671 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md @@ -8,13 +8,15 @@ The exact hardware configuration used in this example: - EnviroDIY ESP32 WiFi module - Hydros21 CTD sensor -An EnviroDIY ESP32 WiFi module can also be used with the older Mayfly v0.5b boards if you change line 86 (for modemVccPin) from 18 to -1. -This is because the Mayfly v1.x board has a separate 3.3v regulator to power the Bee socket and is controlled by turning pin 18 on or off. -Mayfly v0.5b has the Bee socket constantly powered, therefore using "-1" is the proper setting for that line of code. Leave the modemVccPin as 18 for Mayfly version 1.0 and 1.1. +An EnviroDIY ESP32 WiFi module can also be used with the older Mayfly v0.5b boards if you change the modemVccPin from 18 to -1. +This is because the Mayfly v1.x board has a separate 3.3V regulator to power the Bee socket, which is controlled by turning pin 18 on or off. +Mayfly v0.5b has the Bee socket constantly powered, therefore using "-1" is the proper setting for that line of code. +Leave the modemVccPin as 18 for Mayfly version 1.0 and 1.1. The WiFi antenna is built into the ESP32 Bee - no external antenna is needed -Be sure to edit lines 92 and 93 to enter your Wifi access point name and password, and edit the UUID section beginning at line 200 with the correct UUIDs from your specific site on MonitorMyWatershed. +Find and replace `YourWiFiSSID` and `YourWiFiPassword` with your real SSID and password. +Edit the UUID section between `Beginning of Token UUID List` and `End of Token UUID List` with the correct UUIDs from your specific site on MonitorMyWatershed. _______ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/DRWI_NoCellular.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/DRWI_NoCellular.ino index 77a0a0f40..bda662e3f 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/DRWI_NoCellular.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/DRWI_NoCellular.ino @@ -31,7 +31,7 @@ // The name of this program file const char* sketchName = "DRWI_NoCellular.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -43,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 const int8_t sdCardPwrPin = -1; // MCU SD card power pin @@ -55,13 +55,13 @@ const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -83,7 +83,6 @@ MaximDS3231 ds3231(1); const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected) const uint8_t OBS3NumberReadings = 10; -const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC // Campbell OBS 3+ *Low* Range Calibration in Volts const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range] @@ -92,7 +91,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - ADSi2c_addr, OBS3NumberReadings); + OBS3NumberReadings); // Campbell OBS 3+ *High* Range Calibration in Volts @@ -103,7 +102,7 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NumberReadings); /** End [obs3] */ @@ -138,7 +137,7 @@ Variable* variableList[] = { new MaximDS3231_Temp(&ds3231), }; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. To get the list, click the "View // token UUID list" button on the upper right of the site page. // Even if not publishing live data, this is needed so the logger file will be @@ -146,7 +145,7 @@ Variable* variableList[] = { // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** /* clang-format off */ @@ -200,7 +199,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md index 04845e7da..e2ed2e9ff 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md @@ -9,10 +9,11 @@ The exact hardware configuration used in this example: - Hydros21 CTD sensor - Campbell OBS3+ turbidity sensor -Before using this example, you must register a site and sensors at the data portal (). -After you have registered the site and sensors, the portal will generate a registration token and universally unique identifier (UUID) for each site and further UUID's for each variable. -You will need to copy all of those UUID values into your sketch to replace the `12345678-abcd-1234-ef00-1234567890ab` place holders in this example. -__You should register even if your logger will not be sending live data.__ This ensures that the data file your logger writes will be ready to immediately upload to the portal. +Before using this example, you must register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org). +After you have registered the site and sensors, the portal will generate a registration token and universally unique identifier (UUID) for each site and further UUIDs for each variable. +You will need to copy all of those UUID values into your sketch to replace the `12345678-abcd-1234-ef00-1234567890ab` placeholders in this example. +__You should register even if your logger will not be sending live data.__ +This ensures that the data file your logger writes will be ready to immediately upload to the portal. _______ @@ -28,7 +29,7 @@ _______ - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - [Set the logger ID](#set-the-logger-id) - [Set the calibration coefficients for the Campbell OBS3+](#set-the-calibration-coefficients-for-the-campbell-obs3) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -44,7 +45,7 @@ _______ ### Prepare and set up PlatformIO -- Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal () +- Register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org) - Create a new PlatformIO project - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/DRWI_NoCellular/platformio.ini) file in the examples/DRWI_NoCellular 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. @@ -56,18 +57,19 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID. + For most DRWI installations, the logger ID was assigned by the Stroud Water Research Center before the station was built. ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Set the calibration coefficients for the Campbell OBS3+ - The OBS3+ ships with a calibration certificate; you need this sheet! -- Change *__all__* of the the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. -Use numbers from the side of the calibration sheet that shows the calibration in *__volts__*. +- Change *__all__* of the `0.000E+00` and `1.000E+00` values in this section of code to the values on that calibration sheet. + Use numbers from the side of the calibration sheet that shows the calibration in *__volts__*. - The sketch will not compile if these values are not entered properly. - Do not change any values except those that are `0.000E+00` and `1.000E+00`! @@ -93,9 +95,9 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, OBSHigh_C, ADSi2c_addr, OBS3numberReadings); ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable -- Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal () +- Go back to the web page for your site on [Monitor My Watershed](https://monitormywatershed.org) - Find and click the white "View Token UUID List" button above the small map on your site page - __VERY CAREFULLY__ check that the variables are in exactly the same order as in the variable array: @@ -106,13 +108,13 @@ Variable* variableList[] = { ``` - If any of the variables are in a different order on the web page than in your code __reorder the variables in your code to match the website__. -- After you are completely certain that you have the order right in the variable section of your code use the teal "Copy" button on the website to copy the section of code containing all of the UUID's. +- After you are completely certain that you have the order right in the variable section of your code, use the teal "Copy" button on the website to copy the section of code containing all the UUIDs. - Paste the code from the website into your program in this section below the variable array ```cpp // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list if necessary to match! // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** /* clang-format off */ diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/platformio.ini b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/platformio.ini index b54c81223..9db773578 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/platformio.ini +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/platformio.ini @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/DRWI_SIM7080LTE.ino b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/DRWI_SIM7080LTE.ino index ac8ca717b..6e9c377cc 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/DRWI_SIM7080LTE.ino +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/DRWI_SIM7080LTE.ino @@ -19,18 +19,6 @@ * @m_examplenavigation{example_drwi_ediylte,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -50,7 +38,7 @@ // The name of this program file const char* sketchName = "DRWI_SIM7080LTE.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -62,7 +50,7 @@ const int8_t timeZone = -5; // Eastern Standard Time const int32_t serialBaud = 57600; // 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 const int8_t sdCardPwrPin = -1; // MCU SD card power pin @@ -97,7 +85,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // Network connection information const char* apn = - "hologram"; // APN connection name, typically Hologram unless you have a + "YourAPN"; // APN connection name, typically Hologram unless you have a // different provider's SIM card. Change as needed // Create the modem object @@ -111,13 +99,13 @@ SIMComSIM7080 modem = modem7080; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -156,7 +144,6 @@ MeterHydros21 hydros(*hydrosSDI12address, SDI12Power, SDI12Data, const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected) const uint8_t OBS3NumberReadings = 10; -const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC // Campbell OBS 3+ *Low* Range Calibration in Volts const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range] @@ -165,7 +152,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - ADSi2c_addr, OBS3NumberReadings); + OBS3NumberReadings); // Campbell OBS 3+ *High* Range Calibration in Volts @@ -176,7 +163,7 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NumberReadings); /** End [obs3] */ @@ -195,14 +182,14 @@ Variable* variableList[] = { new Modem_SignalPercent(&modem), }; -// All UUID's, device registration, and sampling feature information can be +// All UUIDs, device registration, and sampling feature information can be // pasted directly from Monitor My Watershed. // To get the list, click the "View token UUID list" button on the upper right // of the site page. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** // Check the order of your variables in the variable list!!! -// Be VERY certain that they match the order of your UUID's! +// Be VERY certain that they match the order of your UUIDs! // Rearrange the variables in the variable list ABOVE if necessary to match! // Do not change the order of the variables in the section below. // *** CAUTION --- CAUTION --- CAUTION --- CAUTION --- CAUTION *** @@ -222,7 +209,7 @@ const char* UUIDs[] = // UUID array for device sensors "12345678-abcd-1234-ef00-1234567890ab", // Turbidity (Campbell_OBS3_Turb) (Low) "12345678-abcd-1234-ef00-1234567890ab", // Turbidity (Campbell_OBS3_Turb) (High) "12345678-abcd-1234-ef00-1234567890ab", // Battery voltage (EnviroDIY_Mayfly_Batt) - "12345678-abcd-1234-ef00-1234567890ab", // Battery voltage (EnviroDIY_Mayfly_Batt) + "12345678-abcd-1234-ef00-1234567890ab", // Temperature (Maxim_DS3231_Temp) "12345678-abcd-1234-ef00-1234567890ab", // Percent full scale (EnviroDIY_LTEB_SignalPercent) }; const char* registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token @@ -253,10 +240,10 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -280,7 +267,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } diff --git a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/ReadMe.md b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/ReadMe.md index d2631c2f9..55f2487b2 100644 --- a/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/ReadMe.md +++ b/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/ReadMe.md @@ -1,6 +1,7 @@ # DRWI Sites with EnviroDIY LTE Bees -The DRWI EnviroDIY LTEbee example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. It includes a Meter Hydros 21 (CTD), a Campbell OBS3+, (Turbidity) and a SIM7080G-based EnviroDIY LTEbee for communication. +The DRWI EnviroDIY LTE Bee example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. +It includes a Meter Hydros 21 (CTD), a Campbell OBS3+ (Turbidity), and a SIM7080G-based EnviroDIY LTE Bee for communication. The exact hardware configuration used in this example: @@ -9,14 +10,17 @@ The exact hardware configuration used in this example: - Hydros21 CTD sensor - Campbell Scientific OBS3+ Turbidity sensor -An EnviroDIY LTE SIM7080 module can be used with the older Mayfly v0.5b boards if you change line 101 (for modemVccPin) from 18 to -1. -This is because the Mayfly v1.0 board has a separate 3.3v regulator to power the Bee socket and is controlled by turning pin 18 on or off. -Mayfly v0.5b has the Bee socket constantly powered, therefore using "-1" is the proper setting for that line of code. +A SIM7080-based EnviroDIY LTE Bee module can be used with the older Mayfly v0.5b boards if you change the modemVccPin from 18 to -1. +This is because the Mayfly v1.0 board contains a separate 3.3V regulator that powers the Bee socket; that regulator is enabled/disabled by toggling pin 18. +The Mayfly v0.5b has the Bee socket constantly powered, therefore using "-1" is the proper setting for that line of code. -The EnviroDIY LTE SIM7080 module includes 2 antennas in the package. The small thin one is the cellular antenna, and should be connected to the socket labeled "CELL". The thicker block is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. +The SIM7080-based EnviroDIY LTE Bee module includes two antennas in the package. +The small, thin one is the cellular antenna, and should be connected to the socket labeled "CELL". +The thicker one is the GPS antenna, and should be connected to the "GPS" socket, but only if you intend to use the GPS functionality of the module. +ModularSensors does not currently support GPS functionality, but other libraries such as TinyGPS can work with the SIM7080 module. -The included cell antenna works best in high-signal-strength areas. For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 -from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100) +The included cell antenna works best in high-signal-strength areas. +For most remote areas and logger deployments, we suggest a larger LTE antenna, like the W3907B0100 from PulseLarsen (Digikey 1837-1003-ND or Mouser 673-W3907B0100). _______ diff --git a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md index f897d56f7..7b6e4bc0b 100644 --- a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md +++ b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md @@ -6,7 +6,7 @@ The processor then goes to sleep between readings. This example calls on two of the sensors available in this library. The example may be run exactly as written. -This is the example you should use to deploy a logger somewhere where you don't want or have access to a way of streaming live data and you won't want to upload data to the Monitor My Watershed data portal. +Use this example to deploy a logger when you cannot stream live data or do not want to upload data to Monitor My Watershed. _______ @@ -40,16 +40,17 @@ _______ - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging_LearnEnviroDIY/platformio.ini) file in the examples/simple_logging_LearnEnviroDIY 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 [simple_logging_LearnEnviroDIY.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino) and save it to your computer. Put it into the src directory of your project. +- Open [simple_logging_LearnEnviroDIY.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino) and save it to your computer. + Put 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: +- Change the text `YourLoggerID` in this section of code to your logger ID or serial number: ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Upload! diff --git a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/platformio.ini b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/platformio.ini index 08d007b3e..921815d02 100644 --- a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/platformio.ini +++ b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/platformio.ini @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino index 5493f6868..c4b4c649c 100644 --- a/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino +++ b/examples/OutdatedExamples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino @@ -31,7 +31,7 @@ // The name of this program file const char* sketchName = "simple_logging.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -43,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -57,13 +57,13 @@ const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== diff --git a/examples/ReadMe.md b/examples/ReadMe.md index 46d755b20..a9e4c5164 100644 --- a/examples/ReadMe.md +++ b/examples/ReadMe.md @@ -18,9 +18,10 @@ ___ - [Calculations and Complex Logging](#calculations-and-complex-logging) - [Barometric Pressure Correction](#barometric-pressure-correction) - [Multiple Logging Intervals](#multiple-logging-intervals) - - [Minimizing Cell Data Usage](#minimizing-cell-data-usage) - [Everything at Once - a la carte](#everything-at-once---a-la-carte) - [Menu a la carte](#menu-a-la-carte) + - [EnviroDIY Sensor Stations](#envirodiy-sensor-stations) + - [The EnviroDIY Sensor Station Kit](#the-envirodiy-sensor-station-kit) - [Examples for Outdated Hardware](#examples-for-outdated-hardware) - [Simple Logging for the Learn EnviroDIY course](#simple-logging-for-the-learn-envirodiy-course) - [DRWI Citizen Science](#drwi-citizen-science) @@ -28,7 +29,7 @@ ___ - [DRWI CitSci No Cellular](#drwi-citsci-no-cellular) - [DRWI CitSci (2G)](#drwi-citsci-2g) - [DRWI Digi LTE](#drwi-digi-lte) - - [DRWI EnviroDIY LTEbee](#drwi-envirodiy-ltebee) + - [DRWI EnviroDIY LTE Bee](#drwi-envirodiy-lte-bee) @@ -59,7 +60,7 @@ ___ ### Publishing to Monitor My Watershed -The logging to Monitor My Watershed example uses a Digi XBee in transparent mode to publish data live from a BME280 and Maxim DS18 to the Monitor My Watershed data portal. +The logging to Monitor My Watershed example uses a Digi XBee in transparent mode to publish data live from a BME280 and Maxim DS18. - [Instructions for the logging to Monitor My Watershed example](https://envirodiy.github.io/ModularSensors/example_mmw.html) - [The logging to Monitor My Watershed example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/logging_to_MMW) @@ -67,7 +68,7 @@ The logging to Monitor My Watershed example uses a Digi XBee in transparent mode ### Publishing to ThingSpeak The logging to ThingSpeak example uses an ESP8266 to send data to ThingSpeak. -It also includes a Meter Hydros 21 (formerly know as a Decagon CTD) and a Campbell OBS3+. +It also includes a Meter Hydros 21 (formerly known as a Decagon CTD) and a Campbell OBS3+. - [Instructions for the logging to ThingSpeak example](https://envirodiy.github.io/ModularSensors/example_thingspeak.html) - [The logging to ThingSpeak example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/logging_to_ThingSpeak) @@ -98,16 +99,6 @@ This showcases both how to use two different logging instances and how to use so - [Instructions for the double logger example](https://envirodiy.github.io/ModularSensors/example_double_log.html) - [The double logger example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/double_logger) -### Minimizing Cell Data Usage - -The data saving example 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. -This example also shows how to stop power draw from an RS485 adapter with automatic flow detection. - -- [Instructions for the data saving example](https://envirodiy.github.io/ModularSensors/example_data_saving.html) -- [The data saving example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/data_saving) - ___ ## Everything at Once - a la carte @@ -117,8 +108,8 @@ ___ The "menu a la carte" example shows most of the functions of the library in one gigantic program. It has code in it for every possible sensor and modem and for both AVR and SAMD boards. It is also over 1500 lines long. -This examples is intended to be used like an a la carte menu of all possible options where you selected only the portions of code pertinent to you and delete everything else. -This example is *NOT* intended to be run in its entirety +This example is intended to serve as an a la carte menu of all available options, allowing you to select only the portions of code pertinent to you and delete everything else. +This example is *NOT* intended to be run in its entirety. - [The menu a la carte walkthrough](https://envirodiy.github.io/ModularSensors/example_menu.html) - Unlike the instructions for the other examples which show how to modify the example for your own use, this is a chunk-by-chunk step through of the code with explanations of each portion of the code and links to further documentation on each sensor. @@ -127,6 +118,17 @@ This example is *NOT* intended to be run in its entirety ___ +## EnviroDIY Sensor Stations + +### The EnviroDIY Sensor Station Kit + +This example is designed to be used with the hardware in the [EnviroDIY Monitoring Station Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/). + +- [Instructions for the EnviroDIY sensor station kit example](https://envirodiy.github.io/ModularSensors/example_envirodiy_monitoring_kit.html) +- [The EnviroDIY sensor station kit example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/EnviroDIY_Monitoring_Kit) + +___ + ## Examples for Outdated Hardware ### Simple Logging for the Learn EnviroDIY course @@ -144,12 +146,13 @@ ___ #### DRWI Mayfly 1.x LTE The DRWI Mayfly 1.x LTE example uses the sensors and equipment used by most groups participating in the DRWI (Delaware River Watershed Initiative) Citizen Science project with the Stroud Water Research Center. -It includes a Meter Hydros 21 (CTD) and a SIM7080G-based EnviroDIY LTEbee for communication. -This examples also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. -The results are saved to the SD card and posted to the Monitor My Watershed data portal. Only to be used with newer Mayfly v1.0 and v1.1 boards. +It includes a Meter Hydros 21 (CTD) and a SIM7080G-based EnviroDIY LTE Bee for communication. +This example also makes use of the on-board light, temperature, and humidity sensors on the Mayfly 1.x. +The results are saved to the SD card and posted to Monitor My Watershed. +Only to be used with newer Mayfly v1.0 and v1.1 boards. - [Instructions for the Mayfly 1.x LTE DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_mayfly1.html) -- [The LTEG DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedHardware/DRWI_CitizenScience/DRWI_Mayfly1) +- [The Mayfly 1.x LTE DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1) #### DRWI CitSci No Cellular @@ -158,34 +161,35 @@ It includes a Meter Hydros 21 (CTD) and a Campbell OBS3+ (Turbidity). The exclusion of the modem and publisher simplifies the code from the other DRWI examples and uses less power than running one of cellular versions without attaching the modem. - [Instructions for the no-cellular DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_no_cell.html) -- [The no-cellular DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedHardware/DRWI_CitizenScience/DRWI_NoCellular) +- [The no-cellular DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular) #### DRWI CitSci (2G) -The 2G DRWI Citizen Science example uses the sensors and equipment found on older stations used in the DRWI Citizen Science project prior to 2020. The 2G GPRSbee boards no longer function in the USA, so this code should not be used and is only provided to archival and reference purposes. -It includes a Meter Hydros 21 (formerly know as a Decagon CTD), a Campbell OBS3+, and a Sodaq GPRSBee for communication. -The results are saved to the SD card and posted to the Monitor My Watershed data portal. +The 2G DRWI Citizen Science example uses the sensors and equipment found on older stations used in the DRWI Citizen Science project before 2020. +The 2G GPRSbee boards no longer function in the USA, so this code should not be used and is only provided for archival and reference purposes. +It includes a Meter Hydros 21 (formerly known as a Decagon CTD), a Campbell OBS3+, and a Sodaq GPRSBee for communication. +The results are saved to the SD card and posted to Monitor My Watershed. The only difference between this and the other cellular DRWI examples is the type of modem used. - [Instructions for the 2G DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_2g.html) -- [The 2G DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedHardware/DRWI_CitizenScience/DRWI_CitSci) +- [The 2G DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_CitSci) #### DRWI Digi LTE The DRWI Digi LTE example uses the sensors and equipment common to older stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. -It includes a Meter Hydros 21 (formerly know as a Decagon CTD), a Campbell OBS3+, and a Digi XBee3 LTE-M for communication. -The results are saved to the SD card and posted to the Monitor My Watershed data portal. +It includes a Meter Hydros 21 (formerly known as a Decagon CTD), a Campbell OBS3+, and a Digi XBee3 LTE-M for communication. +The results are saved to the SD card and posted to Monitor My Watershed. The only difference between this and the other cellular DRWI examples is the type of modem used. - [Instructions for the Digi LTE DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_digilte.html) -- [The Digi LTE DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedHardware/DRWI_CitizenScience/DRWI_DigiLTE) +- [The Digi LTE DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE) -#### DRWI EnviroDIY LTEbee +#### DRWI EnviroDIY LTE Bee -The DRWI EnviroDIY LTEbee example uses the sensors and equipment common to newer stations (2016-2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. -It includes a Meter Hydros 21 (CTD), a Campbell OBS3+, (Turbidity) and a SIM7080G-based EnviroDIY LTEbee for communication. -The results are saved to the SD card and posted to the Monitor My Watershed data portal. -The only difference between this and the other cellular DRWI examples below is the type of modem used. +The DRWI EnviroDIY LTE Bee example uses the sensors and equipment common to newer stations (after 2020) deployed by groups participating in the DRWI Citizen Science project with the Stroud Water Research Center. +It includes a Meter Hydros 21 (CTD), a Campbell OBS3+ (Turbidity), and a SIM7080G-based EnviroDIY LTE Bee for communication. +The results are saved to the SD card and posted to Monitor My Watershed. +The only difference between this and the other cellular DRWI examples above is the type of modem used. -- [Instructions for the EnviroDIY LTEbee DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_ediylte.html) -- [The EnviroDIY LTE DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedHardware/DRWI_CitizenScience/DRWI_DigiLTE) +- [Instructions for the EnviroDIY LTE Bee DRWI Citizen Science example](https://envirodiy.github.io/ModularSensors/example_drwi_ediylte.html) +- [The EnviroDIY LTE DRWI Citizen Science example on GitHub](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE) diff --git a/examples/baro_rho_correction/ReadMe.md b/examples/baro_rho_correction/ReadMe.md index 118500af3..4674d360e 100644 --- a/examples/baro_rho_correction/ReadMe.md +++ b/examples/baro_rho_correction/ReadMe.md @@ -19,7 +19,7 @@ _______ - [To Use this Example](#to-use-this-example) - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - [Set the logger ID](#set-the-logger-id) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -35,7 +35,7 @@ _______ ### Prepare and set up PlatformIO -- Register a site and sensors at the Monitor My Watershed/EnviroDIY data portal () +- Register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org) - Create a new PlatformIO project - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/baro_rho_correction/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. @@ -47,16 +47,16 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID or serial number: ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable -- Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal () +- Go back to the web page for your site on [Monitor My Watershed](https://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! diff --git a/examples/baro_rho_correction/baro_rho_correction.ino b/examples/baro_rho_correction/baro_rho_correction.ino index 35fc65681..278d0df81 100644 --- a/examples/baro_rho_correction/baro_rho_correction.ino +++ b/examples/baro_rho_correction/baro_rho_correction.ino @@ -11,19 +11,6 @@ * @m_examplenavigation{example_baro_rho,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// NOTE: These only work with TinyGSM. -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger @@ -44,7 +31,7 @@ // The name of this program file const char* sketchName = "baro_rho_correction.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -56,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -91,7 +78,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status // Network connection information -const char* apn = "xxxxx"; // APN for GPRS connection +const char* apn = "YourAPN"; // APN for GPRS connection // Create the modem object Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn); @@ -109,7 +96,7 @@ Variable* modemSignalPct = new Modem_SignalPercent( // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata @@ -124,7 +111,7 @@ Variable* mcuBoardAvailableRAM = new ProcessorStats_FreeRam( &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); Variable* mcuBoardSampNo = new ProcessorStats_SampleNumber( &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -224,8 +211,9 @@ float calculateWaterPressure(void) { float baroPressureFromBME280 = bme280Press->getValue(); float waterPressure = totalPressureFromMS5803 - (baroPressureFromBME280) * 0.01; - if (totalPressureFromMS5803 == -9999 || baroPressureFromBME280 == -9999) { - waterPressure = -9999; + if (totalPressureFromMS5803 == MS_INVALID_VALUE || + baroPressureFromBME280 == MS_INVALID_VALUE) { + waterPressure = MS_INVALID_VALUE; } // Serial.print(F("Water pressure is ")); // for debugging // Serial.println(waterPressure); // for debugging @@ -252,8 +240,13 @@ Variable* calcWaterPress = new Variable( // For this, we're using the conversion between mbar and mm pure water at 4°C // This calculation gives a final result in mm of water float calculateWaterDepthRaw(void) { - float waterDepth = calculateWaterPressure() * 10.1972; - if (calculateWaterPressure() == -9999) waterDepth = -9999; + float pressure = calculateWaterPressure(); + float waterDepth; + if (pressure == MS_INVALID_VALUE) { + waterDepth = MS_INVALID_VALUE; + } else { + waterDepth = pressure * 10.1972; + } // Serial.print(F("'Raw' water depth is ")); // for debugging // Serial.println(waterDepth); // for debugging return waterDepth; @@ -295,8 +288,9 @@ float calculateWaterDepthTempCorrected(void) { // from P = rho * g * h float rhoDepth = 1000 * waterPressurePa / (waterDensity * gravitationalConstant); - if (calculateWaterPressure() == -9999 || waterTemperatureC == -9999) { - rhoDepth = -9999; + if (calculateWaterPressure() == MS_INVALID_VALUE || + waterTemperatureC == MS_INVALID_VALUE) { + rhoDepth = MS_INVALID_VALUE; } // Serial.print(F("Temperature corrected water depth is ")); // for // debugging Serial.println(rhoDepth); // for debugging @@ -352,18 +346,18 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal +// A Publisher to Monitor My Watershed // Device registration and sampling feature information can be obtained after -// registration at https://monitormywatershed.org or https://data.envirodiy.org +// registration at https://monitormywatershed.org const char* registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token const char* samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -387,7 +381,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Uses the processor sensor to read the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ @@ -496,3 +490,5 @@ void loop() { } } /** End [loop] */ + +// cspell: words baro diff --git a/examples/baro_rho_correction/platformio.ini b/examples/baro_rho_correction/platformio.ini index 92bde228c..3478bd920 100644 --- a/examples/baro_rho_correction/platformio.ini +++ b/examples/baro_rho_correction/platformio.ini @@ -17,7 +17,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -30,9 +30,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/data_saving/ReadMe.md b/examples/data_saving/ReadMe.md deleted file mode 100644 index 43dae2ebc..000000000 --- a/examples/data_saving/ReadMe.md +++ /dev/null @@ -1,82 +0,0 @@ -# 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. - -_______ - - - - - - - -- [Minimizing Cellular Data Use](#minimizing-cellular-data-use) - - [Unique Features of the Data Saving Example](#unique-features-of-the-data-saving-example) - - [To Use this Example](#to-use-this-example) - - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - - [Set the logger ID](#set-the-logger-id) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) - - [Upload!](#upload) - - - -_______ - -## 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 () -- Create a new PlatformIO project -- Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/data_saving/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](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/data_saving/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: - -```cpp -// Logger ID, also becomes the prefix for the name of the data file on SD card -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 () -- 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! - -_______ - - - - - - - - diff --git a/examples/data_saving/data_saving.ino b/examples/data_saving/data_saving.ino deleted file mode 100644 index 40213be69..000000000 --- a/examples/data_saving/data_saving.ino +++ /dev/null @@ -1,618 +0,0 @@ -/** ========================================================================= - * @example{lineno} data_saving.ino - * @copyright Stroud Water Research Center - * @license This example is published under the BSD-3 license. - * @author Sara Geleskie Damiano - * - * @brief Example publishing only a portion of the logged variables. - * - * See [the walkthrough page](@ref example_data_saving) for detailed - * instructions. - * - * @m_examplenavigation{example_data_saving,} - * ======================================================================= */ - -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - - -// ========================================================================== -// Include the libraries required for any data logger -// ========================================================================== -/** Start [includes] */ -// The Arduino library is needed for every Arduino program. -#include - -// Include the main header for ModularSensors -#include -/** End [includes] */ - - -// ========================================================================== -// Creating Additional Serial Ports -// ========================================================================== -/** Start [serial_ports] */ -// The modem and a number of sensors communicate over UART/TTL - often called -// "serial". "Hardware" serial ports (automatically controlled by the MCU) are -// generally the most accurate and should be configured and used for as many -// peripherals as possible. In some cases (ie, modbus communication) many -// sensors can share the same serial port. - -// For AVR boards -#if !defined(ARDUINO_ARCH_SAMD) && !defined(ATMEGA2560) -// Unfortunately, most AVR boards have only one or two hardware serial ports, -// so we'll set up three types of extra software serial ports to use - -// AltSoftSerial by Paul Stoffregen -// (https://github.com/PaulStoffregen/AltSoftSerial) is the most accurate -// software serial port for AVR boards. AltSoftSerial can only be used on one -// set of pins on each board so only one AltSoftSerial port can be used. Not all -// AVR boards are supported by AltSoftSerial. -#include -AltSoftSerial altSoftSerial; -#endif // End software serial for avr boards - -#if defined(ARDUINO_SAMD_FEATHER_M0) -#include // Needed for SAMD pinPeripheral() function - -// Set up a 'new' UART using SERCOM1 -// The Rx will be on digital pin 11, which is SERCOM1's Pad #0 -// The Tx will be on digital pin 10, which is SERCOM1's Pad #2 -// NOTE: SERCOM1 is undefined on a "standard" Arduino Zero and many clones, -// but not all! Please check the variant.cpp file for you individual -// board! -Uart Serial2(&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2); -// Hand over the interrupts to the sercom port -void SERCOM1_Handler() { - Serial2.IrqHandler(); -} -#define ENABLE_SERIAL2 - -#endif // End hardware serial on SAMD21 boards -/** End [serial_ports] */ - - -// ========================================================================== -// Data Logging Options -// ========================================================================== -/** Start [logging_options] */ -// The name of this program file -const char* sketchName = "data_saving.ino"; -// Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; -// How frequently (in minutes) to log data -const int8_t loggingInterval = 15; -// Your logger's timezone. -const int8_t timeZone = -5; // 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 = 31; // MCU interrupt/alarm pin to wake from sleep -// Mayfly 0.x, 1.x D31 = A7 -// 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] */ - - -// ========================================================================== -// Wifi/Cellular Modem Options -// ========================================================================== -/** Start [sodaq_2g_bee_r6] */ -// For the Sodaq 2GBee R6 and R7 based on the SIMCom SIM800 -// NOTE: The Sodaq GPRSBee doesn't expose the SIM800's reset pin -#include -// Create a reference to the serial port for the modem -HardwareSerial& modemSerial = Serial1; // Use hardware serial if possible -const int32_t modemBaud = 9600; // SIM800 does auto-bauding by default - -// Modem Pins - Describe the physical pin connection of your modem to your board -// NOTE: Use -1 for pins that do not apply -const int8_t modemVccPin = 23; // MCU pin controlling modem power -const int8_t modemStatusPin = 19; // MCU pin used to read modem status -const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem - // status (-1 if unconnected) - -// Network connection information -const char* apn = "xxxxx"; // The APN for the gprs connection - -Sodaq2GBeeR6 modem2GB(&modemSerial, modemVccPin, modemStatusPin, apn); -// Create an extra reference to the modem by a generic name -Sodaq2GBeeR6 modem = modem2GB; - -// Create RSSI and signal strength variable pointers for the modem -Variable* modemRSSI = new Modem_RSSI(&modem, - "12345678-abcd-1234-ef00-1234567890ab"); -Variable* modemSignalPct = - new Modem_SignalPercent(&modem, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [sodaq_2g_bee_r6] */ - - -// ========================================================================== -// Using the Processor as a Sensor -// ========================================================================== -/** Start [processor_sensor] */ -#include - -// Create the main processor chip "sensor" - for general metadata -const char* mcuBoardVersion = "v1.1"; -ProcessorStats mcuBoard(mcuBoardVersion); - -// Create sample number, battery voltage, and free RAM variable pointers for the -// processor -Variable* mcuBoardBatt = new ProcessorStats_Battery( - &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* mcuBoardAvailableRAM = new ProcessorStats_FreeRam( - &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* mcuBoardSampNo = new ProcessorStats_SampleNumber( - &mcuBoard, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [processor_sensor] */ - - -// ========================================================================== -// Maxim DS3231 RTC (Real Time Clock) -// ========================================================================== -/** Start [ds3231] */ -#include - -// Create a DS3231 sensor object -MaximDS3231 ds3231(1); - -// Create a temperature variable pointer for the DS3231 -Variable* ds3231Temp = - new MaximDS3231_Temp(&ds3231, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [ds3231] */ - - -// ========================================================================== -// Settings shared between Modbus sensors -// ========================================================================== -/** Start [modbus_shared] */ -// Create a reference to the serial port for modbus -#if defined(ENABLE_SERIAL2) || defined(ENVIRODIY_STONEFLY_M4) || \ - defined(ATMEGA2560) || defined(ARDUINO_AVR_MEGA2560) -HardwareSerial& modbusSerial = Serial2; // Use hardware serial if possible -#else -AltSoftSerial& modbusSerial = altSoftSerial; // For software serial -#endif - -// Define some pins that will be shared by all modbus sensors -const int8_t rs485AdapterPower = - sensorPowerPin; // RS485 adapter power pin (-1 if unconnected) -const int8_t modbusSensorPower = A3; // Sensor power pin -const int8_t rs485EnablePin = -1; // Adapter RE/DE pin (-1 if not applicable) -/** End [modbus_shared] */ - - -// ========================================================================== -// Yosemitech Y504 Dissolved Oxygen Sensor -// ========================================================================== -/** Start [Y504] */ -#include - -byte y504ModbusAddress = 0x04; // The modbus address of the Y504 -const uint8_t y504NumberReadings = 5; -// The manufacturer recommends averaging 10 readings, but we take 5 to minimize -// power consumption - -// Create a Yosemitech Y504 dissolved oxygen sensor object -YosemitechY504 y504(y504ModbusAddress, modbusSerial, rs485AdapterPower, - modbusSensorPower, rs485EnablePin, y504NumberReadings); - -// Create the dissolved oxygen percent, dissolved oxygen concentration, and -// temperature variable pointers for the Y504 -Variable* y504DOpct = - new YosemitechY504_DOpct(&y504, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* y504DOmgL = - new YosemitechY504_DOmgL(&y504, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* y504Temp = - new YosemitechY504_Temp(&y504, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [Y504] */ - - -// ========================================================================== -// Yosemitech Y511 Turbidity Sensor with Wiper -// ========================================================================== -/** Start [Y511] */ -#include - -byte y511ModbusAddress = 0x1A; // The modbus address of the Y511 -const uint8_t y511NumberReadings = 5; -// The manufacturer recommends averaging 10 readings, but we take 5 to minimize -// power consumption - -// Create a Y511-A Turbidity sensor object -YosemitechY511 y511(y511ModbusAddress, modbusSerial, rs485AdapterPower, - modbusSensorPower, rs485EnablePin, y511NumberReadings); - -// Create turbidity and temperature variable pointers for the Y511 -Variable* y511Turb = - new YosemitechY511_Turbidity(&y511, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* y511Temp = - new YosemitechY511_Temp(&y511, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [Y511] */ - - -// ========================================================================== -// Yosemitech Y514 Chlorophyll Sensor -// ========================================================================== -/** Start [Y514] */ -#include - -byte y514ModbusAddress = 0x14; // The modbus address of the Y514 -const uint8_t y514NumberReadings = 5; -// The manufacturer recommends averaging 10 readings, but we take 5 to -// minimize power consumption - -// Create a Y514 chlorophyll sensor object -YosemitechY514 y514(y514ModbusAddress, modbusSerial, rs485AdapterPower, - modbusSensorPower, rs485EnablePin, y514NumberReadings); - -// Create chlorophyll concentration and temperature variable pointers for the -// Y514 -Variable* y514Chloro = new YosemitechY514_Chlorophyll( - &y514, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* y514Temp = - new YosemitechY514_Temp(&y514, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [Y514] */ - - -// ========================================================================== -// Yosemitech Y520 Conductivity Sensor -// ========================================================================== -/** Start [Y520] */ -#include - -byte y520ModbusAddress = 0x20; // The modbus address of the Y520 -const uint8_t y520NumberReadings = 5; -// The manufacturer recommends averaging 10 readings, but we take 5 to minimize -// power consumption - -// Create a Y520 conductivity sensor object -YosemitechY520 y520(y520ModbusAddress, modbusSerial, rs485AdapterPower, - modbusSensorPower, rs485EnablePin, y520NumberReadings); - -// Create specific conductance and temperature variable pointers for the Y520 -Variable* y520Cond = - new YosemitechY520_Cond(&y520, "12345678-abcd-1234-ef00-1234567890ab"); -Variable* y520Temp = - new YosemitechY520_Temp(&y520, "12345678-abcd-1234-ef00-1234567890ab"); -/** End [Y520] */ - - -// ========================================================================== -// Creating the Variable Array[s] and Filling with Variable Objects -// ========================================================================== -/** Start [variable_arrays] */ -// FORM2: Fill array with already created and named variable pointers -// We put ALL of the variable pointers into the first array -Variable* variableList_complete[] = { - mcuBoardSampNo, mcuBoardBatt, mcuBoardAvailableRAM, - ds3231Temp, y504DOpct, y504DOmgL, - y504Temp, y511Turb, y511Temp, - y514Chloro, y514Temp, y520Cond, - y520Temp, modemRSSI, modemSignalPct}; -// Count up the number of pointers in the array -int variableCount_complete = sizeof(variableList_complete) / - sizeof(variableList_complete[0]); -// Create the VariableArray object -VariableArray arrayComplete(variableCount_complete, variableList_complete); - - -// Put only the particularly interesting variables into a second array -// NOTE: We can the same variables into multiple arrays -Variable* variableList_toGo[] = {y504DOmgL, y504Temp, y511Turb, - y514Chloro, y520Cond, modemRSSI}; -// Count up the number of pointers in the array -int variableCount_toGo = sizeof(variableList_toGo) / - sizeof(variableList_toGo[0]); -// Create the VariableArray object -VariableArray arrayToGo(variableCount_toGo, variableList_toGo); -/** End [variable_arrays] */ - - -// ========================================================================== -// The Logger Object[s] -// ========================================================================== -/** Start [loggers] */ -// Create one new logger instance for the complete array -Logger loggerAllVars(LoggerID, loggingInterval, &arrayComplete); - -// Create "another" logger for the variables to go out over the internet -Logger loggerToGo(LoggerID, loggingInterval, &arrayToGo); -/** End [loggers] */ - - -// ========================================================================== -// Creating Data Publisher[s] -// ========================================================================== -/** Start [publishers] */ -// Create a publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal -// Device registration and sampling feature information can be obtained after -// registration at https://monitormywatershed.org or https://data.envirodiy.org -const char* registrationToken = - "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token -const char* samplingFeature = - "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID - -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -// This is only attached to the logger with the shorter variable array -#include -EnviroDIYPublisher EnviroDIYPost(loggerToGo, registrationToken, - samplingFeature); -/** End [publishers] */ - - -// ========================================================================== -// 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); -} - -// Reads the battery voltage -// NOTE: This will actually return the battery level from the previous update! -float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); - return mcuBoard.sensorValues[0]; -} -/** End [working_functions] */ - - -// ========================================================================== -// Arduino Setup Function -// ========================================================================== -/** Start [setup] */ -void setup() { -// Wait for USB connection to be established by PC -// NOTE: Only use this when debugging - if not connected to a PC, this -// could prevent the script from starting -#if defined(SERIAL_PORT_USBVIRTUAL) - while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000)) { - // wait - } -#endif - - // 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); - - // Start the serial connection with the modem - modemSerial.begin(modemBaud); - - // Start the stream for the modbus sensors; all currently supported modbus - // sensors use 9600 baud - modbusSerial.begin(9600); - -// Assign pins SERCOM functionality for SAMD boards -// NOTE: This must happen *after* the various serial.begin statements -#if defined(ARDUINO_SAMD_FEATHER_M0) - // Serial2 - pinPeripheral(10, PIO_SERCOM); // Serial2 Tx/Dout = SERCOM1 Pad #2 - pinPeripheral(11, PIO_SERCOM); // Serial2 Rx/Din = SERCOM1 Pad #0 -#endif - // 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) - loggerClock::setRTCOffset(0); - - // Attach the same modem to both loggers - // It is only needed for the logger that will be sending out data, but - // attaching it to both allows either logger to control NIST synchronization - loggerAllVars.attachModem(modem); - loggerToGo.attachModem(modem); - modem.setModemLED(modemLEDPin); - loggerAllVars.setLoggerPins(wakePin, sdCardSSPin, sdCardPwrPin, buttonPin, - greenLED); - - // Set up the connection information with EnviroDIY for both loggers - // Doing this for both loggers ensures that the header of the csv will have - // the tokens in it - loggerAllVars.setSamplingFeatureUUID(samplingFeature); - loggerToGo.setSamplingFeatureUUID(samplingFeature); - - // Note: Please change these battery voltages to match your battery - - // Set up the sensors, except at lowest battery level - // Like with the logger, because the variables are duplicated in the arrays, - // we only need to do this for the complete array. - if (getBatteryVoltage() > 3.4) { - Serial.println(F("Setting up sensors...")); - arrayComplete.setupSensors(); - } - - // Sync the clock if it isn't valid or we have battery to spare - if (getBatteryVoltage() > 3.55 || !loggerClock::isRTCSane()) { - // Synchronize the RTC with NIST - // This will also set up the modem - loggerAllVars.syncRTC(); - } - - // 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 - // Writing to the SD card can be power intensive, so if we're skipping - // the sensor setup we'll skip this too. - if (getBatteryVoltage() > 3.4) { - loggerAllVars.turnOnSDcard( - true); // true = wait for card to settle after power up - loggerAllVars.createLogFile(true); // true = write a new header - loggerAllVars.turnOffSDcard( - true); // true = wait for internal housekeeping after write - } - - // Call the processor sleep - Serial.println(F("Putting processor to sleep")); - loggerAllVars.systemSleep(); -} -/** End [setup] */ - - -// ========================================================================== -// Arduino Loop Function -// ========================================================================== -/** Start [loop] */ -// Use this long loop when you want to do something special -// Because of the way alarms work on the RTC, it will wake the processor and -// start the loop every minute exactly on the minute. -// The processor may also be woken up by another interrupt or level change on a -// pin - from a button or some other input. -// The "if" statements in the loop determine what will happen - whether the -// sensors update, testing mode starts, or it goes back to sleep. -void loop() { - // Reset the watchdog - extendedWatchDog::resetWatchDog(); - - // Assuming we were woken up by the clock, check if the current time is an - // even interval of the logging interval - // We're only doing anything at all if the battery is above 3.4V - if (loggerAllVars.checkInterval() && getBatteryVoltage() > 3.4) { - // Flag to notify that we're in already awake and logging a point - Logger::isLoggingNow = true; - extendedWatchDog::resetWatchDog(); - - // Print a line to show new reading - Serial.println(F("------------------------------------------")); - // Turn on the LED to show we're taking a reading - loggerAllVars.alertOn(); - // Power up the SD Card, but skip any waits after power up - loggerAllVars.turnOnSDcard(false); - extendedWatchDog::resetWatchDog(); - - // Start the stream for the modbus sensors - // Because RS485 adapters tend to "steal" current from the data pins - // we will explicitly start and end the serial connection in the loop. - modbusSerial.begin(9600); - - // Do a complete update on the "full" array. - // This this includes powering all of the sensors, getting updated - // values, and turing them back off. - // NOTE: The wake function for each sensor should force sensor setup - // to run if the sensor was not previously set up. - arrayComplete.completeUpdate(); - extendedWatchDog::resetWatchDog(); - - // End the stream for the modbus sensors - // Because RS485 adapters tend to "steal" current from the data pins - // we will explicitly start and end the serial connection in the loop. - modbusSerial.end(); - -#if defined(AltSoftSerial_h) - // Explicitly set the pin modes for the AltSoftSerial pins to make sure - // they're low - pinMode(5, OUTPUT); // On a Mayfly, pin D5 is the AltSoftSerial Tx pin - pinMode(6, OUTPUT); // On a Mayfly, pin D6 is the AltSoftSerial Rx pin - digitalWrite(5, LOW); - digitalWrite(6, LOW); -#endif - -#if defined(ARDUINO_SAMD_FEATHER_M0) - digitalWrite(10, LOW); - digitalWrite(11, LOW); -#endif - - // Create a csv data record and save it to the log file - loggerAllVars.logToSD(); - extendedWatchDog::resetWatchDog(); - - // Connect to the network - // Again, we're only doing this if the battery is doing well - if (getBatteryVoltage() > 3.55) { - if (modem.modemWake()) { - extendedWatchDog::resetWatchDog(); - if (modem.connectInternet()) { - extendedWatchDog::resetWatchDog(); - // Publish data to remotes - loggerToGo.publishDataToRemotes(); - modem.updateModemMetadata(); - - extendedWatchDog::resetWatchDog(); - // Sync the clock at noon - // NOTE: All loggers have the same clock, pick one - if (Logger::markedLocalUnixTime != 0 && - Logger::markedLocalUnixTime % 86400 == 43200) { - Serial.println(F("Running a daily clock sync...")); - loggerClock::setRTClock(modem.getNISTTime(), 0, - epochStart::unix_epoch); - } - - // Disconnect from the network - extendedWatchDog::resetWatchDog(); - modem.disconnectInternet(); - } - } - // Turn the modem off - extendedWatchDog::resetWatchDog(); - modem.modemSleepPowerDown(); - } - - // Cut power from the SD card - without additional housekeeping wait - loggerAllVars.turnOffSDcard(false); - extendedWatchDog::resetWatchDog(); - // Turn off the LED - loggerAllVars.alertOff(); - // Print a line to show reading ended - Serial.println(F("------------------------------------------\n")); - - // Unset flag - Logger::isLoggingNow = false; - } - - // Check if it was instead the testing interrupt that woke us up - // Want to enter the testing mode for the "complete" logger so we can see - // the data from _ALL_ sensors - // NOTE: The testingISR attached to the button at the end of the "setup()" - // function turns on the startTesting flag. So we know if that flag is set - // then we want to run the testing mode function. - if (Logger::startTesting) loggerAllVars.benchTestingMode(); - - // Call the processor sleep - // Only need to do this for one of the loggers - loggerAllVars.systemSleep(); -} - -/** End [loop] */ diff --git a/examples/data_saving/platformio.ini b/examples/data_saving/platformio.ini deleted file mode 100644 index dd636635e..000000000 --- a/examples/data_saving/platformio.ini +++ /dev/null @@ -1,40 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; http://docs.platformio.org/page/projectconf.html - -[platformio] -description = ModularSensors example using two "loggers" to save cellular data - -[env:mayfly] -monitor_speed = 115200 -board = mayfly -platform = atmelavr -framework = arduino -lib_ldf_mode = deep+ -lib_ignore = - RTCZero - Adafruit NeoPixel - Adafruit GFX Library - Adafruit SSD1306 - Adafruit ADXL343 - Adafruit STMPE610 - Adafruit TouchScreen - Adafruit ILI9341 -build_flags = - -DSDI12_EXTERNAL_PCINT - -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 -lib_deps = - envirodiy/EnviroDIY_ModularSensors -; ^^ Use this when working from an official release of the library -; https://github.com/EnviroDIY/ModularSensors.git#develop -; ^^ Use this when if you want to pull from the develop branch - https://github.com/PaulStoffregen/AltSoftSerial.git diff --git a/examples/double_logger/ReadMe.md b/examples/double_logger/ReadMe.md index 3094b7151..11398bf73 100644 --- a/examples/double_logger/ReadMe.md +++ b/examples/double_logger/ReadMe.md @@ -46,11 +46,11 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID or serial number: ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Upload! diff --git a/examples/double_logger/double_logger.ino b/examples/double_logger/double_logger.ino index 7b4f8a8ce..2f645fc28 100644 --- a/examples/double_logger/double_logger.ino +++ b/examples/double_logger/double_logger.ino @@ -12,18 +12,6 @@ * @m_examplenavigation{example_double_log,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -43,7 +31,7 @@ // The name of this program file const char* sketchName = "double_logger.ino"; // Logger ID - we're only using one logger ID for both "loggers" -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // The TWO filenames for the different logging intervals const char* FileName5min = "Logger_5MinuteInterval.csv"; const char* FileName1min = "Logger_1MinuteInterval.csv"; @@ -56,7 +44,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -89,8 +77,8 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status (-1 if unconnected) // Network connection information -const char* wifiId = "xxxxx"; // WiFi access point, unnecessary for GPRS -const char* wifiPwd = "xxxxx"; // WiFi password, unnecessary for GPRS +const char* wifiId = "YourWiFiSSID"; // The WiFi access point +const char* wifiPwd = "YourWiFiPassword"; // The WiFi password DigiXBeeWifi modemXBWF(&modemSerial, modemVccPin, modemStatusPin, useCTSforStatus, modemResetPin, modemSleepRqPin, wifiId, @@ -103,13 +91,13 @@ DigiXBeeWifi modem = modemXBWF; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -311,27 +299,8 @@ void loop() { // Turn on the LED to show we're taking a reading digitalWrite(greenLED, HIGH); - // Send power to all of the sensors (do this directly on the - // VariableArray) - Serial.print(F("Powering sensors...\n")); - array1min.sensorsPowerUp(); - extendedWatchDog::resetWatchDog(); - // Wake up all of the sensors (do this directly on the VariableArray) - Serial.print(F("Waking sensors...\n")); - array1min.sensorsWake(); - extendedWatchDog::resetWatchDog(); - // Update the values from all attached sensors (do this directly on the - // VariableArray) - Serial.print(F("Updating sensor values...\n")); - array1min.updateAllSensors(); - extendedWatchDog::resetWatchDog(); - // Put sensors to sleep (do this directly on the VariableArray) - Serial.print(F("Putting sensors back to sleep...\n")); - array1min.sensorsSleep(); - extendedWatchDog::resetWatchDog(); - // Cut sensor power (do this directly on the VariableArray) - Serial.print(F("Cutting sensor power...\n")); - array1min.sensorsPowerDown(); + Serial.print(F("Running a complete sensor update...\n")); + array1min.completeUpdate(); extendedWatchDog::resetWatchDog(); // Stream the csv data to the SD card @@ -354,27 +323,9 @@ void loop() { // Turn on the LED to show we're taking a reading digitalWrite(redLED, HIGH); - // Send power to all of the sensors (do this directly on the - // VariableArray) - Serial.print(F("Powering sensors...\n")); - array5min.sensorsPowerUp(); - extendedWatchDog::resetWatchDog(); - // Wake up all of the sensors (do this directly on the VariableArray) - Serial.print(F("Waking sensors...\n")); - array5min.sensorsWake(); - extendedWatchDog::resetWatchDog(); - // Update the values from all attached sensors (do this directly on the - // VariableArray) - Serial.print(F("Updating sensor values...\n")); - array5min.updateAllSensors(); - extendedWatchDog::resetWatchDog(); - // Put sensors to sleep (do this directly on the VariableArray) - Serial.print(F("Putting sensors back to sleep...\n")); - array5min.sensorsSleep(); - extendedWatchDog::resetWatchDog(); - // Cut sensor power (do this directly on the VariableArray) - Serial.print(F("Cutting sensor power...\n")); - array5min.sensorsPowerDown(); + // Complete sensor update on this VariableArray + Serial.print(F("Running a complete sensor update...\n")); + array5min.completeUpdate(); extendedWatchDog::resetWatchDog(); // Stream the csv data to the SD card @@ -409,3 +360,5 @@ void loop() { logger1min.systemSleep(); } /** End [loop] */ + +// cspell: words modemXBWF diff --git a/examples/double_logger/platformio.ini b/examples/double_logger/platformio.ini index 8f4230c1d..88d796134 100644 --- a/examples/double_logger/platformio.ini +++ b/examples/double_logger/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/example_dependencies.json b/examples/example_dependencies.json index d3f5f117e..ead9d87ef 100644 --- a/examples/example_dependencies.json +++ b/examples/example_dependencies.json @@ -26,7 +26,7 @@ "name": "Adafruit NeoPixel", "owner": "adafruit", "url": "https://github.com/adafruit/Adafruit_NeoPixel", - "version": "~1.15.1", + "version": "~1.15.4", "note": "Adafruit NeoPixel, for boards with a built in NeoPixel", "authors": ["Adafruit"], "frameworks": "arduino" diff --git a/examples/examples.dox b/examples/examples.dox index f069abcd1..d0a62d610 100644 --- a/examples/examples.dox +++ b/examples/examples.dox @@ -47,8 +47,6 @@ @subpage example_double_log "DELETE THIS LINK" -@subpage example_data_saving "DELETE THIS LINK" - */ /** @@ -66,15 +64,17 @@ /** @page page_the_examples -@subpage examples_outdated "DELETE THIS LINK" +@subpage page_examples_outdated "DELETE THIS LINK" -@page examples_outdated Examples for Outdated Hardware +@page page_examples_outdated Examples for Outdated Hardware [Examples for Outdated Hardware](@ref examples_outdated) @subpage example_learn_envirodiy "DELETE THIS LINK" +*/ + /** -@page examples_outdated +@page page_examples_outdated @subpage page_examples_drwi "DELETE THIS LINK" diff --git a/examples/logging_to_MMW/ReadMe.md b/examples/logging_to_MMW/ReadMe.md index b4a9384b3..b69232eda 100644 --- a/examples/logging_to_MMW/ReadMe.md +++ b/examples/logging_to_MMW/ReadMe.md @@ -1,12 +1,13 @@ -# Sending Data to Monitor My Watershed/EnviroDIY +# Sending Data to Monitor My Watershed -This sketch reduces menu_a_la_carte.ino to provide an example of how to log to 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. +This sketch reduces menu_a_la_carte.ino to provide an example of how to log to from two sensors, the BME280 and DS18. +To complete the setup 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. +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 Monitor My Watershed. _______ @@ -16,12 +17,12 @@ _______ -- [Sending Data to Monitor My Watershed/EnviroDIY](#sending-data-to-monitor-my-watershedenvirodiy) +- [Sending Data to Monitor My Watershed](#sending-data-to-monitor-my-watershed) - [Unique Features of the Monitor My Watershed Example](#unique-features-of-the-monitor-my-watershed-example) - [To Use this Example](#to-use-this-example) - [Prepare and set up PlatformIO](#prepare-and-set-up-platformio) - [Set the logger ID](#set-the-logger-id) - - [Set the universally universal identifiers (UUID) for each variable](#set-the-universally-universal-identifiers-uuid-for-each-variable) + - [Set the universally unique identifiers (UUIDs) for each variable](#set-the-universally-unique-identifiers-uuids-for-each-variable) - [Upload!](#upload) @@ -30,14 +31,14 @@ _______ ## Unique Features of the Monitor My Watershed Example -- A single logger publishes data to the Monitor My Watershed data portal. +- A single logger publishes data to Monitor My Watershed. - 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 () +- Register a site and sensors on [Monitor My Watershed](https://monitormywatershed.org) - Create a new PlatformIO project - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/logging_to_MMW/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. @@ -49,16 +50,16 @@ _______ ### Set the logger ID -- Change the "XXXX" in this section of code to the loggerID assigned by Stroud: +- Change the text `YourLoggerID` in this section of code to your logger ID or serial number: ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` -### Set the universally universal identifiers (UUID) for each variable +### Set the universally unique identifiers (UUIDs) for each variable -- Go back to the web page for your site at the Monitor My Watershed/EnviroDIY data portal () +- Go back to the web page for your site on [Monitor My Watershed](https://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! diff --git a/examples/logging_to_MMW/logging_to_MMW.ino b/examples/logging_to_MMW/logging_to_MMW.ino index c68c610c6..dd1231149 100644 --- a/examples/logging_to_MMW/logging_to_MMW.ino +++ b/examples/logging_to_MMW/logging_to_MMW.ino @@ -11,18 +11,6 @@ * @m_examplenavigation{example_mmw,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -40,9 +28,9 @@ // ========================================================================== /** Start [logging_options] */ // The name of this program file -const char* sketchName = "logging_to MMW.ino"; +const char* sketchName = "logging_to_MMW.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -54,7 +42,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -90,7 +78,7 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem // status (-1 if unconnected) // Network connection information -const char* apn = "xxxxx"; // The APN for the gprs connection +const char* apn = "YourAPN"; // The APN for the gprs connection // NOTE: If possible, use the `STATUS/SLEEP_not` (XBee pin 13) for status, but // the `CTS` pin can also be used if necessary @@ -105,13 +93,13 @@ DigiXBeeCellularTransparent modem = modemXBCT; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -204,18 +192,18 @@ Logger dataLogger(LoggerID, loggingInterval, &varArray); // Creating Data Publisher[s] // ========================================================================== /** Start [publishers] */ -// A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal +// A Publisher to Monitor My Watershed // Device registration and sampling feature information can be obtained after -// registration at https://monitormywatershed.org or https://data.envirodiy.org +// registration at https://monitormywatershed.org const char* registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token const char* samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // Sampling feature UUID -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken, - samplingFeature); +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken, + samplingFeature); /** End [publishers] */ @@ -239,7 +227,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ @@ -250,18 +238,16 @@ float getBatteryVoltage() { // ========================================================================== /** Start [setup] */ void setup() { + // Start the primary serial connection + Serial.begin(serialBaud); + // Wait for USB connection to be established by PC -// NOTE: Only use this when debugging - if not connected to a PC, this -// could prevent the script from starting +// NOTE: Only use this when debugging - if not connected to a PC, this adds an +// unnecessary startup delay #if defined(SERIAL_PORT_USBVIRTUAL) - while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000)) { - // wait - } + while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000L)) { delay(10); } #endif - // 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); diff --git a/examples/logging_to_MMW/platformio.ini b/examples/logging_to_MMW/platformio.ini index 8c2996ae3..07f62b446 100644 --- a/examples/logging_to_MMW/platformio.ini +++ b/examples/logging_to_MMW/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel diff --git a/examples/logging_to_ThingSpeak/ReadMe.md b/examples/logging_to_ThingSpeak/ReadMe.md index 92d206b1c..b11398a22 100644 --- a/examples/logging_to_ThingSpeak/ReadMe.md +++ b/examples/logging_to_ThingSpeak/ReadMe.md @@ -54,20 +54,20 @@ _______ - Modify logging_to_ThingSpeak.ino to have the modem, sensor, and variable objects that you are interested in. - This example is written for an _ESP8266 (wifi)_ modem. -Change this to whatever modem you are using. -Paste-able chunks of code for each modem are available in the individual sensor documentation or in the menu a la carte example. + Change this to whatever modem you are using. + Paste-able chunks of code for each modem are available in the individual sensor documentation or in the menu a la carte example. - Don't forget to put in your wifi username/password or cellular APN! - This example is written for a Campbell OBS3+ and a Meter Hydros 21. -Remove those sensors if you are not using them and add code for all of your sensors. -See the pages for the individual sensors in the [documentation](https://envirodiy.github.io/ModularSensors/index.html) for code snippets/examples. + Remove those sensors if you are not using them and add code for all of your sensors. + See the pages for the individual sensors in the [documentation](https://envirodiy.github.io/ModularSensors/index.html) for code snippets/examples. - Remember, no more than **8** variables/fields can be sent to a single ThingSpeak channel. -If you want to send data to multiple channels, you must create individual logger objects with unique publishers attached for each channel you want to send to. + If you want to send data to multiple channels, you must create individual logger objects with unique publishers attached for each channel you want to send to. - **Make sure the pin numbers and serial ports selected in your code match with how things are physically attached to your board!** - Order the variables in your variable array in the same order as your fields are on ThingSpeak. - This order is **crucial**. -The results from the variables in the VariableArray will be sent to ThingSpeak in the order they are in the array; that is, the first variable in the array will be sent as Field1, the second as Field2, etc. - - Any UUID's or custom variable codes are ignored for ThingSpeak. -They will only appear in the header of your file on the SD card. + The results from the variables in the VariableArray will be sent to ThingSpeak in the order they are in the array; that is, the first variable in the array will be sent as Field1, the second as Field2, etc. + - Any UUIDs or custom variable codes are ignored for ThingSpeak. + They will only appear in the header of your file on the SD card. - Find this information for your ThingSpeak account and channel and put it into logging_to_ThingSpeak.ino: ```cpp diff --git a/examples/logging_to_ThingSpeak/logging_to_ThingSpeak.ino b/examples/logging_to_ThingSpeak/logging_to_ThingSpeak.ino index 709050edf..4ba70855b 100644 --- a/examples/logging_to_ThingSpeak/logging_to_ThingSpeak.ino +++ b/examples/logging_to_ThingSpeak/logging_to_ThingSpeak.ino @@ -12,18 +12,6 @@ * @m_examplenavigation{example_thingspeak,} * ======================================================================= */ -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger // ========================================================================== @@ -43,7 +31,7 @@ // The name of this program file const char* sketchName = "logging_to_ThingSpeak.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -55,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -88,8 +76,8 @@ const int8_t modemLEDPin = redLED; // MCU pin connected an LED to show modem status // Network connection information -const char* wifiId = "xxxxx"; // The WiFi access point -const char* wifiPwd = "xxxxx"; // The password for connecting to WiFi +const char* wifiId = "YourWiFiSSID"; // The WiFi access point +const char* wifiPwd = "YourWiFiPassword"; // The WiFi password // Create the loggerModem object EspressifESP8266 modemESP(&modemSerial, modemVccPin, modemResetPin, wifiId, @@ -102,13 +90,13 @@ EspressifESP8266 modem = modemESP; // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== @@ -130,7 +118,6 @@ MaximDS3231 ds3231(1); const int8_t OBS3Power = sensorPowerPin; // Power pin (-1 if unconnected) const uint8_t OBS3NumberReadings = 10; -const uint8_t ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC // Campbell OBS 3+ *Low* Range Calibration in Volts const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output const float OBSLow_A = 0.000E+00; // "A" value (X^2) [*low* range] @@ -139,7 +126,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - ADSi2c_addr, OBS3NumberReadings); + OBS3NumberReadings); // Campbell OBS 3+ *High* Range Calibration in Volts @@ -150,7 +137,7 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NumberReadings); /** End [obs3] */ @@ -249,7 +236,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Reads the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[0] == -9999) mcuBoard.update(); + if (mcuBoard.sensorValues[0] == MS_INVALID_VALUE) mcuBoard.update(); return mcuBoard.sensorValues[0]; } /** End [working_functions] */ @@ -362,3 +349,5 @@ void loop() { } } /** End [loop] */ + +// cSpell: words TurbHigh TurbLow setRESTAPIKey diff --git a/examples/logging_to_ThingSpeak/platformio.ini b/examples/logging_to_ThingSpeak/platformio.ini index 28ada0e2a..1435c8543 100644 --- a/examples/logging_to_ThingSpeak/platformio.ini +++ b/examples/logging_to_ThingSpeak/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/menu_a_la_carte/ReadMe.md b/examples/menu_a_la_carte/ReadMe.md index 9807faa6d..e1131571c 100644 --- a/examples/menu_a_la_carte/ReadMe.md +++ b/examples/menu_a_la_carte/ReadMe.md @@ -27,9 +27,7 @@ ___ - [Example showing all possible functionality](#example-showing-all-possible-functionality) - [Walking Through the Code](#walking-through-the-code) - - [Defines and Includes](#defines-and-includes) - - [Defines for the Arduino IDE](#defines-for-the-arduino-ide) - - [Library Includes](#library-includes) + - [Library Includes](#library-includes) - [Logger Settings](#logger-settings) - [Creating Extra Serial Ports](#creating-extra-serial-ports) - [AVR Boards](#avr-boards) @@ -79,6 +77,7 @@ ___ - [Decagon CTD-10 Conductivity, Temperature, and Depth Sensor](#decagon-ctd-10-conductivity-temperature-and-depth-sensor) - [Decagon ES2 Conductivity and Temperature Sensor](#decagon-es2-conductivity-and-temperature-sensor) - [Everlight ALS-PT19 Ambient Light Sensor](#everlight-als-pt19-ambient-light-sensor) + - [External Voltage via Processor ADC](#external-voltage-via-processor-adc) - [External Voltage via TI ADS1x15](#external-voltage-via-ti-ads1x15) - [Freescale Semiconductor MPL115A2 Miniature I2C Digital Barometer](#freescale-semiconductor-mpl115a2-miniature-i2c-digital-barometer) - [Geolux HydroCam Camera](#geolux-hydrocam-camera) @@ -91,6 +90,7 @@ ___ - [Maxbotix HRXL Ultrasonic Range Finder](#maxbotix-hrxl-ultrasonic-range-finder) - [Maxim DS18 One Wire Temperature Sensor](#maxim-ds18-one-wire-temperature-sensor) - [Measurement Specialties MS5803-14BA Pressure Sensor](#measurement-specialties-ms5803-14ba-pressure-sensor) + - [TE Connectivity MS5837 Pressure Sensor](#te-connectivity-ms5837-pressure-sensor) - [Meter SDI-12 Sensors](#meter-sdi-12-sensors) - [Meter ECH2O Soil Moisture Sensor](#meter-ech2o-soil-moisture-sensor) - [Meter Hydros 21 Conductivity, Temperature, and Depth Sensor](#meter-hydros-21-conductivity-temperature-and-depth-sensor) @@ -159,31 +159,7 @@ ___ -## Defines and Includes - -### Defines for the Arduino IDE - -The top few lines of the examples set defines of buffer sizes and yields needed for the Arduino IDE. -That IDE read any defines within the top few lines and applies them as build flags for the processor. -This is *not* standard behavior for C++ (which is what Arduino code really is) - this is a unique aspect of the Arduino IDE. - - - -If you are using PlatformIO, you should instead set these as global build flags in your platformio.ini. -This is standard behavior for C++. - -```ini -build_flags = - -D SDI12_EXTERNAL_PCINT - -D NEOSWSERIAL_EXTERNAL_PCINT - -D MQTT_MAX_PACKET_SIZE=240 - -D TINY_GSM_RX_BUFFER=64 - -D TINY_GSM_YIELD_MS=2 -``` - -___ - -### Library Includes +## Library Includes Next, include the libraries needed for every program using ModularSensors. @@ -208,7 +184,7 @@ ___ #### AVR Boards -Most Arduino AVR style boards have very few (ie, one, or none) dedicated serial ports *available* after counting out the programming serial port. +Most Arduino AVR style boards have very few (i.e., one or none) dedicated serial ports *available* after counting out the programming serial port. So to connect anything else, we need to try to emulate the processor serial functionality with a software library. This example shows three possible libraries that can be used to emulate a serial port on an AVR board. @@ -537,7 +513,7 @@ To create a Sodaq2GBeeR6 object we need to know - the serial object name, - the MCU pin controlling modem power, (**NOTE:** On the GPRSBee R6 and R7 the pin labeled as ON/OFF in Sodaq's diagrams is tied to *both* the SIM800 power supply and the (inverted) SIM800 `PWRKEY`. -You should enter this pin as the power pin.) + You should enter this pin as the power pin.) - and the SIM card's cellular access point name (APN). Pins that do not apply should be set as -1. @@ -604,8 +580,8 @@ ___ Set options and create the objects for using the processor as a sensor to report battery level, processor free ram, and sample number. The processor can return the number of "samples" it has taken, the amount of RAM it has available and, for some boards, the battery voltage (EnviroDIY Mayfly, Sodaq Mbili, Ndogo, Autonomo, and One, Adafruit Feathers). -The version of the board is required as input (ie, for a EnviroDIY Mayfly: "v0.3" or "v0.4" or "v0.5"). -Use a blank value (ie, "") for un-versioned boards. +The version of the board is required as input (i.e., for an EnviroDIY Mayfly: "v0.3" or "v0.4" or "v0.5"). +Use a blank value (i.e., "") for un-versioned boards. Please note that while you can opt to average more than one sample, it really makes no sense to do so for the processor. The number of "samples" taken will increase by one for each time another processor "measurement" is taken so averaging multiple measurements from the processor will result in the number of samples increasing by more than one with each loop. @@ -699,6 +675,7 @@ The default I2C addresses for the circuits are: - ORP (redox): 0x62 (98) - pH: 0x63 (99) - RTD (temperature): 0x66 (102) + All of the circuits can be re-addressed to any other 8 bit number if desired. To use multiple circuits of the same type, re-address them. @@ -756,7 +733,7 @@ ___ Here is the code for the Bosch BME280 environmental sensor. The only input needed is the Arduino pin controlling power on/off; the i2cAddressHex is optional as is the number of readings to average. -Keep in mind that the possible I2C addresses of the BME280 match those of the MS5803; when using those sensors together, make sure they are set to opposite addresses. +Keep in mind that the I2C address (0x76) of the BME280 matches that of some configurations of the MS5803, MS5837, BMP388, and BMP390 sensors; when using those sensors together, make sure they are set to different addresses. @see @ref sensor_bme280 @@ -766,6 +743,8 @@ ___ ### Bosch BMP388 and BMP398 Pressure Sensors +Keep in mind that the I2C address (0x76) of the BMP388/BMP390 sensors matches that of some configurations of the MS5803, MS5837, and BME280 sensors; when using those sensors together, make sure they are set to different addresses. + @see @ref sensor_bmp3xx @@ -832,6 +811,18 @@ ___ ___ +### External Voltage via Processor ADC + +The Arduino pin controlling power on/off, the analog data pin on the processor, and the multiplier for a voltage divider are required for the sensor constructor. +If your processor operating voltage is not defined in KnownProcessors.h, you must input it as the fourth argument; otherwise you can enter the value `OPERATING_VOLTAGE` to use the voltage defined in KnownProcessors.h. +The number of measurements to average, if more than one is desired, goes as the fifth argument. + +@see @ref sensor_processor_analog + + + +___ + ### External Voltage via TI ADS1x15 The Arduino pin controlling power on/off and the analog data channel *on the TI ADS1115* are required for the sensor constructor. @@ -899,7 +890,7 @@ ___ The next two sections are for Keller RS485/Modbus water level sensors. The sensor class constructors for each are nearly identical, except for the class name. -The sensor constructors require as input: the sensor modbus address, a stream instance for data (ie, `Serial`), and one or two power pins. +The sensor constructors require as input: the sensor modbus address, a stream instance for data (i.e., `Serial`), and one or two power pins. The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) Please see the section "[Notes on Arduino Streams and Software Serial](https://envirodiy.github.io/ModularSensors/page_arduino_streams.html)" for more information about what streams can be used along with this library. In tests on these sensors, SoftwareSerial_ExtInts *did not work* to communicate with these sensors, because it isn't stable enough. @@ -933,7 +924,7 @@ ___ ### Maxbotix HRXL Ultrasonic Range Finder -The Arduino pin controlling power on/off, a stream instance for received data (ie, `Serial`), and the Arduino pin controlling the trigger are required for the sensor constructor. +The Arduino pin controlling power on/off, a stream instance for received data (i.e., `Serial`), and the Arduino pin controlling the trigger are required for the sensor constructor. (Use -1 for the trigger pin if you do not have it connected.) Please see the section "[Notes on Arduino Streams and Software Serial](https://envirodiy.github.io/ModularSensors/page_arduino_streams.html)" for more information about what streams can be used along with this library. @@ -961,14 +952,24 @@ ___ ### Measurement Specialties MS5803-14BA Pressure Sensor The only input needed is the Arduino pin controlling power on/off; the i2cAddressHex and maximum pressure are optional as is the number of readings to average. -Keep in mind that the possible I2C addresses of the MS5803 match those of the BME280. - -@see @ref sensor_ms5803 +Keep in mind that the I2C address (0x76) of the MS5803 matches those of some configurations of the MS5837, BME280, BMP388, and BMP390 sensors; when using those sensors together, make sure they are set to different addresses. ___ +### TE Connectivity MS5837 Pressure Sensor + +The MS5837 is commonly used in Blue Robotics Bar02/Bar30 pressure sensors for underwater applications and depth measurement. +The only required input is the Arduino pin controlling power on/off; the sensor model, fluid density, air pressure, and number of readings to average are optional. +Keep in mind that the I2C address (0x76) of the MS5837 matches that of some configurations of the MS5803, BME280, BMP388, and BMP390 sensors; when using those sensors together, make sure they are set to different addresses. + +@see @ref sensor_ms5837 + + + +___ + ### Meter SDI-12 Sensors The next few sections are for Meter SDI-12 sensors. @@ -1130,7 +1131,7 @@ ___ The next several sections are for Yosemitech brand sensors. The sensor class constructors for each are nearly identical, except for the class name. -The sensor constructor requires as input: the sensor modbus address, a stream instance for data (ie, `Serial`), and one or two power pins. +The sensor constructor requires as input: the sensor modbus address, a stream instance for data (i.e., `Serial`), and one or two power pins. The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) For most of the sensors, Yosemitech strongly recommends averaging multiple (in most cases 10) readings for each measurement. @@ -1277,8 +1278,8 @@ Here we use the `new` keyword to create multiple variables and get pointers to t #### Creating Variables and Pasting UUIDs from MonitorMyWatershed -If you are sending data to monitor my watershed, it is much easier to create the variables in an array and then to paste the UUID's all together as copied from the "View Token UUID List" link for a site. -If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUID's. +If you are sending data to Monitor My Watershed, it is much easier to create the variables in an array and then to paste the UUIDs all together as copied from the "View Token UUID List" link for a site. +If using this method, be careful to ensure the order of your variables exactly matches the order of your UUIDs. @@ -1302,13 +1303,13 @@ ___ Here we set up all three possible data publishers and link all of them to the same Logger object. -#### Monitor My Watershed +#### Monitor My Watershed -To publish data to the Monitor My Watershed / EnviroDIY Data Sharing Portal first you must register yourself as a user at or . +To publish data to Monitor My Watershed, you must first register as a user at . Then you must register your site. After registering your site, a sampling feature and registration token for that site should be visible on the site page. - + ___ @@ -1563,10 +1564,8 @@ Every time the logger wakes we check the battery voltage and do 1 of three thing 1. If the battery is very low, go immediately back to sleep and hope the sun comes back out 2. If the battery is at a moderate level, attempt to collect data from sensors, but do not attempt to publish data. -The modem the biggest power user of the whole system. -3. - -At full power, do everything. + The modem is the biggest power user of the whole system. +3. At full power, do everything. @@ -1578,7 +1577,7 @@ Here are some guidelines for writing a loop function: - If you want to log on an even interval, use `if (checkInterval())` or `if (checkMarkedInterval())` to verify that the current or marked time is an even interval of the logging interval.. - Call the `markTime()` function if you want associate with a two iterations of sensor updates with the same timestamp. -This allows you to use `checkMarkedInterval()` to check if an action should be preformed based on the exact time when the logger woke rather than up to several seconds later when iterating through sensors. + This allows you to use `checkMarkedInterval()` to check if an action should be performed based on the exact time when the logger woke rather than up to several seconds later when iterating through sensors. - Either: - Power up all of your sensors with `sensorsPowerUp()`. - Wake up all your sensors with `sensorsWake()`. @@ -1587,7 +1586,7 @@ This allows you to use `checkMarkedInterval()` to check if an action should be p - Power down all of your sensors with `sensorsPowerDown()`. - Or: - Do a full update loop of all sensors, including powering them with `completeUpdate()`. -(This combines the previous 5 functions.) + (This combines the previous 5 functions.) - After updating the sensors, then call any functions you want to send/print/save data. - Finish by putting the logger back to sleep, if desired, with `systemSleep()`. @@ -1596,7 +1595,6 @@ All together, this gives: If you need more help in writing a complex loop, the [double_logger example program](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/double_logger) demonstrates using a custom loop function in order to log two different groups of sensors at different logging intervals. -The [data_saving example program](https://github.com/EnviroDIY/ModularSensors/tree/master/examples/data_saving) shows using a custom loop in order to save cellular data by saving data from many variables on the SD card, but only sending a portion of the data to the EnviroDIY data portal. diff --git a/examples/menu_a_la_carte/menu_a_la_carte.ino b/examples/menu_a_la_carte/menu_a_la_carte.ino index 6ffae3a4d..98e1c825d 100644 --- a/examples/menu_a_la_carte/menu_a_la_carte.ino +++ b/examples/menu_a_la_carte/menu_a_la_carte.ino @@ -17,18 +17,6 @@ // this makes sure the argument is expanded before converting to string #define STR(X) STR_(X) -// ========================================================================== -// Defines for TinyGSM -// ========================================================================== -/** Start [defines] */ -#ifndef TINY_GSM_RX_BUFFER -#define TINY_GSM_RX_BUFFER 64 -#endif -#ifndef TINY_GSM_YIELD_MS -#define TINY_GSM_YIELD_MS 2 -#endif -/** End [defines] */ - // ========================================================================== // Include the libraries required for any data logger @@ -48,7 +36,7 @@ // The modem and a number of sensors communicate over UART/TTL - often called // "serial". "Hardware" serial ports (automatically controlled by the MCU) are // generally the most accurate and should be configured and used for as many -// peripherals as possible. In some cases (ie, modbus communication) many +// peripherals as possible. In some cases (i.e., modbus communication) many // sensors can share the same serial port. // For AVR boards @@ -343,11 +331,11 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) uint8_t buttonPinMode = INPUT; // mode for debugging pin // NOTE: On the Mayfly (and Stonefly), pin 21 is tied to a button that pulls the // pin HIGH when pressed and an external pull-down that keeps the pin LOW when -// the button is not pressed. We want the pin mode to be INPUT - ie, floating +// the button is not pressed. We want the pin mode to be INPUT - i.e., floating // internally and pulled down externally until the button is pressed. AVR // processors like the 1284P on the Mayfly do not have internal pull-down // resistors - they do not have an INPUT_PULLDOWN mode like SAMD processors. @@ -356,7 +344,7 @@ uint8_t wakePinMode = INPUT_PULLUP; // mode for wake pin // Mayfly 0.x, 1.x D31 = A7 // NOTE: On the Mayfly, pin D31=A7 is tied directly to the RTC INT/SQW pin // on the onboard DS3231 RTC. The interrupt from the DS3231 will pull the pin -// DOWN, so we want the pin mode to be INPUT_PULLUP - ie, pulled up until the +// DOWN, so we want the pin mode to be INPUT_PULLUP - i.e., pulled up until the // RTC pulls it down. // 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 the wakePin to 1. @@ -370,17 +358,17 @@ const int8_t relayPowerPin = A3; // MCU pin controlling an optional power relay const int32_t serialBaud = 921600; // 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) uint8_t buttonPinMode = INPUT_PULLDOWN; // mode for debugging pin // NOTE: On the Stonefly (and Mayfly), 21 is tied to a button that pulls the pin // HIGH when pressed and an external pull-down that keeps the pin LOW when the -// button is not pressed. We want the pin mode to be INPUT_PULLDOWN - ie, pulled -// down both internally and externally until the button is pressed. +// button is not pressed. We want the pin mode to be INPUT_PULLDOWN - i.e., +// pulled down both internally and externally until the button is pressed. const int8_t wakePin = 38; // MCU interrupt/alarm pin to wake from sleep uint8_t wakePinMode = INPUT_PULLUP; // mode for wake pin // NOTE: On the Stonefly, pin D38 is tied directly to the RTC INT/SQW pin // on the onboard RV-8803 RTC. The interrupt from the RV-8803 will pull the pin -// DOWN, so we want the pin mode to be INPUT_PULLUP - ie, pulled up until the +// DOWN, so we want the pin mode to be INPUT_PULLUP - i.e., pulled up until the // RTC pulls it down. const int8_t sdCardPwrPin = -1; // MCU SD card power pin // const int8_t sdCardPwrPin = 32; // MCU SD card power pin @@ -397,7 +385,7 @@ const int8_t greenLED = PIN_LED2; // Pin for the green LED const int8_t greenLED = -1; // Pin for the green LED #endif const int8_t redLED = LED_BUILTIN; // Pin for the red LED -const int8_t buttonPin = -1; // Pin for debugging mode (ie, button pin) +const int8_t buttonPin = -1; // Pin for debugging mode (i.e., button pin) uint8_t buttonPinMode = INPUT_PULLUP; // mode for debugging pin const int8_t wakePin = -1; // MCU interrupt/alarm pin to wake from sleep uint8_t wakePinMode = INPUT_PULLUP; // mode for wake pin @@ -437,11 +425,11 @@ Logger dataLogger(LoggerID, samplingFeature, loggingInterval); // Network connection information // APN for cellular connection -#define CELLULAR_APN "add_your_cellular_apn" +#define CELLULAR_APN "YourAPN" // WiFi access point name -#define WIFI_ID "your_wifi_ssid" +#define WIFI_ID "YourWiFiSSID" // WiFi password (WPA2) -#define WIFI_PASSWD "your_wifi_password" +#define WIFI_PASSWD "YourWiFiPassword" #if defined(BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT) #define BUILD_HAS_MODEM @@ -989,6 +977,7 @@ Variable* mcuBoardReset = new ProcessorStats_ResetCode( #if defined(MS_USE_DS3231) // ========================================================================== // Maxim DS3231 RTC (Real Time Clock) +// Built in on Mayfly 0.x and 1.x // ========================================================================== /** Start [maxim_ds3231] */ #include @@ -1009,23 +998,35 @@ Variable* ds3231Temp = // ========================================================================== /** Start [alphasense_co2] */ #include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t AlphasenseCO2Power = sensorPowerPin; // Power pin -aco2_adsDiffMux_t AlphasenseDiffMux = - DIFF_MUX_2_3; // Differential voltage config -const uint8_t AlphasenseCO2ADSi2c_addr = - 0x48; // The I2C address of the ADS1115 ADC +const int8_t asCO2Power = sensorPowerPin; // Power pin +const int8_t asCO2Channel1 = 2; // First differential channel +const int8_t asCO2Channel2 = 3; // Second differential channel +const uint8_t asCO2Meas = 1; // Number of readings to average // Create an Alphasense CO2 sensor object -AlphasenseCO2 alphasenseCO2(AlphasenseCO2Power, AlphasenseDiffMux, - AlphasenseCO2ADSi2c_addr); +AlphasenseCO2 alphasenseCO2(asCO2Power, asCO2Channel1, asCO2Channel2, + asCO2Meas); // Create PAR and raw voltage variable pointers for the CO2 Variable* asCO2 = new AlphasenseCO2_CO2(&alphasenseCO2, "12345678-abcd-1234-ef00-1234567890ab"); Variable* asco2voltage = new AlphasenseCO2_Voltage( &alphasenseCO2, "12345678-abcd-1234-ef00-1234567890ab"); + +// create a custom analog reader based on the TI ADS1115 (optional) +float AsCO2Multiplier = 1.0f; // factor for a voltage divider +adsGain_t AsCO2AdsGain = GAIN_ONE; // gain of the ADS1115 +float AsCO2adsSupply = 3.3f; // supply voltage of the ADS1115 +const uint8_t AsCO2i2c_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader AsCO2ADS(AsCO2Multiplier, AsCO2AdsGain, AsCO2i2c_addr, + AsCO2adsSupply); + +// Create an Alphasense CO2 sensor object with the custom TIADS1x15Reader +AlphasenseCO2 alphasenseCO2_c(asCO2Power, asCO2Channel1, asCO2Channel2, + asCO2Meas, &AsCO2ADS); /** End [alphasense_co2] */ #endif @@ -1046,14 +1047,14 @@ Variable* asco2voltage = new AlphasenseCO2_Voltage( // NOTE: Use -1 for any pins that don't apply or aren't being used. byte anbModbusAddress = 0x55; // The modbus address of ANB pH Sensor (0x55 is the default) -const int8_t anbPower = sensorPowerPin; // ANB pH Sensor power pin -const int8_t alAdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t al485EnablePin = -1; // Adapter RE/DE pin -const uint8_t anbNumberReadings = 1; +const int8_t anbPower = sensorPowerPin; // ANB pH Sensor power pin +const int8_t anbAdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t anb485EnablePin = -1; // Adapter RE/DE pin +const uint8_t anbNReadings = 1; // Number of readings // Create an ANB pH sensor object -ANBpH anbPH(anbModbusAddress, modbusSerial, anbPower, alAdapterPower, - al485EnablePin, anbNumberReadings); +ANBpH anbPH(anbModbusAddress, modbusSerial, anbPower, loggingInterval, + anbAdapterPower, anb485EnablePin, anbNReadings); // Create all of the variable pointers for the ANB pH sensor Variable* anbPHValue = new ANBpH_pH(&anbPH, @@ -1131,20 +1132,32 @@ Variable* dhtHI = new AOSongDHT_HI(&dht, // ========================================================================== /** Start [apogee_sq212] */ #include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t SQ212Power = sensorPowerPin; // Power pin -const int8_t SQ212ADSChannel = 3; // The ADS channel for the SQ212 -const uint8_t SQ212ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC +const int8_t sq212Power = sensorPowerPin; // Power pin +const int8_t sq212ADSChannel = 3; // The ADS channel for the SQ212 +const uint8_t sq212Readings = 1; // The number of readings to average -// Create an Apogee SQ212 sensor object -ApogeeSQ212 SQ212(SQ212Power, SQ212ADSChannel, SQ212ADSi2c_addr); +// Create an Apogee SQ212 sensor object with the default voltage reader +ApogeeSQ212 sq212(sq212Power, sq212ADSChannel); // Create PAR and raw voltage variable pointers for the SQ212 Variable* sq212PAR = - new ApogeeSQ212_PAR(&SQ212, "12345678-abcd-1234-ef00-1234567890ab"); + new ApogeeSQ212_PAR(&sq212, "12345678-abcd-1234-ef00-1234567890ab"); Variable* sq212voltage = - new ApogeeSQ212_Voltage(&SQ212, "12345678-abcd-1234-ef00-1234567890ab"); + new ApogeeSQ212_Voltage(&sq212, "12345678-abcd-1234-ef00-1234567890ab"); + +// create a custom analog reader based on the TI ADS1115 (optional) +float sq212Multiplier = 1.0f; // factor for a voltage divider +adsGain_t sq212AdsGain = GAIN_ONE; // gain of the ADS1115 +float sq212adsSupply = 3.3f; // supply voltage of the ADS1115 +const uint8_t sq212i2c_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader sq212ADS(sq212Multiplier, sq212AdsGain, sq212i2c_addr, + sq212adsSupply); + +// Create an Apogee SQ212 sensor object with the custom TIADS1x15Reader +ApogeeSQ212 sq212_c(sq212Power, sq212ADSChannel, sq212Readings, &sq212ADS); /** End [apogee_sq212] */ #endif @@ -1311,10 +1324,10 @@ Variable* atlasGrav = new AtlasScientificEC_SpecificGravity( // (that is, the specific conductance). For this example, we will use the // temperature measured by the Atlas RTD above this. You could use the // temperature returned by any other water temperature sensor if desired. -// **DO NOT** use your logger board temperature (ie, from the DS3231) to +// **DO NOT** use your logger board temperature (i.e., from the DS3231) to // calculate specific conductance! float calculateAtlasSpCond() { - float spCond = -9999; // Always safest to start with a bad value + float spCond = MS_INVALID_VALUE; // Always safest to start with a bad value float waterTemp = atlasTemp->getValue(); float rawCond = atlasCond->getValue(); // ^^ Linearized temperature correction coefficient per degrees Celsius. @@ -1323,7 +1336,7 @@ float calculateAtlasSpCond() { // environmental monitoring and geophysical data inversion. Environ Monit // Assess. 2004 Aug-Sep;96(1-3):119-28. // doi: 10.1023/b:emas.0000031719.83065.68. PMID: 15327152. - if (waterTemp != -9999 && rawCond != -9999) { + if (waterTemp != MS_INVALID_VALUE && rawCond != MS_INVALID_VALUE) { // make sure both inputs are good float temperatureCoef = 0.019; spCond = rawCond / (1 + temperatureCoef * (waterTemp - 25.0)); @@ -1447,11 +1460,11 @@ Variable* clarivueError = new CampbellClariVUE10_ErrorCode( // ========================================================================== /** Start [campbell_obs3] */ #include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t OBS3Power = sensorPowerPin; // Power pin -const uint8_t OBS3NumberReadings = 10; -const uint8_t OBS3ADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC +const int8_t OBS3Power = sensorPowerPin; // Power pin +const uint8_t OBS3NReadings = 10; const int8_t OBSLowADSChannel = 0; // ADS channel for *low* range output @@ -1462,7 +1475,7 @@ const float OBSLow_C = 0.000E+00; // "C" value [*low* range] // Create a Campbell OBS3+ *low* range sensor object CampbellOBS3 osb3low(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, OBSLow_C, - OBS3ADSi2c_addr, OBS3NumberReadings); + OBS3NReadings); // Create turbidity and voltage variable pointers for the low range of the OBS3 Variable* obs3TurbLow = new CampbellOBS3_Turbidity( @@ -1480,13 +1493,31 @@ const float OBSHigh_C = 0.000E+00; // "C" value [*high* range] // Create a Campbell OBS3+ *high* range sensor object CampbellOBS3 osb3high(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, - OBSHigh_C, OBS3ADSi2c_addr, OBS3NumberReadings); + OBSHigh_C, OBS3NReadings); // Create turbidity and voltage variable pointers for the high range of the OBS3 Variable* obs3TurbHigh = new CampbellOBS3_Turbidity( &osb3high, "12345678-abcd-1234-ef00-1234567890ab", "TurbHigh"); Variable* obs3VoltHigh = new CampbellOBS3_Voltage( &osb3high, "12345678-abcd-1234-ef00-1234567890ab", "TurbHighV"); + +// create a custom analog reader based on the TI ADS1115 (optional) +float OBS3Multiplier = 1.0f; // factor for a voltage divider +adsGain_t OBS3AdsGain = GAIN_ONE; // gain of the ADS1115 +float OBS3AdsSupplyVoltage = 3.3f; // supply voltage of the ADS1115 +const uint8_t OBS3AdsI2C_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader OBSADS(OBS3Multiplier, OBS3AdsGain, OBS3AdsI2C_addr, + OBS3AdsSupplyVoltage); + +// Create a Campbell OBS3+ *low* range sensor object with the custom +// TIADS1x15Reader +CampbellOBS3 osb3low_c(OBS3Power, OBSLowADSChannel, OBSLow_A, OBSLow_B, + OBSLow_C, OBS3NReadings, &OBSADS); + +// Create a Campbell OBS3+ *high* range sensor object with the custom +// TIADS1x15Reader +CampbellOBS3 osb3high_c(OBS3Power, OBSHighADSChannel, OBSHigh_A, OBSHigh_B, + OBSHigh_C, OBS3NReadings, &OBSADS); /** End [campbell_obs3] */ #endif @@ -1528,13 +1559,13 @@ Variable* rainvueRainRateMax = new CampbellRainVUE10_RainRateMax( #include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const char* CTDSDI12address = "1"; // The SDI-12 Address of the CTD -const uint8_t CTDNumberReadings = 6; // The number of readings to average -const int8_t CTDPower = sensorPowerPin; // Power pin -const int8_t CTDData = sdi12DataPin; // The SDI-12 data pin +const char* CTDSDI12address = "1"; // The SDI-12 Address of the CTD +const uint8_t CTDNReadings = 6; // The number of readings to average +const int8_t CTDPower = sensorPowerPin; // Power pin +const int8_t CTDData = sdi12DataPin; // The SDI-12 data pin // Create a Decagon CTD sensor object -DecagonCTD ctd(*CTDSDI12address, CTDPower, CTDData, CTDNumberReadings); +DecagonCTD ctd(*CTDSDI12address, CTDPower, CTDData, CTDNReadings); // Create conductivity, temperature, and depth variable pointers for the CTD Variable* ctdCond = new DecagonCTD_Cond(&ctd, @@ -1555,13 +1586,13 @@ Variable* ctdDepth = #include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const char* ES2SDI12address = "3"; // The SDI-12 Address of the ES2 -const int8_t ES2Power = sensorPowerPin; // Power pin -const int8_t ES2Data = sdi12DataPin; // The SDI-12 data pin -const uint8_t ES2NumberReadings = 5; +const char* ES2SDI12address = "3"; // The SDI-12 Address of the ES2 +const int8_t ES2Power = sensorPowerPin; // Power pin +const int8_t ES2Data = sdi12DataPin; // The SDI-12 data pin +const uint8_t ES2NReadings = 5; // Create a Decagon ES2 sensor object -DecagonES2 es2(*ES2SDI12address, ES2Power, ES2Data, ES2NumberReadings); +DecagonES2 es2(*ES2SDI12address, ES2Power, ES2Data, ES2NReadings); // Create specific conductance and temperature variable pointers for the ES2 Variable* es2Cond = new DecagonES2_Cond(&es2, @@ -1578,6 +1609,7 @@ Variable* es2Temp = new DecagonES2_Temp(&es2, // ========================================================================== /** Start [everlight_alspt19] */ #include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. const int8_t alsPower = sensorPowerPin; // Power pin @@ -1588,14 +1620,15 @@ const int8_t alsData = A4; // The ALS PT-19 data pin #endif const int8_t alsSupply = 3.3; // The ALS PT-19 supply power voltage const int8_t alsResistance = 10; // The ALS PT-19 loading resistance (in kΩ) -const uint8_t alsNumberReadings = 10; +const uint8_t alsNReadings = 10; // Create a Everlight ALS-PT19 sensor object EverlightALSPT19 alsPt19(alsPower, alsData, alsSupply, alsResistance, - alsNumberReadings); + alsNReadings); -// For an EnviroDIY Mayfly, you can use the abbreviated version -// EverlightALSPT19 alsPt19(alsNumberReadings); +// For a board with ALS parameters set in KnownProcessors.h (i.e., an EnviroDIY +// Mayfly or Stonefly), you can simply do: +// EverlightALSPT19 alsPt19_mf(alsNReadings); // Create voltage, current, and illuminance variable pointers for the ALS-PT19 Variable* alsPt19Volt = new EverlightALSPT19_Voltage( @@ -1604,6 +1637,16 @@ Variable* alsPt19Current = new EverlightALSPT19_Current( &alsPt19, "12345678-abcd-1234-ef00-1234567890ab"); Variable* alsPt19Lux = new EverlightALSPT19_Illuminance( &alsPt19, "12345678-abcd-1234-ef00-1234567890ab"); + +// create a custom analog reader based on the Processor ADC (optional) +float alsMultiplier = 1.0f; // factor for a voltage divider +float alsAdsSupply = 3.3f; // supply voltage of the Processor ADC +ProcessorAnalogReader alsADS(alsMultiplier, alsAdsSupply); + +// Create an Everlight ALS-PT19 sensor object with the custom +// ProcessorAnalogReader +EverlightALSPT19 alsPt19_c(alsPower, alsData, alsSupply, alsResistance, + alsNReadings, &alsADS); /** End [everlight_alspt19] */ #endif @@ -1616,15 +1659,32 @@ Variable* alsPt19Lux = new EverlightALSPT19_Illuminance( #include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t ADSPower = sensorPowerPin; // Power pin -const int8_t ADSChannel = 2; // The ADS channel of interest -const float dividerGain = 10; // Gain setting if using a voltage divider -const uint8_t evADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC -const uint8_t VoltReadsToAvg = 1; // Only read one sample - -// Create an External Voltage sensor object -TIADS1x15 ads1x15(ADSPower, ADSChannel, dividerGain, evADSi2c_addr, - VoltReadsToAvg); +const int8_t evADSPower = sensorPowerPin; // Power pin +const int8_t evADSChannel1 = + 2; // The first ADS channel (or only for single-ended readings) +const int8_t evADSChannel2 = + 3; // The second ADS channel (for differential readings). Pass -1 for + // single-ended readings. +const uint8_t evReadsToAvg = 1; // Only read one sample + +// Create a single-ended External Voltage sensor object with the default +// TIADS1x15Reader +TIADS1x15 ads1x15(evADSPower, evADSChannel1); +// Create a differential External Voltage sensor object with the default +// TIADS1x15Reader +TIADS1x15 ads1x15_d(evADSPower, evADSChannel1, evADSChannel2); + +// create a custom ADS instance (optional) +float evVoltageMultiplier = 1.0f; // factor for a voltage divider +adsGain_t evAdsGain = GAIN_ONE; // gain of the ADS1115 +float evAdsSupplyVoltage = 3.3f; // supply voltage of the ADS1115 +const uint8_t evAdsI2C_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader evADS(evVoltageMultiplier, evAdsGain, evAdsI2C_addr, + evAdsSupplyVoltage); + +// Create a single ended External Voltage sensor object with the custom +// TIADS1x15Reader +TIADS1x15 ads1x15_c(evADSPower, evADSChannel1, -1, evReadsToAvg, &evADS); // Create a voltage variable pointer Variable* ads1x15Volt = @@ -1633,6 +1693,42 @@ Variable* ads1x15Volt = #endif +#if defined(BUILD_SENSOR_PROCESSOR_ANALOG) +// ========================================================================== +// External Voltage via Processor ADC +// ========================================================================== +/** Start [processor_analog] */ +#include + +// NOTE: Use -1 for any pins that don't apply or aren't being used. +const int8_t processorAnalogPowerPin = sensorPowerPin; // Power pin +const int8_t processorAnalogDataPin = A0; // Analog input pin (processor ADC) +const uint8_t processorAnalogNReadings = 1; // Only read one sample + +// Create a Processor Analog sensor object with the default +// ProcessorAnalogReader +ProcessorAnalog processorAnalog(processorAnalogPowerPin, processorAnalogDataPin, + processorAnalogNReadings); + +// create a custom analog reader based on the Processor ADC (optional) +float processorAnalogMultiplier = 1.0f; // factor for a voltage divider +float processorAnalogSupply = 3.3f; // supply voltage of the Processor ADC +ProcessorAnalogReader processorAnalogReaderCustom(processorAnalogMultiplier, + processorAnalogSupply); + +// Create a Processor Analog sensor object with the custom ProcessorAnalogReader +ProcessorAnalog processorAnalog_c(processorAnalogPowerPin, + processorAnalogDataPin, + processorAnalogNReadings, + &processorAnalogReaderCustom); + +// Create a voltage variable pointer +Variable* processorAnalogVolts = new ProcessorAnalog_Voltage( + &processorAnalog, "12345678-abcd-1234-ef00-1234567890ab"); +/** End [processor_analog] */ +#endif + + #if defined(BUILD_SENSOR_FREESCALE_MPL115A2) // ========================================================================== // Freescale Semiconductor MPL115A2 Barometer @@ -1700,16 +1796,16 @@ Variable* hydrocamByteError = new GeoluxHydroCam_ByteError( // NOTE: Use -1 for any pins that don't apply or aren't being used. byte gplp8ModbusAddress = 0x19; // The modbus address of the gplp8 // Raw Request >>> {0x19, 0x03, 0x00, 0xC8, 0x00, 0x01, 0x06, 0x2C} -const int8_t gplp8AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t gplp8SensorPower = relayPowerPin; // Sensor power pin -const int8_t gplp8EnablePin = -1; // Adapter RE/DE pin -const uint8_t gplp8NumberReadings = 1; +const int8_t gplp8AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t gplp8SensorPower = relayPowerPin; // Sensor power pin +const int8_t gplp8EnablePin = -1; // Adapter RE/DE pin +const uint8_t gplp8NReadings = 1; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a GroPoint Profile GPLP-8 sensor object GroPointGPLP8 gplp8(gplp8ModbusAddress, modbusSerial, gplp8AdapterPower, - gplp8SensorPower, gplp8EnablePin, gplp8NumberReadings); + gplp8SensorPower, gplp8EnablePin, gplp8NReadings); // Create moisture variable pointers for each segment of the GPLP-8 Variable* gplp8Moist1 = new GroPointGPLP8_Moist( @@ -1768,13 +1864,13 @@ Variable* gplp8Temp13 = new GroPointGPLP8_Temp( #include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const char* RDOSDI12address = "5"; // The SDI-12 Address of the RDO PRO-X -const int8_t RDOPower = sensorPowerPin; // Power pin -const int8_t RDOData = sdi12DataPin; // The SDI-12 data pin -const uint8_t RDONumberReadings = 3; +const char* rdoSDI12address = "5"; // The SDI-12 Address of the RDO PRO-X +const int8_t rdoPower = sensorPowerPin; // Power pin +const int8_t rdoData = sdi12DataPin; // The SDI-12 data pin +const uint8_t rdoNReadings = 3; // Create an In-Situ RDO PRO-X dissolved oxygen sensor object -InSituRDO insituRDO(*RDOSDI12address, RDOPower, RDOData, RDONumberReadings); +InSituRDO insituRDO(*rdoSDI12address, rdoPower, rdoData, rdoNReadings); // Create dissolved oxygen percent, dissolved oxygen concentration, temperature, // and oxygen partial pressure variable pointers for the RDO PRO-X @@ -1798,16 +1894,16 @@ Variable* rdoO2pp = #include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const char* TROLLSDI12address = +const char* trollSDI12address = "1"; // The SDI-12 Address of the Aqua/Level TROLL -const int8_t TROLLPower = +const int8_t trollPower = sensorPowerPin; // Pin to switch power on and off (-1 if unconnected) -const int8_t TROLLData = sdi12DataPin; // The SDI-12 data pin -const uint8_t TROLLNumberReadings = 2; // The number of readings to average +const int8_t trollData = sdi12DataPin; // The SDI-12 data pin +const uint8_t trollNReadings = 2; // The number of readings to average // Create an In-Situ TROLL sensor object -InSituTrollSdi12a insituTROLL(*TROLLSDI12address, TROLLPower, TROLLData, - TROLLNumberReadings); +InSituTrollSdi12a insituTROLL(*trollSDI12address, trollPower, trollData, + trollNReadings); // Create pressure, temperature, and depth variable pointers for the TROLL Variable* trollPressure = new InSituTrollSdi12a_Pressure( @@ -1838,13 +1934,12 @@ byte acculevelModbusAddress = 0x01; // The modbus address of KellerAcculevel const int8_t acculevelPower = relayPowerPin; // Acculevel Sensor power pin const int8_t alAdapterPower = sensorPowerPin; // RS485 adapter power pin const int8_t al485EnablePin = -1; // Adapter RE/DE pin -const uint8_t acculevelNumberReadings = 5; +const uint8_t acculevelNReadings = 5; // The manufacturer recommends taking and averaging a few readings // Create a Keller Acculevel sensor object KellerAcculevel acculevel(acculevelModbusAddress, modbusSerial, alAdapterPower, - acculevelPower, al485EnablePin, - acculevelNumberReadings); + acculevelPower, al485EnablePin, acculevelNReadings); // Create pressure, temperature, and height variable pointers for the Acculevel Variable* acculevPress = new KellerAcculevel_Pressure( @@ -1875,13 +1970,12 @@ byte nanolevelModbusAddress = 0x01; // The modbus address of KellerNanolevel const int8_t nlAdapterPower = sensorPowerPin; // RS485 adapter power pin const int8_t nanolevelPower = relayPowerPin; // Sensor power pin const int8_t nl485EnablePin = -1; // Adapter RE/DE pin -const uint8_t nanolevelNumberReadings = 5; +const uint8_t nanolevelNReadings = 5; // The manufacturer recommends taking and averaging a few readings // Create a Keller Nanolevel sensor object KellerNanolevel nanolevel(nanolevelModbusAddress, modbusSerial, nlAdapterPower, - nanolevelPower, nl485EnablePin, - nanolevelNumberReadings); + nanolevelPower, nl485EnablePin, nanolevelNReadings); // Create pressure, temperature, and height variable pointers for the Nanolevel Variable* nanolevPress = new KellerNanolevel_Pressure( @@ -1911,12 +2005,12 @@ Variable* nanolevHeight = new KellerNanolevel_Height( const int8_t SonarPower = sensorPowerPin; // Excite (power) pin const int8_t Sonar1Trigger = -1; // Trigger pin // Trigger should be a *unique* negative number if unconnected -const int16_t Sonar1MaxRange = 9999; // Maximum range of sonar -const uint8_t sonar1NumberReadings = 3; // The number of readings to average +const int16_t Sonar1MaxRange = 9999; // Maximum range of sonar +const uint8_t sonar1NReadings = 3; // The number of readings to average // Create a MaxBotix Sonar sensor object MaxBotixSonar sonar1(sonarSerial, SonarPower, Sonar1Trigger, Sonar1MaxRange, - sonar1NumberReadings); + sonar1NReadings); // Create an ultrasonic range variable pointer Variable* sonar1Range = @@ -1938,12 +2032,12 @@ Variable* sonar1Range = DeviceAddress OneWireAddress1 = {0x28, 0xFF, 0xBD, 0xBA, 0x81, 0x16, 0x03, 0x0C}; // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t OneWirePower = sensorPowerPin; // Power pin -const int8_t OneWireBus = A0; // OneWire Bus Pin -const int8_t ds18NumberReadings = 3; +const int8_t OneWirePower = sensorPowerPin; // Power pin +const int8_t OneWireBus = A0; // OneWire Bus Pin +const int8_t ds18NReadings = 3; // Create a Maxim DS18 sensor objects (use this form for a known address) -MaximDS18 ds18(OneWireAddress1, OneWirePower, OneWireBus, ds18NumberReadings); +MaximDS18 ds18(OneWireAddress1, OneWirePower, OneWireBus, ds18NReadings); // Create a Maxim DS18 sensor object (use this form for a single sensor on bus // with an unknown address) @@ -1984,6 +2078,39 @@ Variable* ms5803Temp = #endif +#if defined(BUILD_SENSOR_TE_CONNECTIVITY_MS5837) +// ========================================================================== +// TE Connectivity MS5837 Pressure Sensor (deployed in Blue Robotics +// Bar02/Bar30) +// ========================================================================== +/** Start [te_connectivity_ms5837] */ +#include + +// NOTE: Use -1 for any pins that don't apply or aren't being used. +const int8_t MS5837Power = sensorPowerPin; // Power pin +const MS5837Model MS5837model = MS5837Model::MS5837_02BA; +// - MS5837Model::MS5837_30BA for 30 bar range sensors (MS5837-30BA) +// - MS5837Model::MS5837_02BA for 2 bar range sensors (MS5837-02BA) +// - MS5837Model::MS5803_01BA for 1 bar range sensors (MS5803-01BA) +const uint8_t MS5837ReadingsToAvg = 1; + +// Create a TE Connectivity MS5837 pressure and temperature sensor object +TEConnectivityMS5837 ms5837(MS5837Power, MS5837model, MS5837ReadingsToAvg); + +// Create pressure, temperature, depth, and altitude variable pointers for the +// MS5837 +Variable* ms5837Press = new TEConnectivityMS5837_Pressure( + &ms5837, "12345678-abcd-1234-ef00-1234567890ab"); +Variable* ms5837Temp = new TEConnectivityMS5837_Temp( + &ms5837, "12345678-abcd-1234-ef00-1234567890ab"); +Variable* ms5837Depth = new TEConnectivityMS5837_Depth( + &ms5837, "12345678-abcd-1234-ef00-1234567890ac"); +Variable* ms5837Alt = new TEConnectivityMS5837_Altitude( + &ms5837, "12345678-abcd-1234-ef00-1234567890ad"); +/** End [te_connectivity_ms5837] */ +#endif + + #if defined(BUILD_SENSOR_DECAGON_5TM) // ========================================================================== // Meter ECH2O Soil Moisture Sensor @@ -2020,13 +2147,13 @@ Variable* fivetmTemp = // NOTE: Use -1 for any pins that don't apply or aren't being used. const char* hydros21SDI12address = "1"; // The SDI-12 Address of the Hydros21 -const uint8_t hydros21NumberReadings = 6; // The number of readings to average -const int8_t hydros21Power = sensorPowerPin; // Power pin -const int8_t hydros21Data = sdi12DataPin; // The SDI-12 data pin +const uint8_t hydros21NReadings = 6; // The number of readings to average +const int8_t hydros21Power = sensorPowerPin; // Power pin +const int8_t hydros21Data = sdi12DataPin; // The SDI-12 data pin // Create a Decagon Hydros21 sensor object MeterHydros21 hydros21(*hydros21SDI12address, hydros21Power, hydros21Data, - hydros21NumberReadings); + hydros21NReadings); // Create conductivity, temperature, and depth variable pointers for the // Hydros21 @@ -2051,11 +2178,11 @@ Variable* hydros21Depth = const char* teros11SDI12address = "4"; // The SDI-12 Address of the Teros 11 const int8_t terosPower = sensorPowerPin; // Power pin const int8_t terosData = sdi12DataPin; // The SDI-12 data pin -const uint8_t teros11NumberReadings = 3; // The number of readings to average +const uint8_t teros11NReadings = 3; // The number of readings to average // Create a METER TEROS 11 sensor object MeterTeros11 teros11(*teros11SDI12address, terosPower, terosData, - teros11NumberReadings); + teros11NReadings); // Create the matric potential, volumetric water content, and temperature // variable pointers for the Teros 11 @@ -2217,12 +2344,13 @@ Variable* inaPower = new TIINA219_Power(&ina219, // ========================================================================== /** Start [turner_cyclops] */ #include +#include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t cyclopsPower = sensorPowerPin; // Power pin -const uint8_t cyclopsNumberReadings = 10; -const uint8_t cyclopsADSi2c_addr = 0x48; // The I2C address of the ADS1115 ADC -const int8_t cyclopsADSChannel = 0; // ADS channel +const int8_t cyclopsPower = sensorPowerPin; // Power pin +const uint8_t cyclopsNReadings = 10; +const int8_t cyclopsADSChannel = 0; // ADS channel // Cyclops calibration information const float cyclopsStdConc = 1.000; // Concentration of the standard used @@ -2234,8 +2362,7 @@ const float cyclopsBlankVolt = // Create a Turner Cyclops sensor object TurnerCyclops cyclops(cyclopsPower, cyclopsADSChannel, cyclopsStdConc, - cyclopsStdVolt, cyclopsBlankVolt, cyclopsADSi2c_addr, - cyclopsNumberReadings); + cyclopsStdVolt, cyclopsBlankVolt, cyclopsNReadings); // Create the voltage variable pointer - used for any type of Cyclops Variable* cyclopsVoltage = @@ -2269,6 +2396,29 @@ Variable* cyclopsTryptophan = new TurnerCyclops_Tryptophan( &cyclops, "12345678-abcd-1234-ef00-1234567890ab"); Variable* cyclopsRedChloro = new TurnerCyclops_RedChlorophyll( &cyclops, "12345678-abcd-1234-ef00-1234567890ab"); + +// create a custom analog reader based on the TI ADS1115 (optional) +float cyclopsMultiplier = 1.0f; // factor for a voltage divider +adsGain_t cyclopsAdsGain = GAIN_ONE; // gain of the ADS1115 +float cyclopsAdsSupplyVoltage = 3.3f; // supply voltage of the ADS1115 +const uint8_t cyclopsAdsI2C_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader cyclopsADS(cyclopsMultiplier, cyclopsAdsGain, + cyclopsAdsI2C_addr, cyclopsAdsSupplyVoltage); + +// Create a Turner Cyclops sensor object with the custom ADS instance +TurnerCyclops cyclops_c(cyclopsPower, cyclopsADSChannel, cyclopsStdConc, + cyclopsStdVolt, cyclopsBlankVolt, cyclopsNReadings, + &cyclopsADS); + +// create a custom analog reader based on the Processor ADC (optional) +float cyclopsMultiplier2 = 1.0f; // factor for a voltage divider +float cyclopsSupply2 = 3.3f; // supply voltage of the Processor ADC +ProcessorAnalogReader cyclopsADS2(cyclopsMultiplier2, cyclopsSupply2); + +// Create a Turner Cyclops sensor object with the custom ADS instance +TurnerCyclops cyclops_c2(cyclopsPower, cyclopsADSChannel, cyclopsStdConc, + cyclopsStdVolt, cyclopsBlankVolt, cyclopsNReadings, + &cyclopsADS2); /** End [turner_cyclops] */ #endif @@ -2279,39 +2429,47 @@ Variable* cyclopsRedChloro = new TurnerCyclops_RedChlorophyll( // ========================================================================== /** Start [turner_turbidity_plus] */ #include +#include // NOTE: Use -1 for any pins that don't apply or aren't being used. -const int8_t turbidityPlusPower = sensorPowerPin; // Power pin -const int8_t turbidityPlusWiper = relayPowerPin; // Wiper pin -ttp_adsDiffMux_t turbidityPlusDiffMux = - DIFF_MUX_2_3; // Differential voltage config -const uint8_t turbidityPlusNumberReadings = 10; -const uint8_t turbidityPlusADSi2c_addr = - 0x48; // The I2C address of the ADS1115 ADC -adsGain_t turbidityPlusGain = GAIN_ONE; // The gain of the ADS -float tpVoltageDividerFactor = 1; // The factor for a voltage divider, if any +const int8_t ttPlusPower = sensorPowerPin; // Power pin +const int8_t ttPlusWiper = relayPowerPin; // Wiper pin +const int8_t ttPlusChannel1 = 2; // First differential channel +const int8_t ttPlusChannel2 = 3; // Second differential channel +const uint8_t ttPlusReadings = 10; // Turbidity Plus calibration information -const float turbidityPlusStdConc = 1.000; // Concentration of the standard used - // for a 1-point sensor calibration. -const float turbidityPlusStdVolt = +const float ttPlusStdConc = 1.000; // Concentration of the standard used + // for a 1-point sensor calibration. +const float ttPlusStdVolt = 1.000; // The voltage (in volts) measured for the conc_std. -const float turbidityPlusBlankVolt = +const float ttPlusBlankVolt = 0.000; // The voltage (in volts) measured for a blank. // Create a Turner Turbidity Plus sensor object -TurnerTurbidityPlus turbidityPlus(turbidityPlusPower, turbidityPlusWiper, - turbidityPlusDiffMux, turbidityPlusStdConc, - turbidityPlusStdVolt, turbidityPlusBlankVolt, - turbidityPlusADSi2c_addr, turbidityPlusGain, - turbidityPlusNumberReadings, - tpVoltageDividerFactor); +TurnerTurbidityPlus turbidityPlus(ttPlusPower, ttPlusWiper, ttPlusChannel1, + ttPlusChannel2, ttPlusStdConc, ttPlusStdVolt, + ttPlusBlankVolt, ttPlusReadings); // Create the variable pointers Variable* turbidityPlusVoltage = new TurnerTurbidityPlus_Voltage( &turbidityPlus, "12345678-abcd-1234-ef00-1234567890ab"); Variable* turbidityPlusTurbidity = new TurnerTurbidityPlus_Turbidity( &turbidityPlus, "12345678-abcd-1234-ef00-1234567890ab"); + +// create a custom analog reader based on the TI ADS1115 (optional) +float ttPlusMultiplier = 1.0f; // factor for a voltage divider +adsGain_t ttPlusAdsGain = GAIN_ONE; // gain of the ADS1115 +float ttPlusAdsSupply = 3.3f; // supply voltage of the ADS1115 +const uint8_t ttPlusI2C_addr = 0x48; // The I2C address of the ADS1115 ADC +TIADS1x15Reader ttPlusADS(ttPlusMultiplier, ttPlusAdsGain, ttPlusI2C_addr, + ttPlusAdsSupply); + +// Create a Turner Turbidity Plus sensor object with the custom TIADS1x15Reader +TurnerTurbidityPlus turbidityPlus_c(ttPlusPower, ttPlusWiper, ttPlusChannel1, + ttPlusChannel2, ttPlusStdConc, + ttPlusStdVolt, ttPlusBlankVolt, + ttPlusReadings, &ttPlusADS); /** End [turner_turbidity_plus] */ #endif @@ -2322,25 +2480,27 @@ Variable* turbidityPlusTurbidity = new TurnerTurbidityPlus_Turbidity( // ========================================================================== /** Start [analog_elec_conductivity] */ #include +#include -const int8_t ECpwrPin = A4; // Power pin (-1 if continuously powered) -const int8_t ECdataPin1 = A0; // Data pin (must be an analog pin, ie A#) +const int8_t analogECPower = A4; // Power pin (-1 if continuously powered) +const int8_t analogECData = A0; // Data pin (must be an analog pin, i.e., A#) +const uint8_t analogECNReadings = 1; // The number of readings to average // Create an Analog Electrical Conductivity sensor object -AnalogElecConductivity analogEC_phy(ECpwrPin, ECdataPin1); +AnalogElecConductivity analogEC(analogECPower, analogECData); // Create a conductivity variable pointer for the analog sensor Variable* analogEc_cond = new AnalogElecConductivity_EC( - &analogEC_phy, "12345678-abcd-1234-ef00-1234567890ab"); + &analogEC, "12345678-abcd-1234-ef00-1234567890ab"); // Create a calculated variable for the temperature compensated conductivity // (that is, the specific conductance). For this example, we will use the // temperature measured by the Maxim DS18 saved as ds18Temp several sections // above this. You could use the temperature returned by any other water // temperature sensor if desired. **DO NOT** use your logger board temperature -// (ie, from the DS3231) to calculate specific conductance! +// (i.e., from the DS3231) to calculate specific conductance! float calculateAnalogSpCond() { - float spCond = -9999; // Always safest to start with a bad value + float spCond = MS_INVALID_VALUE; // Always safest to start with a bad value float waterTemp = ds18Temp->getValue(); float rawCond = analogEc_cond->getValue(); float temperatureCoef = 0.019; @@ -2350,7 +2510,7 @@ float calculateAnalogSpCond() { // environmental monitoring and geophysical data inversion. Environ Monit // Assess. 2004 Aug-Sep;96(1-3):119-28. // doi: 10.1023/b:emas.0000031719.83065.68. PMID: 15327152. - if (waterTemp != -9999 && rawCond != -9999) { + if (waterTemp != MS_INVALID_VALUE && rawCond != MS_INVALID_VALUE) { // make sure both inputs are good spCond = rawCond / (1 + temperatureCoef * (waterTemp - 25.0)); } @@ -2374,6 +2534,17 @@ Variable* analogEc_spcond = new Variable( calculateAnalogSpCond, analogSpCondResolution, analogSpCondName, analogSpCondUnit, analogSpCondCode, analogSpCondUUID); /** End [analog_elec_conductivity] */ + +// create a custom analog reader based on the Processor ADC (optional) +float analogECMultiplier = 1.0f; // factor for a voltage divider +float analogECSupply = 3.3f; // supply voltage of the Processor ADC +ProcessorAnalogReader analogECADS(analogECMultiplier, analogECSupply); + +// Create an AnalogElecConductivity sensor object with the custom ADS instance +AnalogElecConductivity analogEC_c(analogECPower, analogECData, + ANALOGELECCONDUCTIVITY_RSERIES_OHMS, + ANALOGELECCONDUCTIVITY_KONST, + analogECNReadings, &analogECADS); #endif @@ -2424,17 +2595,17 @@ Variable* VegaPulsError = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y504ModbusAddress = 0x04; // The modbus address of the Y504 -const int8_t y504AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y504SensorPower = relayPowerPin; // Sensor power pin -const int8_t y504EnablePin = -1; // Adapter RE/DE pin -const uint8_t y504NumberReadings = 5; +byte y504ModbusAddress = 0x04; // The modbus address of the Y504 +const int8_t y504AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y504SensorPower = relayPowerPin; // Sensor power pin +const int8_t y504EnablePin = -1; // Adapter RE/DE pin +const uint8_t y504NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Yosemitech Y504 dissolved oxygen sensor object YosemitechY504 y504(y504ModbusAddress, modbusSerial, y504AdapterPower, - y504SensorPower, y504EnablePin, y504NumberReadings); + y504SensorPower, y504EnablePin, y504NReadings); // Create the dissolved oxygen percent, dissolved oxygen concentration, and // temperature variable pointers for the Y504 @@ -2462,17 +2633,17 @@ Variable* y504Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y510ModbusAddress = 0x0B; // The modbus address of the Y510 -const int8_t y510AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y510SensorPower = relayPowerPin; // Sensor power pin -const int8_t y510EnablePin = -1; // Adapter RE/DE pin -const uint8_t y510NumberReadings = 5; +byte y510ModbusAddress = 0x0B; // The modbus address of the Y510 +const int8_t y510AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y510SensorPower = relayPowerPin; // Sensor power pin +const int8_t y510EnablePin = -1; // Adapter RE/DE pin +const uint8_t y510NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y510-B Turbidity sensor object YosemitechY510 y510(y510ModbusAddress, modbusSerial, y510AdapterPower, - y510SensorPower, y510EnablePin, y510NumberReadings); + y510SensorPower, y510EnablePin, y510NReadings); // Create turbidity and temperature variable pointers for the Y510 Variable* y510Turb = @@ -2497,17 +2668,17 @@ Variable* y510Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y511ModbusAddress = 0x1A; // The modbus address of the Y511 -const int8_t y511AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y511SensorPower = relayPowerPin; // Sensor power pin -const int8_t y511EnablePin = -1; // Adapter RE/DE pin -const uint8_t y511NumberReadings = 5; +byte y511ModbusAddress = 0x1A; // The modbus address of the Y511 +const int8_t y511AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y511SensorPower = relayPowerPin; // Sensor power pin +const int8_t y511EnablePin = -1; // Adapter RE/DE pin +const uint8_t y511NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y511-A Turbidity sensor object YosemitechY511 y511(y511ModbusAddress, modbusSerial, y511AdapterPower, - y511SensorPower, y511EnablePin, y511NumberReadings); + y511SensorPower, y511EnablePin, y511NReadings); // Create turbidity and temperature variable pointers for the Y511 Variable* y511Turb = @@ -2532,17 +2703,17 @@ Variable* y511Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y513ModbusAddress = 0x13; // The modbus address of the Y513 -const int8_t y513AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y513SensorPower = relayPowerPin; // Sensor power pin -const int8_t y513EnablePin = -1; // Adapter RE/DE pin -const uint8_t y513NumberReadings = 5; +byte y513ModbusAddress = 0x13; // The modbus address of the Y513 +const int8_t y513AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y513SensorPower = relayPowerPin; // Sensor power pin +const int8_t y513EnablePin = -1; // Adapter RE/DE pin +const uint8_t y513NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y513 Blue Green Algae (BGA) sensor object YosemitechY513 y513(y513ModbusAddress, modbusSerial, y513AdapterPower, - y513SensorPower, y513EnablePin, y513NumberReadings); + y513SensorPower, y513EnablePin, y513NReadings); // Create Blue Green Algae (BGA) concentration and temperature variable // pointers for the Y513 @@ -2567,17 +2738,17 @@ Variable* y513Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y514ModbusAddress = 0x14; // The modbus address of the Y514 -const int8_t y514AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y514SensorPower = relayPowerPin; // Sensor power pin -const int8_t y514EnablePin = -1; // Adapter RE/DE pin -const uint8_t y514NumberReadings = 5; +byte y514ModbusAddress = 0x14; // The modbus address of the Y514 +const int8_t y514AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y514SensorPower = relayPowerPin; // Sensor power pin +const int8_t y514EnablePin = -1; // Adapter RE/DE pin +const uint8_t y514NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y514 chlorophyll sensor object YosemitechY514 y514(y514ModbusAddress, modbusSerial, y514AdapterPower, - y514SensorPower, y514EnablePin, y514NumberReadings); + y514SensorPower, y514EnablePin, y514NReadings); // Create chlorophyll concentration and temperature variable pointers for the // Y514 @@ -2603,17 +2774,17 @@ Variable* y514Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y520ModbusAddress = 0x20; // The modbus address of the Y520 -const int8_t y520AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y520SensorPower = relayPowerPin; // Sensor power pin -const int8_t y520EnablePin = -1; // Adapter RE/DE pin -const uint8_t y520NumberReadings = 5; +byte y520ModbusAddress = 0x20; // The modbus address of the Y520 +const int8_t y520AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y520SensorPower = relayPowerPin; // Sensor power pin +const int8_t y520EnablePin = -1; // Adapter RE/DE pin +const uint8_t y520NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y520 conductivity sensor object YosemitechY520 y520(y520ModbusAddress, modbusSerial, y520AdapterPower, - y520SensorPower, y520EnablePin, y520NumberReadings); + y520SensorPower, y520EnablePin, y520NReadings); // Create specific conductance and temperature variable pointers for the Y520 Variable* y520Cond = @@ -2638,16 +2809,16 @@ Variable* y520Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y532ModbusAddress = 0x32; // The modbus address of the Y532 -const int8_t y532AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y532SensorPower = relayPowerPin; // Sensor power pin -const int8_t y532EnablePin = 4; // Adapter RE/DE pin -const uint8_t y532NumberReadings = 1; +byte y532ModbusAddress = 0x32; // The modbus address of the Y532 +const int8_t y532AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y532SensorPower = relayPowerPin; // Sensor power pin +const int8_t y532EnablePin = 4; // Adapter RE/DE pin +const uint8_t y532NReadings = 1; // The manufacturer actually doesn't mention averaging for this one // Create a Yosemitech Y532 pH sensor object YosemitechY532 y532(y532ModbusAddress, modbusSerial, y532AdapterPower, - y532SensorPower, y532EnablePin, y532NumberReadings); + y532SensorPower, y532EnablePin, y532NReadings); // Create pH, electrical potential, and temperature variable pointers for the // Y532 @@ -2675,16 +2846,16 @@ Variable* y532Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y533ModbusAddress = 0x32; // The modbus address of the Y533 -const int8_t y533AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y533SensorPower = relayPowerPin; // Sensor power pin -const int8_t y533EnablePin = 4; // Adapter RE/DE pin -const uint8_t y533NumberReadings = 1; +byte y533ModbusAddress = 0x32; // The modbus address of the Y533 +const int8_t y533AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y533SensorPower = relayPowerPin; // Sensor power pin +const int8_t y533EnablePin = 4; // Adapter RE/DE pin +const uint8_t y533NReadings = 1; // The manufacturer actually doesn't mention averaging for this one // Create a Yosemitech Y533 ORP sensor object YosemitechY533 y533(y533ModbusAddress, modbusSerial, y533AdapterPower, - y533SensorPower, y533EnablePin, y533NumberReadings); + y533SensorPower, y533EnablePin, y533NReadings); // Create ORP and temperature variable pointers for the Y533 Variable* y533ORP = @@ -2709,17 +2880,17 @@ Variable* y533Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y551ModbusAddress = 0x50; // The modbus address of the Y551 -const int8_t y551AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y551SensorPower = relayPowerPin; // Sensor power pin -const int8_t y551EnablePin = -1; // Adapter RE/DE pin -const uint8_t y551NumberReadings = 5; +byte y551ModbusAddress = 0x50; // The modbus address of the Y551 +const int8_t y551AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y551SensorPower = relayPowerPin; // Sensor power pin +const int8_t y551EnablePin = -1; // Adapter RE/DE pin +const uint8_t y551NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y551 chemical oxygen demand sensor object YosemitechY551 y551(y551ModbusAddress, modbusSerial, y551AdapterPower, - y551SensorPower, y551EnablePin, y551NumberReadings); + y551SensorPower, y551EnablePin, y551NReadings); // Create COD, turbidity, and temperature variable pointers for the Y551 Variable* y551COD = @@ -2749,16 +2920,16 @@ Variable* y551Temp = byte y560ModbusAddress = 0x60; // The modbus address of the Y560. // NOTE: Hexidecimal 0x60 = 96 decimal used by Yosemitech SmartPC -const int8_t y560AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y560SensorPower = relayPowerPin; // Sensor power pin -const int8_t y560EnablePin = -1; // Adapter RE/DE pin -const uint8_t y560NumberReadings = 3; +const int8_t y560AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y560SensorPower = relayPowerPin; // Sensor power pin +const int8_t y560EnablePin = -1; // Adapter RE/DE pin +const uint8_t y560NReadings = 3; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y560 Ammonium Probe object YosemitechY560 y560(y560ModbusAddress, modbusSerial, y560AdapterPower, - y560SensorPower, y560EnablePin, y560NumberReadings); + y560SensorPower, y560EnablePin, y560NReadings); // Create COD, turbidity, and temperature variable pointers for the Y560 Variable* y560NH4_N = @@ -2785,17 +2956,17 @@ Variable* y560Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y700ModbusAddress = 0x70; // The modbus address of the Y700 -const int8_t y700AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y700SensorPower = relayPowerPin; // Sensor power pin -const int8_t y700EnablePin = -1; // Adapter RE/DE pin -const uint8_t y700NumberReadings = 5; +byte y700ModbusAddress = 0x70; // The modbus address of the Y700 +const int8_t y700AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y700SensorPower = relayPowerPin; // Sensor power pin +const int8_t y700EnablePin = -1; // Adapter RE/DE pin +const uint8_t y700NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Y700 pressure sensor object YosemitechY700 y700(y700ModbusAddress, modbusSerial, y700AdapterPower, - y700SensorPower, y700EnablePin, y700NumberReadings); + y700SensorPower, y700EnablePin, y700NReadings); // Create pressure and temperature variable pointers for the Y700 Variable* y700Pres = @@ -2821,17 +2992,17 @@ Variable* y700Temp = // for Additional Serial Ports" section // NOTE: Use -1 for any pins that don't apply or aren't being used. -byte y4000ModbusAddress = 0x05; // The modbus address of the Y4000 -const int8_t y4000AdapterPower = sensorPowerPin; // RS485 adapter power pin -const int8_t y4000SensorPower = relayPowerPin; // Sensor power pin -const int8_t y4000EnablePin = -1; // Adapter RE/DE pin -const uint8_t y4000NumberReadings = 5; +byte y4000ModbusAddress = 0x05; // The modbus address of the Y4000 +const int8_t y4000AdapterPower = sensorPowerPin; // RS485 adapter power pin +const int8_t y4000SensorPower = relayPowerPin; // Sensor power pin +const int8_t y4000EnablePin = -1; // Adapter RE/DE pin +const uint8_t y4000NReadings = 5; // The manufacturer recommends averaging 10 readings, but we take 5 to minimize // power consumption // Create a Yosemitech Y4000 multi-parameter sensor object YosemitechY4000 y4000(y4000ModbusAddress, modbusSerial, y4000AdapterPower, - y4000SensorPower, y4000EnablePin, y4000NumberReadings); + y4000SensorPower, y4000EnablePin, y4000NReadings); // Create all of the variable pointers for the Y4000 Variable* y4000DO = @@ -2894,11 +3065,12 @@ Variable* dOptoTemp = // variable->getValue() float calculateVariableValue() { - float calculatedResult = -9999; // Always safest to start with a bad value + float calculatedResult = + MS_INVALID_VALUE; // Always safest to start with a bad value // float inputVar1 = variable1->getValue(); // float inputVar2 = variable2->getValue(); // make sure both inputs are good - // if (inputVar1 != -9999 && inputVar2 != -9999) { + // if (inputVar1 != MS_INVALID_VALUE && inputVar2 != MS_INVALID_VALUE) { // calculatedResult = inputVar1 + inputVar2; // } return calculatedResult; @@ -2958,7 +3130,7 @@ VariableArray varArray(variableCount, variableList); #elif defined(BUILD_TEST_SEPARATE_UUIDS) /** Start [variables_separate_uuids] */ // Version 2: Create two separate arrays, on for the variables and a separate -// one for the UUID's, then give both as input to the variable array +// one for the UUIDs, then give both as input to the variable array // constructor. Be cautious when doing this though because order is CRUCIAL! Variable* variableList[] = { new ProcessorStats_SampleNumber(&mcuBoard), @@ -2977,7 +3149,7 @@ const char* UUIDs[] = { "12345678-abcd-1234-ef00-1234567890ab", "12345678-abcd-1234-ef00-1234567890ab", "12345678-abcd-1234-ef00-1234567890ab", - // ... The number of UUID's must match the number of variables! + // ... The number of UUIDs must match the number of variables! "12345678-abcd-1234-ef00-1234567890ab", "12345678-abcd-1234-ef00-1234567890ab", "12345678-abcd-1234-ef00-1234567890ab", @@ -2985,7 +3157,7 @@ const char* UUIDs[] = { }; // Count up the number of pointers in the array int variableCount = sizeof(variableList) / sizeof(variableList[0]); -// Create the VariableArray object and attach the UUID's +// Create the VariableArray object and attach the UUIDs VariableArray varArray(variableCount, variableList, UUIDs); /** End [variables_separate_uuids] */ // ========================================================================== @@ -3095,6 +3267,9 @@ Variable* variableList[] = { #if defined(BUILD_SENSOR_TIADS1X15) ads1x15Volt, #endif +#if defined(BUILD_SENSOR_PROCESSOR_ANALOG) + processorAnalogVolts, +#endif #if defined(BUILD_SENSOR_FREESCALE_MPL115A2) mplTemp, mplPress, @@ -3157,6 +3332,12 @@ Variable* variableList[] = { ms5803Temp, ms5803Press, #endif +#if defined(BUILD_SENSOR_TE_CONNECTIVITY_MS5837) + ms5837Temp, + ms5837Press, + ms5837Depth, + ms5837Alt, +#endif #if defined(BUILD_SENSOR_DECAGON_5TM) fivetmEa, fivetmVWC, @@ -3314,14 +3495,14 @@ VariableArray varArray(variableCount, variableList); #endif -#if defined(BUILD_PUB_ENVIRO_DIY_PUBLISHER) && \ +#if defined(BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER) && \ (!defined(BUILD_MODEM_NO_MODEM) && defined(BUILD_HAS_MODEM)) // ========================================================================== -// A Publisher to Monitor My Watershed / EnviroDIY Data Sharing Portal +// A Publisher to Monitor My Watershed // ========================================================================== -/** Start [enviro_diy_publisher] */ +/** Start [monitor_my_watershed_publisher] */ // Device registration and sampling feature information can be obtained after -// registration at https://monitormywatershed.org or https://data.envirodiy.org +// registration at https://monitormywatershed.org const char* registrationToken = "12345678-abcd-1234-ef00-1234567890ab"; // Device registration token // NOTE: Because we already set the sampling feature with the logger @@ -3329,10 +3510,10 @@ const char* registrationToken = // const char* samplingFeature = "12345678-abcd-1234-ef00-1234567890ab"; // // Sampling feature UUID -// Create a data publisher for the Monitor My Watershed/EnviroDIY POST endpoint -#include -EnviroDIYPublisher EnviroDIYPost(dataLogger, registrationToken); -/** End [enviro_diy_publisher] */ +// Create a data publisher for the Monitor My Watershed POST endpoint +#include +MonitorMyWatershedPublisher MonitorMWPost(dataLogger, registrationToken); +/** End [monitor_my_watershed_publisher] */ #endif @@ -3566,7 +3747,7 @@ void greenRedFlash(uint8_t numFlash = 4, uint8_t rate = 75) { // Uses the processor sensor object to read the battery voltage // NOTE: This will actually return the battery level from the previous update! float getBatteryVoltage() { - if (mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == -9999 || + if (mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == MS_INVALID_VALUE || mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM] == 0) { mcuBoard.update(); } @@ -3581,17 +3762,21 @@ float getBatteryVoltage() { void setup() { /** Start [setup_flashing_led] */ // Blink the LEDs to show the board is on and starting up - greenRedFlash(3, 35); + greenRedFlash(3, 100); /** End [setup_flashing_led] */ + // IMMEDIATELY set up the watchdog timer for 5 minutes + // The watchdog interval will be reset in the data logger's begin() + // function. + extendedWatchDog::setupWatchDog(static_cast(5 * 60)); + /** Start [setup_wait] */ // Wait for USB connection to be established by PC // NOTE: Only use this when debugging - if not connected to a PC, this adds an // unnecessary startup delay #if defined(SERIAL_PORT_USBVIRTUAL) - while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000L)) { - // wait - } + while (!SERIAL_PORT_USBVIRTUAL && (millis() < 10000L)) { delay(10); } + greenRedFlash(3, 10); #endif /** End [setup_wait] */ @@ -3695,7 +3880,7 @@ void setup() { PRINTOUT(F("Setting logging interval to"), loggingInterval, F("minutes")); dataLogger.setLoggingInterval(loggingInterval); PRINTOUT(F("Setting number of initial 1 minute intervals to 10")); - dataLogger.setinitialShortIntervals(10); + dataLogger.setStartupMeasurements(10); // Attach the variable array to the logger PRINTOUT(F("Attaching the variable array")); dataLogger.setVariableArray(&varArray); @@ -3771,17 +3956,9 @@ void setup() { modem.modemWake(); // NOTE: This will also set up the modem // WARNING: PLEASE REMOVE AUTOBAUDING FOR PRODUCTION CODE! if (!modem.gsmModem.testAT()) { - PRINTOUT(F("Attempting autobauding..")); - uint32_t foundBaud = TinyGsmAutoBaud(modemSerial); - if (foundBaud != 0 || (modemBaud > 57600 && F_CPU == 8000000L)) { - PRINTOUT(F("Got modem response at baud of"), foundBaud, - F("Firing an attempt to change the baud rate to"), - modemBaud); - modem.gsmModem.sendAT(GF("+UART_DEF="), modemBaud, F(",8,1,0,0")); - modem.gsmModem.waitResponse(); - modemSerial.end(); - modemSerial.begin(modemBaud); - } + PRINTOUT(F("Attempting to force the modem baud rate.")); + modem.gsmModem.forceModemBaud(modemSerial, + static_cast(modemBaud)); } /** End [setup_esp] */ #endif @@ -3802,21 +3979,10 @@ void setup() { modem.modemWake(); // NOTE: This will also set up the modem // WARNING: PLEASE REMOVE AUTOBAUDING FOR PRODUCTION CODE! if (!modem.gsmModem.testAT()) { - PRINTOUT(F("Attempting autobauding..")); - uint32_t foundBaud = TinyGsmAutoBaud(modemSerial); - if (foundBaud != 0 && !(F_CPU <= 8000000L && foundBaud >= 115200) && - !(F_CPU <= 16000000L && foundBaud > 115200)) { - PRINTOUT(F("Got modem response at baud of"), foundBaud, - F("Firing an attempt to change the baud rate to"), - modemBaud); - modem.gsmModem.setBaud( - modemBaud); // Make sure we're *NOT* auto-bauding! - modem.gsmModem.waitResponse(); - modemSerial.end(); - modemSerial.begin(modemBaud); - } + PRINTOUT(F("Attempting to force the modem baud rate.")); + modem.gsmModem.forceModemBaud(modemSerial, + static_cast(modemBaud)); } - modem.gsmModem.setBaud(modemBaud); // Make sure we're *NOT* auto-bauding! modem.gsmModem.setNetworkMode(38); // set to LTE only // 2 Automatic // 13 GSM only @@ -3946,6 +4112,7 @@ void loop() { mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM], F("V) going back to sleep.")); dataLogger.systemSleep(); +#if !defined(BUILD_MODEM_NO_MODEM) && defined(BUILD_HAS_MODEM) } else if (getBatteryVoltage() < 3.55) { // At moderate voltage, log data but don't send it over the modem PRINTOUT(F("Battery at"), @@ -3959,6 +4126,16 @@ void loop() { F("V; high enough to log and publish data")); dataLogger.logDataAndPublish(); } +#else + } else { + // If the battery is good enough to log, log the data but we have no + // modem so we can't publish + PRINTOUT(F("Battery at"), + mcuBoard.sensorValues[PROCESSOR_BATTERY_VAR_NUM], + F("V; high enough to log data")); + dataLogger.logData(); + } +#endif } /** End [simple_loop] */ @@ -4102,10 +4279,12 @@ void loop() { /** End [complex_loop] */ -// cspell: ignore EDBG XBCT XBLTEB XBWF SVZM BatterymV Atlasp oversample -// cspell: ignore asco2voltage atlasGrav Hayashi emas PMID temperatureCoef -// cspell: ignore ClariVUESDI12address RainVUESDI12address Turb CTDSDI -// cspell: ignore RDOSDI TROLLSDI acculev nanolev TMSDI ELEC fivetm tallyi -// cspell: ignore kmph TIINA Chloro Fluoroscein PTSA BTEX ECpwrPin anlg spcond -// cspell: ignore Relia NEOPIXEL RESTAPI autobauding xbeec -// cspell: ignore CFUN UMNOPROF URAT PHEC +// cspell: words EDBG XBCT XBLTEB XBWF SVZM BatterymV Atlasp oversample +// cspell: words asco2voltage atlasGrav Hayashi emas PMID temperatureCoef +// cspell: words ClariVUESDI12address RainVUESDI12address Turb CTDSDI +// cspell: words RDOSDI TROLLSDI acculev nanolev TMSDI ELEC fivetm tallyi +// cspell: words kmph TIINA Chloro Fluoroscein PTSA BTEX analogECPower anlg +// cspell: words spcond Relia NEOPIXEL RESTAPI autobauding xbeec CFUN UMNOPROF +// cspell: words URAT PHEC GAIN_TWOTHIRDS anbPHEC OBSADS CTDNReadings rdoDOmgL +// cspell: words ANALOGELECCONDUCTIVITY_RSERIES_OHMS analogECADS +// cspell: words ANALOGELECCONDUCTIVITY_KONST diff --git a/examples/menu_a_la_carte/platformio.ini b/examples/menu_a_la_carte/platformio.ini index b2888d268..4950952ed 100644 --- a/examples/menu_a_la_carte/platformio.ini +++ b/examples/menu_a_la_carte/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -29,15 +29,12 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 -DENABLE_SERIAL2 -DENABLE_SERIAL3 ; -D BUILD_MODEM_DIGI_XBEE_CELLULAR_TRANSPARENT ; Turn on first time w/ a Digi LTE-M module ; -D MS_LOGGERBASE_DEBUG ; -D MS_DATAPUBLISHERBASE_DEBUG - ; -D MS_ENVIRODIYPUBLISHER_DEBUG + ; -D MS_MONITORMYWATERSHEDPUBLISHER_DEBUG lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from a tagged release of the library diff --git a/examples/simple_logging/ReadMe.md b/examples/simple_logging/ReadMe.md index 63733c728..d5fbb4529 100644 --- a/examples/simple_logging/ReadMe.md +++ b/examples/simple_logging/ReadMe.md @@ -1,10 +1,10 @@ # Simple Logging This shows the simplest use of a "logger" object. -That is, creating an array of variable objects and then creating a logger object that utilizes those variables to update all of the variable results together and save the data to a SD card. +That is, creating an array of variable objects and then creating a logger object that utilizes those variables to update all of the variable results together and save the data to an SD card. The processor then goes to sleep between readings. -This is the example you should use to deploy a logger somewhere where you don't want or have access to a way of streaming live data and you won't want to upload data to the Monitor My Watershed data portal. +This is the example you should use to deploy a logger somewhere where you don't want or have access to a way of streaming live data and you won't be uploading data to Monitor My Watershed. _______ @@ -35,16 +35,17 @@ _______ - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging/platformio.ini) file in the examples/simple_logging 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 [simple_logging.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging/simple_logging.ino) and save it to your computer. Put it into the src directory of your project. +- Open [simple_logging.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/simple_logging/simple_logging.ino) and save it to your computer. + Put 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: +- Change the text `YourLoggerID` in this section of code to your logger ID or serial number: ```cpp // Logger ID, also becomes the prefix for the name of the data file on SD card -const char *LoggerID = "XXXX"; +const char *LoggerID = "YourLoggerID"; ``` ### Upload! diff --git a/examples/simple_logging/platformio.ini b/examples/simple_logging/platformio.ini index 0b9f4778f..08f8b1067 100644 --- a/examples/simple_logging/platformio.ini +++ b/examples/simple_logging/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/examples/simple_logging/simple_logging.ino b/examples/simple_logging/simple_logging.ino index 5d47ad7c7..0db8452b9 100644 --- a/examples/simple_logging/simple_logging.ino +++ b/examples/simple_logging/simple_logging.ino @@ -31,7 +31,7 @@ // The name of this program file const char* sketchName = "simple_logging.ino"; // Logger ID, also becomes the prefix for the name of the data file on SD card -const char* LoggerID = "XXXXX"; +const char* LoggerID = "YourLoggerID"; // How frequently (in minutes) to log data const int8_t loggingInterval = 15; // Your logger's timezone. @@ -43,7 +43,7 @@ const int8_t timeZone = -5; // Eastern Standard Time 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 buttonPin = 21; // Pin for debugging mode (i.e., button pin) const int8_t wakePin = 31; // MCU interrupt/alarm pin to wake from sleep // Mayfly 0.x D31 = A7 // Set the wake pin to -1 if you do not want the main processor to sleep. @@ -57,13 +57,13 @@ const int8_t sensorPowerPin = 22; // MCU pin controlling main sensor power // ========================================================================== // Using the Processor as a Sensor // ========================================================================== -/** Start [processor_sensor] */ +/** Start [processor_stats] */ #include // Create the main processor chip "sensor" - for general metadata const char* mcuBoardVersion = "v1.1"; ProcessorStats mcuBoard(mcuBoardVersion); -/** End [processor_sensor] */ +/** End [processor_stats] */ // ========================================================================== diff --git a/examples/single_sensor/ReadMe.md b/examples/single_sensor/ReadMe.md index dc7925ac0..58fca4874 100644 --- a/examples/single_sensor/ReadMe.md +++ b/examples/single_sensor/ReadMe.md @@ -34,8 +34,9 @@ _______ - Replace the contents of the platformio.ini for your new project with the [platformio.ini](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/single_sensor/platformio.ini) file in the examples/single_sensor 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 [single_sensor.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/single_sensor/single_sensor.ino) and save it to your computer. Put it into the src directory of your project. - - Delete main.cpp in that folder. +- Open [single_sensor.ino](https://raw.githubusercontent.com/EnviroDIY/ModularSensors/master/examples/single_sensor/single_sensor.ino) and save it to your computer. +- Put it into the src directory of your project. +- Delete main.cpp in that folder. ### Upload! diff --git a/examples/single_sensor/platformio.ini b/examples/single_sensor/platformio.ini index d188380d5..2a9a7e46f 100644 --- a/examples/single_sensor/platformio.ini +++ b/examples/single_sensor/platformio.ini @@ -16,7 +16,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -29,9 +29,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 lib_deps = envirodiy/EnviroDIY_ModularSensors ; ^^ Use this when working from an official release of the library diff --git a/extras/AWS_IoT_SetCertificates/AWS_IoT_SetCertificates.ino b/extras/AWS_IoT_SetCertificates/AWS_IoT_SetCertificates.ino index 9c7df9e67..a3b0e77c5 100644 --- a/extras/AWS_IoT_SetCertificates/AWS_IoT_SetCertificates.ino +++ b/extras/AWS_IoT_SetCertificates/AWS_IoT_SetCertificates.ino @@ -47,7 +47,7 @@ // Set serial for AT commands (to the module) // Use Hardware Serial on Mega, Leonardo, Micro #ifndef __AVR_ATmega328P__ -#define SerialAT SerialBee +#define SerialAT Serial1 // or Software Serial on Uno, Nano #else diff --git a/extras/AWS_IoT_SetCertificates/ReadMe.md b/extras/AWS_IoT_SetCertificates/ReadMe.md index 341b863ad..bece8a053 100644 --- a/extras/AWS_IoT_SetCertificates/ReadMe.md +++ b/extras/AWS_IoT_SetCertificates/ReadMe.md @@ -77,12 +77,12 @@ Unfortunately, the Stonefly cannot read the certificates from your computer, so #### Set your AWS IoT Core Endpoint -In line 11, find and replace the text `YOUR_ENDPOINT-ats.iot.YOUR_REGION.amazonaws.com` with your real endpoint. +Find and replace the text `YOUR_ENDPOINT-ats.iot.YOUR_REGION.amazonaws.com` with your real endpoint. Make sure there are quotation marks around the endpoint string, as there are in the example. #### Set your Thing Name -In line 13, find and replace the text `YOUR_THING_NAME` with your assigned thing name. +Find and replace the text `YOUR_THING_NAME` with your assigned thing name. Make sure there are quotation marks around the name string, as there are in the example. #### Set your AWS IoT Core Client Certificate @@ -99,7 +99,7 @@ On Windows: - Open the file anyway. Once you have the file open, it should look like a bunch of random characters sandwiched between the lines `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----`. -Find and replace the text `paste the certificate here` in approximately line 47 of with the text of your certificate. +Find and replace the text `paste the certificate here` with the text of your certificate. Make sure that the text begins and ends with the lines `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` as it does in the example and in your certificate text. #### Set your AWS IoT Core Client Private Key @@ -108,11 +108,8 @@ From the required files mentioned above, find and open the file that *ends with* As before, you need to see the file in a text editor and you may get a security warning when you open it. Once you have the file open, it should look like a bunch of random characters sandwiched between the lines `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----`. -Find and replace the text `paste the private key here` in approximately line 71 of with the text of your certificate. -Make sure that the text begins and ends with the lines `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` as it does in the example and in your certificate text. - -> [!NOTE] -> The line number where the private key starts may change based on the length of the certificate pasted above it. +Find and replace the text `paste the private key here` with the text of your private key. +Make sure that the text begins and ends with the lines `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` as it does in the example and in your private key. ### Customize the Set Certificates program @@ -121,15 +118,15 @@ Since all of the private information went into the config file modified above, o #### Select your Modem -In lines 27-31 remove the slashes (`//`) before the modem that you want to use and add slashes to all of the others. +In the section beginning with `// Select your modem:` remove the slashes (`//`) before the modem that you want to use and add slashes to all of the others. #### Set your cellular APN or Wifi credentials -If you are using a cellular modem, in line 78, find and replace the text `hologram` with the APN for your SIM card. +If you are using a cellular modem, find and replace the text `YourAPN` with the APN for your SIM card. Make sure there are quotation marks around the APN string, as there are in the example. -If you are using a Hologram SIM, you don't need to change this. +If you are using a Hologram SIM, use the text `hologram`. -If you are using a WiFi modem, in lines 83-84 find and replace `YourWiFiSSID` and `YourWiFiPassword` with your real SSID and password. +If you are using a WiFi modem, find and replace `YourWiFiSSID` and `YourWiFiPassword` with your real SSID and password. ## Upload to your Board @@ -146,12 +143,14 @@ These are the messages you should see on the serial monitor as your program runs - After warm up, the modem will initialize and you should see the message `Initializing modem` … … `success`. - Next you will see a print out of the modem info; `Modem Info:` followed by the serial number and other info about your modem. This is just for your information. - The Stonefly will then load the CA certificate, client certificate, and key onto the modem, convert them to the modem's internal file format, and print back the loaded text so you can confirm it matches later if there are problems. -- After the certificates are loaded, the modem waits for a network connection. If the connection succeeds, you'll see the message `Waiting for network` … … `success` +- After the certificates are loaded, the modem waits for a network connection. + If the connection succeeds, you'll see the message `Waiting for network` … … `success` > [!WARNING] > The first time you connect to the a new cellular tower with a new SIM card and new modem, the connection may take a **long** time - up to several minutes. -- Now the modem waits gets a LTE internet connection. If the connection succeeds, you'll see the message `Connecting to hologram` … `success` +- Now the modem gets an LTE internet connection. + If the connection succeeds, you'll see the message `Connecting to hologram` … `success` - Once fully connected, the modem will sync its timestamp with NTP. This is required for later secured connections. - You'll see a message `=== MQTT NOT CONNECTED ===` because you haven't connected yet. - The modem will connect to MQTT, giving the message `Connecting to YOUR_ENDPOINT with client ID YOUR_THING_NAME` … `success` @@ -177,8 +176,8 @@ There are some [tips in the Read Me](https://github.com/EnviroDIY/USGS_NGWOS/?ta If you see the message `failed to initialize modem`, there's a communication problem between the modem and the Arduino. If after the failure message and a delay you do see your modem serial number or firmware version after the message `Modem Info:`, you can ignore this error: it was a baud rate problem and the Arduino adjusted. If you don't get the modem info, there's something wrong. -If your SIM card requires a user name and password to unlock (uncommon), enter those in lines 79 and 80 of the ino file and recompile and re-upload. -Confirm that your wires between the Arduino and your modem are correct and you've set the correct port for `SerialAT` in line 45. +If your SIM card requires a user name and password to unlock (uncommon), enter those in as the `gprsUser` and `gprsPass` in the GPRS credentials section of the ino file and recompile and re-upload. +Confirm that your wires between the Arduino and your modem are correct and you've set the correct port for `SerialAT` in the line `#define SerialAT Serial1`. Confirm that your modem has power and that any expected LED's are lit. ### The certificates fail to load on the modem diff --git a/extras/LTExBee_FirstConnection/LTExBee_FirstConnection.ino b/extras/LTExBee_FirstConnection/LTExBee_FirstConnection.ino index fbd762fe3..4432b5590 100644 --- a/extras/LTExBee_FirstConnection/LTExBee_FirstConnection.ino +++ b/extras/LTExBee_FirstConnection/LTExBee_FirstConnection.ino @@ -19,7 +19,7 @@ StreamDebugger debugger(Serial1, Serial); TinyGsm gsmModem(debugger); -const char* apn = "hologram"; +const char* apn = "YourAPN"; void setup() { // Set the reset pin HIGH to ensure the Bee does not continually reset diff --git a/extras/sdi12_address_change/platformio.ini b/extras/sdi12_address_change/platformio.ini index 3c0cc96a2..71c3ef7e1 100644 --- a/extras/sdi12_address_change/platformio.ini +++ b/extras/sdi12_address_change/platformio.ini @@ -17,7 +17,7 @@ monitor_speed = 115200 board = mayfly platform = atmelavr framework = arduino -lib_ldf_mode = deep+ +lib_ldf_mode = deep lib_ignore = RTCZero Adafruit NeoPixel @@ -30,9 +30,6 @@ lib_ignore = build_flags = -DSDI12_EXTERNAL_PCINT -DNEOSWSERIAL_EXTERNAL_PCINT - -DMQTT_MAX_PACKET_SIZE=240 - -DTINY_GSM_RX_BUFFER=64 - -DTINY_GSM_YIELD_MS=2 -DENABLE_SERIAL2 -DENABLE_SERIAL3 lib_deps = diff --git a/extras/sdi12_address_change/sdi12_address_change.ino b/extras/sdi12_address_change/sdi12_address_change.ino index 88f3e2271..a1b08d879 100644 --- a/extras/sdi12_address_change/sdi12_address_change.ino +++ b/extras/sdi12_address_change/sdi12_address_change.ino @@ -21,7 +21,7 @@ #define SERIAL_BAUD 115200 // The pin of the SDI-12 data bus #define DATA_PIN 7 -// The // Sensor power pin (or -1 if not switching power) +// The Sensor power pin (or -1 if not switching power) #define POWER_PIN 22 // Define the SDI-12 bus @@ -33,8 +33,7 @@ char oldAddress = '!'; // invalid address as placeholder // this checks for activity at a particular address // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' -boolean -checkActive(byte i) { // this checks for activity at a particular address +boolean checkActive(byte i) { Serial.print("Checking address "); Serial.print(static_cast(i)); Serial.print("..."); diff --git a/library.json b/library.json index 8540e2fcc..a51ea34b7 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "EnviroDIY_ModularSensors", "version": "0.37.0", - "description": "A library that allows access to multiple sensors through a unified interface. This allows the user to simply access many sensors to log the data or send the data to data repositories like the EnviroDIY data portal.", + "description": "A library that allows access to multiple sensors through a unified interface. This allows the user to simply access many sensors to log the data or send the data to data repositories like Monitor My Watershed.", "keywords": "modular, sensor, sensors, datalogger, logger, low power, sleeping, EnviroDIY, ModularSensors, Mayfly, WikiWatershed, Monitor My Watershed, ThingSpeak", "platforms": ["atmelavr", "atmelsam"], "frameworks": "arduino", @@ -154,7 +154,7 @@ "name": "Adafruit ADS1X15", "owner": "adafruit", "url": "https://github.com/adafruit/Adafruit_ADS1X15", - "version": "~2.6.0", + "version": "~2.6.2", "note": "Driver for TI's ADS1X15: 12 and 16-bit Differential or Single-Ended ADC with PGA and Comparator.", "authors": ["Adafruit"], "frameworks": "arduino" @@ -192,7 +192,7 @@ "owner": "adafruit", "library id": "19", "url": "https://github.com/adafruit/DHT-sensor-library", - "version": "~1.4.6", + "version": "~1.4.7", "note": "AOSong DHT Sensor Library by Adafruit", "authors": ["Adafruit"], "frameworks": "arduino" @@ -256,7 +256,7 @@ "owner": "milesburton", "library id": "54", "url": "https://github.com/milesburton/Arduino-Temperature-Control-Library", - "version": "~4.0.4", + "version": "~4.0.6", "note": "DallasTemperature - Arduino Library for Dallas Temperature ICs (DS18B20, DS18S20, DS1822, DS1820)", "authors": ["Guil Barros", "Miles Burton", "Rob Tillart", "Tim Nuewsome"], "frameworks": "arduino" @@ -286,6 +286,16 @@ "note": "General interface to MS5803-series pressure transducers", "authors": ["Bobby Schulz", "Andrew Wickert", "Chad Sandell", "Sara Damiano"] }, + { + "name": "MS5837", + "owner": "robtillaart", + "library id": "6205", + "url": "https://github.com/RobTillaart/MS5837", + "version": "~0.3.2", + "note": "Arduino library for the MS5837 temperature and pressure sensor and compatibles", + "authors": ["Rob Tillaart"], + "frameworks": "arduino" + }, { "name": "Tally_Library_I2C", "version": "https://github.com/EnviroDIY/Tally_Library.git#Dev_I2C", @@ -299,7 +309,7 @@ "owner": "envirodiy", "library id": "1824", "url": "https://github.com/EnviroDIY/SensorModbusMaster", - "version": "~1.6.5", + "version": "~1.7.0", "note": "EnviroDIY SensorModbusMaster - Arduino library for communicating via modbus with the Arduino acting as the modbus master.", "authors": ["Sara Damiano"], "frameworks": "arduino" @@ -309,7 +319,7 @@ "owner": "envirodiy", "library id": "5439", "url": "https://github.com/EnviroDIY/KellerModbus", - "version": "~0.2.5", + "version": "~0.2.7", "note": "Arduino library for communication with Keller pressure and water level sensors via Modbus.", "authors": ["Anthony Aufdenkampe"] }, @@ -318,7 +328,7 @@ "owner": "envirodiy", "library id": "2078", "url": "https://github.com/EnviroDIY/YosemitechModbus", - "version": "~0.5.2", + "version": "~0.5.4", "note": "Arduino library for communication with Yosemitech sensors via Modbus.", "authors": ["Sara Damiano", "Anthony Aufdenkampe"], "frameworks": "arduino" @@ -327,7 +337,7 @@ "name": "GroPointModbus", "owner": "envirodiy", "url": "https://github.com/EnviroDIY/GroPointModbus", - "version": "~0.1.3", + "version": "~0.1.5", "note": "A library to use an Arduino as a master to control and communicate via modbus with GroPoint soil moisture sensors.", "authors": ["Anthony Aufdenkampe"], "frameworks": "arduino" @@ -345,7 +355,7 @@ "name": "fast_math", "owner": "robtillaart", "url": "https://github.com/RobTillaart/fast_math", - "version": "~0.2.4", + "version": "~0.2.5", "note": "Arduino library for fast math algorithms.", "authors": ["Rob Tillaart"], "frameworks": "*", @@ -355,7 +365,7 @@ "name": "ANBSensorsModbus", "owner": "envirodiy", "url": "https://github.com/EnviroDIY/ANBSensorsModbus", - "version": "~0.2.5", + "version": "~0.4.2", "note": "A library for communicating with pH sensors manufactured by ANB Sensors using Modbus.", "authors": ["Sara Damiano"], "frameworks": "arduino" diff --git a/library.properties b/library.properties index 08e144776..2df531b72 100644 --- a/library.properties +++ b/library.properties @@ -3,9 +3,9 @@ version=0.37.0 author=Sara Damiano , Shannon Hicks maintainer=Sara Damiano sentence=A library that allows access to multiple sensors through a unified interface. -paragraph=This allows the user to simply access many sensors to log the data or send the data to data repositories like the EnviroDIY data portal. +paragraph=This allows the user to simply access many sensors to log the data or send the data to data repositories like Monitor My Watershed. category=Sensors url=https://github.com/EnviroDIY/ModularSensors architectures=avr,samd includes=LoggerBase.h -depends=EnviroDIY_DS3231, RTCZero, EnableInterrupt, SdFat, TinyGSM, PubSubClient, Adafruit BusIO, Adafruit Unified Sensor, Adafruit ADS1X15, Adafruit AM2315, Adafruit BME280 Library, DHT sensor library, Adafruit INA219, Adafruit MPL115A2, Adafruit SHT4x Library, OneWire, DallasTemperature, SDI-12, SensorModbusMaster, KellerModbus, YosemitechModbus, SparkFun Qwiic RTC RV8803 Arduino Library, StreamDebugger +depends=EnviroDIY_DS3231, RTCZero, EnableInterrupt, SdFat, TinyGSM, PubSubClient, Adafruit BusIO, Adafruit Unified Sensor, Adafruit ADS1X15, Adafruit AM2315, Adafruit BME280 Library, DHT sensor library, Adafruit INA219, Adafruit MPL115A2, Adafruit SHT4x Library, OneWire, DallasTemperature, SDI-12, SensorModbusMaster, KellerModbus, YosemitechModbus, SparkFun Qwiic RTC RV8803 Arduino Library, StreamDebugger, MS5837 diff --git a/setupGitFilters.bat b/setupGitFilters.bat new file mode 100644 index 000000000..6ae3d9bf4 --- /dev/null +++ b/setupGitFilters.bat @@ -0,0 +1,25 @@ +@echo off +REM Setup script for Git filter drivers used in ModularSensors +REM This configures the smudgePasswords and disableDebug filters referenced in .gitattributes + +echo Setting up Git filter drivers for ModularSensors... + +REM Configure the smudgePasswords filter for .ino files +echo Configuring smudgePasswords filter... +git config --local filter.smudgePasswords.clean "powershell -ExecutionPolicy RemoteSigned -File filters/cleanPasswords.ps1" +git config --local filter.smudgePasswords.smudge "powershell -ExecutionPolicy RemoteSigned -File filters/smudgePasswords.ps1" + +REM Configure the disableDebug filter for ModSensorDebugConfig.h +echo Configuring disableDebug filter... +git config --local filter.disableDebug.clean "powershell -ExecutionPolicy RemoteSigned -File filters/cleanDebugConfig.ps1" +git config --local filter.disableDebug.smudge "powershell -ExecutionPolicy RemoteSigned -File filters/smudgeDebugConfig.ps1" + +echo. +echo Git filter drivers have been successfully configured! +echo. +echo Filters configured: +echo smudgePasswords - Manages credentials in .ino files +echo disableDebug - Manages debug defines in ModSensorDebugConfig.h +echo. +echo To re-apply filters to existing files, first commit or stash local changes. +echo Then re-checkout only the affected files (for example, specific .ino files and ModSensorDebugConfig.h). diff --git a/setupGitFilters.ps1 b/setupGitFilters.ps1 new file mode 100644 index 000000000..e9f1b1b8f --- /dev/null +++ b/setupGitFilters.ps1 @@ -0,0 +1,21 @@ +#!/usr/bin/env powershell +# Setup script for Git filter drivers used in ModularSensors +# This configures the smudgePasswords and disableDebug filters referenced in .gitattributes + +Write-Host "Setting up Git filter drivers for ModularSensors..." -ForegroundColor Green + +# Configure the smudgePasswords filter for .ino files +Write-Host "Configuring smudgePasswords filter..." -ForegroundColor Yellow +git config --local filter.smudgePasswords.clean "powershell -ExecutionPolicy RemoteSigned -File filters/cleanPasswords.ps1" +git config --local filter.smudgePasswords.smudge "powershell -ExecutionPolicy RemoteSigned -File filters/smudgePasswords.ps1" + +# Configure the disableDebug filter for ModSensorDebugConfig.h +Write-Host "Configuring disableDebug filter..." -ForegroundColor Yellow +git config --local filter.disableDebug.clean "powershell -ExecutionPolicy RemoteSigned -File filters/cleanDebugConfig.ps1" +git config --local filter.disableDebug.smudge "powershell -ExecutionPolicy RemoteSigned -File filters/smudgeDebugConfig.ps1" + +Write-Host "`nGit filter drivers have been successfully configured!" -ForegroundColor Green +Write-Host "`nFilters configured:" -ForegroundColor Cyan +Write-Host " smudgePasswords - Manages credentials in .ino files" -ForegroundColor White +Write-Host " disableDebug - Manages debug defines in ModSensorDebugConfig.h" -ForegroundColor White +Write-Host "`nYou may need to run 'git checkout HEAD -- .' to apply filters to existing files." -ForegroundColor Magenta diff --git a/src/ClockSupport.cpp b/src/ClockSupport.cpp index b03d9d0e8..8b7f67534 100644 --- a/src/ClockSupport.cpp +++ b/src/ClockSupport.cpp @@ -11,7 +11,6 @@ #include "ClockSupport.h" #include "LoggerBase.h" - epochTime::epochTime(time_t timestamp, epochStart epoch) { _unixTimestamp = convert_epoch(timestamp, epoch, epochStart::unix_epoch); } @@ -181,6 +180,8 @@ const uint32_t epochTime::leapSeconds[NUMBER_LEAP_SECONDS] = LEAP_SECONDS; // Initialize the processor epoch epochStart loggerClock::_core_epoch = epochStart::y2k_epoch; +// Initialize the processor timezone offset +int32_t loggerClock::_core_tz = 0; // Initialize the static timezone int8_t loggerClock::_rtcUTCOffset = 0; @@ -210,7 +211,7 @@ RTCZero loggerClock::zero_sleep_rtc; // Sets the static offset from UTC that the RTC is programmed in -// I VERY VERY STRONGLY RECOMMEND SETTING THE RTC IN UTC (ie, offset = 0) +// I VERY VERY STRONGLY RECOMMEND SETTING THE RTC IN UTC (i.e., offset = 0) // You can either set the RTC offset directly or set the offset between the // RTC and the logger void loggerClock::setRTCOffset(int8_t offsetHours) { @@ -227,7 +228,7 @@ void loggerClock::setRTCOffset(int8_t offsetHours) { } #endif } -int8_t loggerClock::getRTCOffset(void) { +int8_t loggerClock::getRTCOffset() { return loggerClock::_rtcUTCOffset; } @@ -280,7 +281,7 @@ String loggerClock::formatDateTime_ISO8601(time_t epochSeconds, String loggerClock::formatDateTime_ISO8601(epochTime in_time, int8_t epochSecondsUTCOffset) { // Use the conversion function to get a temporary variable for the epoch - // time in the epoch used by the processor core (ie, used by gmtime). + // time in the epoch used by the processor core (i.e., used by gmtime). time_t t = epochTime::convert_epoch(in_time, loggerClock::_core_epoch); MS_DEEP_DBG(F("Input time converted to processor epoch:"), t, '(', epochTime::printEpochName(loggerClock::_core_epoch), ')'); @@ -330,7 +331,7 @@ void loggerClock::formatDateTime(char* buffer, const char* fmt, void loggerClock::formatDateTime(char* buffer, const char* fmt, epochTime in_time) { // Use the conversion function to get a temporary variable for the epoch - // time in the epoch used by the processor core (ie, used by gmtime). + // time in the epoch used by the processor core (i.e., used by gmtime). time_t t = epochTime::convert_epoch(in_time, loggerClock::_core_epoch); MS_DEEP_DBG(F("Input time converted to processor epoch:"), t, '(', epochTime::printEpochName(loggerClock::_core_epoch), ')'); @@ -382,7 +383,9 @@ bool loggerClock::setRTClock(epochTime in_time, int8_t utcOffset) { // If the RTC is already within 5 seconds of the input time, just quit if (abs(new_rtc_value - prev_rtc_value) < 5) { PRINTOUT(F("Clock already within 5 seconds of time.")); - return false; + // return true because the clock is correctly set, even if we didn't + // actually set it + return true; } MS_DEEP_DBG(F("Setting raw RTC value to:"), new_rtc_value); @@ -395,7 +398,7 @@ bool loggerClock::setRTClock(epochTime in_time, int8_t utcOffset) { } // This checks that the logger time is within a "sane" range -bool loggerClock::isRTCSane(void) { +bool loggerClock::isRTCSane() { time_t curRTC = getRawRTCNow(); bool is_sane = isEpochTimeSane(curRTC, loggerClock::_rtcUTCOffset, loggerClock::_rtcEpoch); @@ -437,7 +440,7 @@ void loggerClock::setNextRTCInterrupt(epochTime in_time, int8_t utcOffset) { resetClockInterruptStatus(); // Use the conversion function to get a temporary variable for the epoch - // time in the epoch used by the processor core (ie, used by gmtime). + // time in the epoch used by the processor core (i.e., used by gmtime). time_t t = epochTime::convert_epoch(in_time, _rtcEpoch) - static_cast(utcOffset * 3600); MS_DBG(F("Setting the next alarm on the"), MS_CLOCK_NAME, F("to"), @@ -451,7 +454,7 @@ void loggerClock::setNextRTCInterrupt(epochTime in_time, int8_t utcOffset) { #if defined(MS_USE_RV8803) // NOTE: The RV-8803 hardware does **NOT** support alarms at finer frequency - // than minutes! The alarm will fire when the minute turns (ie, at + // than minutes! The alarm will fire when the minute turns (i.e., at // hh:mm:00). To set an alarm at a specific second interval, you would have // to use a periodic countdown timer interrupt and start the interrupt timer // carefully on the second you want to match. @@ -471,7 +474,7 @@ void loggerClock::setNextRTCInterrupt(epochTime in_time, int8_t utcOffset) { rtc.enableHardwareInterrupt(ALARM_INTERRUPT); #elif defined(MS_USE_DS3231) - // MATCH_HOURS = match hours *and* minutes, seconds, ie 1x per day at set + // MATCH_HOURS = match hours *and* minutes, seconds, i.e., 1x per day at set // hh:mm:ss rtc.enableInterrupts(MATCH_HOURS, 0, tmp->tm_hour, tmp->tm_min, tmp->tm_sec); // interrupt at (h,m,s) @@ -529,7 +532,7 @@ void loggerClock::disableRTCInterrupts() { #endif } -void loggerClock::resetClockInterruptStatus(void) { +void loggerClock::resetClockInterruptStatus() { MS_DBG(F("Clearing all interrupt flags on the"), MS_CLOCK_NAME); #if defined(MS_USE_RV8803) // NOTE: We're not going to bother to call getInterruptFlag(x) to see which @@ -552,7 +555,7 @@ void loggerClock::resetClockInterruptStatus(void) { #endif } -void loggerClock::rtcISR(void) { +void loggerClock::rtcISR() { #if defined(MS_CLOCKSUPPORT_DEBUG) || defined(MS_LOGGERBASE_DEBUG_DEEP) // This is bad practice - calling a Serial.print from an ISR // But.. it's so helpful for debugging! @@ -563,8 +566,10 @@ void loggerClock::rtcISR(void) { } void loggerClock::begin() { - MS_DBG(F("Getting the epoch the processor uses for gmtime")); - loggerClock::_core_epoch = getProcessorEpochStart(); + MS_DBG(F("Getting the epoch the processor core uses for gmtime")); + getProcessorEpochStart(); // Sets _core_epoch internally + MS_DBG(F("Getting the timezone the processor core uses for mktime")); + getProcessorTimeZone(); // Sets _core_tz internally PRINTOUT(F("An"), MS_CLOCK_NAME, F("will be used as the real time clock")); MS_DBG(F("Beginning"), MS_CLOCK_NAME, F("real time clock")); rtcBegin(); @@ -579,6 +584,8 @@ void loggerClock::begin() { static_cast(static_cast(_core_epoch) - static_cast(epochStart::unix_epoch)), F("seconds")); + MS_DBG(F("The processor considers local time to be"), _core_tz, + F("seconds ("), _core_tz / 3600, F("hours) offset from UTC")); MS_DBG(F("The attached"), MS_CLOCK_NAME, F("uses a"), epochTime::printEpochName(_rtcEpoch), F("epoch internally, which starts"), @@ -612,9 +619,84 @@ epochStart loggerClock::getProcessorEpochStart() { case 1980: ret_val = epochStart::gps_epoch; break; case 1900: ret_val = epochStart::nist_epoch; break; } + loggerClock::_core_epoch = ret_val; return ret_val; } +// This is yet another awkward function, but time support varies across device +// cores and I'm not sure if there is a better way to get the timezone offset +// that the processor/core considers "local time". We need to know this because +// the mktime function converts the input time to the number of seconds since +// the epoch in the processor's timezone. The UTC version of the function +// (timegm(&timeParts)) is not available on all platforms, and I have no idea +// how to consistently set or detect the timezone across platforms, so instead +// we will just use mktime and then compare the returned timestamp to the known +// epoch start to figure out the offset. +int32_t loggerClock::getProcessorTimeZone() { + // Create a time struct for Jan 1, 2000 at 00:00:00 in the processor's epoch + tm timeParts = {}; + timeParts.tm_sec = 0; + timeParts.tm_min = 0; + timeParts.tm_hour = 0; + timeParts.tm_mday = 1; + timeParts.tm_mon = 0; /* tm_mon is 0-11 */ + timeParts.tm_year = 100; /* tm_year is since 1900 */ + timeParts.tm_wday = 0; /* day of week, will be calculated */ + timeParts.tm_yday = 0; /* day of year, will be calculated */ + timeParts.tm_isdst = 0; /* daylight saving time flag */ + time_t timeTimeT = mktime(&timeParts); + + // Check for mktime failure + if (timeTimeT == (time_t)-1) { + MS_DBG(F("mktime failed, defaulting timezone offset to 0")); + loggerClock::_core_tz = 0; + return 0; + } + + // make a epoch time from the converted time + // NOTE: Re-run getProcessorEpochStart() instead of calling _core_epoch in + // case the functions are called out of order and _core_epoch hasn't been + // set yet. + epochTime timeEpoch(timeTimeT, getProcessorEpochStart()); + // convert to Y2K epoch + time_t timeY2K = epochTime::convert_epoch(timeEpoch, epochStart::y2k_epoch); + // Since we started with Jan 1, 2000, the offset from the input time and 0 + // in the Y2K epoch can only be caused by timezone shifts within the mktime + // function. + // Handle both signed and unsigned time_t properly + // Check if time_t is signed by testing if (time_t)-1 < (time_t)0 + int32_t tz_offset; + constexpr bool is_time_t_signed = ((time_t)-1 < (time_t)0); + + if (is_time_t_signed) { + // For signed time_t, negative values are represented normally + if (timeY2K >= -static_cast(SECONDS_IN_DAY) && + timeY2K <= static_cast(SECONDS_IN_DAY)) { + tz_offset = static_cast(timeY2K); + } else { + tz_offset = 0; // Outside reasonable timezone range (±24 hours) + } + } else { + // For unsigned time_t, check for wraparound indicating negative values + if (timeY2K <= SECONDS_IN_DAY) { + // Positive offset or zero + tz_offset = static_cast(timeY2K); + } else { + // Check if this looks like a wrapped negative value + const time_t max_unsigned = (time_t)-1; + if (timeY2K > (max_unsigned - SECONDS_IN_DAY)) { + // This is likely a wrapped negative offset + time_t offsetMagnitude = max_unsigned - timeY2K + 1; + tz_offset = -static_cast(offsetMagnitude); + } else { + tz_offset = 0; // Outside reasonable timezone range + } + } + } + loggerClock::_core_tz = tz_offset; + return tz_offset; +} + inline time_t loggerClock::tsToRawRTC(time_t ts, int8_t utcOffset, epochStart epoch) { time_t tz_change = diff --git a/src/ClockSupport.h b/src/ClockSupport.h index f84e9936b..91ec46bf8 100644 --- a/src/ClockSupport.h +++ b/src/ClockSupport.h @@ -37,36 +37,18 @@ #include #include -// Where possible, use the board's built in clock -#if defined(ENVIRODIY_STONEFLY_M4) && not defined(MS_USE_RV8803) -/** - * @brief Select RV-8803 as the RTC for the EnviroDIY Stonefly. - */ -#define MS_USE_RV8803 -#undef MS_USE_DS3231 -#undef MS_USE_RTC_ZERO -#elif defined(ARDUINO_AVR_ENVIRODIY_MAYFLY) && not defined(MS_USE_DS3231) -/** - * @brief Select DS3231 as the RTC for the EnviroDIY Mayfly. - */ -#define MS_USE_DS3231 -#undef MS_USE_RV8803 -#undef MS_USE_RTC_ZERO -#elif (defined(ARDUINO_ARCH_SAMD) && !defined(__SAMD51__)) && \ - !defined(MS_USE_DS3231) && !defined(MS_USE_RV8803) && \ - !defined(MS_USE_RTC_ZERO) -/** - * @brief Select the SAMD21's internal clock (via RTC Zero) if no other RTC is - * specified. - */ -#define MS_USE_RTC_ZERO -#undef MS_USE_RV8803 -#undef MS_USE_DS3231 -#endif - -#if !defined(MS_USE_RV8803) && !defined(MS_USE_DS3231) && \ - !defined(MS_USE_RTC_ZERO) -#error Define a clock to use for the RTC for Modular Sensors! +// Validate that exactly one clock has been selected (should be set by +// KnownProcessors.h) +#if (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) > 1 +#error Multiple clocks defined! Only one of MS_USE_RV8803, MS_USE_DS3231, or MS_USE_RTC_ZERO can be selected at a time. +#elif (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) == 0 && \ + (defined(ARDUINO_ARCH_SAMD) && !defined(__SAMD51__)) +#pragma message "No clock defined! Using processor as RTC." +#elif (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) == 0 +#error No clock defined! Define exactly one of MS_USE_RV8803, MS_USE_DS3231, or MS_USE_RTC_ZERO for the RTC. Check that KnownProcessors.h is properly setting defaults for your board, or select a clock in ModSensorConfig.h for other processors. #endif /** @@ -82,16 +64,19 @@ * @brief A text description of the clock */ #if defined(MS_USE_RV8803) +// #pragma message "Using RV-8803 RTC as the clock." #define MS_CLOCK_NAME "RV-8803" #include // Interrupt is active low on the RV8803 #define CLOCK_INTERRUPT_MODE FALLING #elif defined(MS_USE_DS3231) +// #pragma message "Using DS3231 RTC as the clock." #define MS_CLOCK_NAME "DS3231" #include // Interrupt is active low on the DS3231 #define CLOCK_INTERRUPT_MODE FALLING #elif defined(MS_USE_RTC_ZERO) +// #pragma message "Using SAMD 32-bit RTC as the clock." #define MS_CLOCK_NAME "SAMD 32-bit RTC" #include #endif @@ -141,6 +126,9 @@ 599184012, 820108813, 914803214, 1025136015, 1119744016, 1167264017} #endif +/// @brief The number of seconds in a day +#define SECONDS_IN_DAY 86400L + /** * @brief Set the epoch start value. @@ -158,14 +146,14 @@ enum class epochStart : time_t { EPOCH_NIST_TO_UNIX, ///< Use a Unix epoch, starting Jan 1, 1970. ///< This is the default for this library y2k_epoch = EPOCH_NIST_TO_UNIX + - EPOCH_UNIX_TO_Y2K, ///< Use an epoch starting Jan 1, 2000, as some - ///< RTC's and Arduinos do (946684800s ahead of - ///< UNIX epoch) + EPOCH_UNIX_TO_Y2K, ///< Use an epoch starting Jan 1, 2000, as + ///< some RTC's and Arduinos do (946684800s + ///< ahead of UNIX epoch) gps_epoch = EPOCH_NIST_TO_UNIX + EPOCH_UNIX_TO_GPS, ///< Use the GPS epoch starting Jan 5, 1980 ///< (was 315964800s ahead of UNIX epoch at - ///< founding, has drifted farther apart due to - ///< leap seconds) + ///< founding, has drifted farther apart due + ///< to leap seconds) nist_epoch = 0 ///< Use the epoch starting Jan 1, 1900 as returned by ///< the NIST Network Time Protocol (RFC-1305 and later ///< versions) and Time Protocol (RFC-868) (2208988800 @@ -330,6 +318,15 @@ class epochTime { * deleted constructor. * * @todo Support half/quarter hour time zones + * + * Dealing with time is **hard**! This library only supports the bare minimum of + * what I think is necessary to get the logger's clock working and to convert + * between different epoch types. It does not support time zones (other than a + * static offset from UTC), daylight savings time, or any of the other + * complications of time. + * + * If you thought handling time was simple, read this: + * https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca */ class loggerClock { public: @@ -362,8 +359,8 @@ class loggerClock { * @brief Set the static offset in hours from UTC that the RTC is programmed * in. * - * @note I VERY, VERY STRONGLY RECOMMEND SETTING THE RTC IN UTC(ie, offset = - * 0) + * @note I VERY, VERY STRONGLY RECOMMEND SETTING THE RTC IN UTC + * (i.e., offset = 0) * * @param offsetHours The offset of the real-time clock (RTC) from UTC in * hours @@ -374,15 +371,15 @@ class loggerClock { * * @return The offset of the real-time clock (RTC) from UTC in hours */ - static int8_t getRTCOffset(void); + static int8_t getRTCOffset(); /** * @brief Get the current Universal Coordinated Time (UTC) epoch time from * the RTC. * * @param utcOffset The offset from UTC to return the epoch time in. - * @param epoch The type of epoch to use (ie, the standard for the start of - * the epoch). + * @param epoch The type of epoch to use (i.e., the standard for the start + * of the epoch). * * @return The number of seconds from the start of the given epoch. */ @@ -479,13 +476,18 @@ class loggerClock { * * @param ts The number of seconds since the start of the given epoch. * @param utcOffset The offset of the epoch time from UTC. - * @param epoch The type of epoch to use (ie, the standard for the start of - * the epoch). + * @param epoch The type of epoch to use (i.e., the standard for the start + * of the epoch). * * @return True if the input timestamp passes sanity checks **and** - * the clock has been successfully set. + * the clock is now at or within tolerance (±5 seconds) of the target time. + * This includes both cases where the clock was successfully set and where + * the clock was already within tolerance and did not need adjustment. * * @note There is no timezone correction in this function + * @note Changed behavior: Previously returned true only when clock was + * actually written. Now returns true when clock is at/within tolerance, + * regardless of whether it was written. */ static bool setRTClock(time_t ts, int8_t utcOffset, epochStart epoch); /** @@ -496,9 +498,14 @@ class loggerClock { * @param utcOffset The offset of the epoch time from UTC. * * @return True if the input timestamp passes sanity checks **and** - * the clock has been successfully set. + * the clock is now at or within tolerance (±5 seconds) of the target time. + * This includes both cases where the clock was successfully set and where + * the clock was already within tolerance and did not need adjustment. * * @note There is no timezone correction in this function + * @note Changed behavior: Previously returned true only when clock was + * actually written. Now returns true when clock is at/within tolerance, + * regardless of whether it was written. */ static bool setRTClock(epochTime in_time, int8_t utcOffset); @@ -511,7 +518,7 @@ class loggerClock { * @return True if the current time on the RTC passes sanity range * checking */ - static bool isRTCSane(void); + static bool isRTCSane(); /** * @brief Check that a given epoch time (seconds since 1970) is within a * "sane" range. @@ -522,8 +529,8 @@ class loggerClock { * @param ts The timestamp to check (in seconds since the start of the given * epoch). * @param utcOffset The offset of the epoch time from UTC in hours. - * @param epoch The type of epoch to use (ie, the standard for the start of - * the epoch). + * @param epoch The type of epoch to use (i.e., the standard for the start + * of the epoch). * @return True if the given time passes sanity range checking. */ static bool isEpochTimeSane(time_t ts, int8_t utcOffset, epochStart epoch); @@ -546,8 +553,8 @@ class loggerClock { * @param ts The timestamp for the next interrupt - in seconds from the * start of the input epoch. * @param utcOffset The offset of the epoch time from UTC in hours. - * @param epoch The type of epoch to use (ie, the standard for the start of - * the epoch). + * @param epoch The type of epoch to use (i.e., the standard for the start + * of the epoch). */ static void setNextRTCInterrupt(time_t ts, int8_t utcOffset, epochStart epoch); @@ -587,7 +594,7 @@ class loggerClock { * For some clocks, we need to reset the clock's interrupt flag so the next * interrupt will fire. */ - static void rtcISR(void); + static void rtcISR(); /** * @brief Start up the real-time clock. @@ -605,20 +612,31 @@ class loggerClock { */ static epochStart getCoreEpochStart() { return loggerClock::_core_epoch; - }; + } + /** + * @brief Get the timezone offset for the processor/Arduino core in seconds + * from UTC + * + * @return The timezone offset for the processor/Arduino core in seconds + * from UTC + */ + static int32_t getCoreTimeZone() { + return loggerClock::_core_tz; + } /** * @brief Get the epoch start for the RTC as an epochStart object * * @return The epoch start for the RTC */ static epochStart getRTCEpochStart() { - return _rtcEpoch; - }; + return loggerClock::_rtcEpoch; + } protected: /** - * @brief Figure out where the epoch starts for the processor. + * @brief Figure out what epoch start is defined for the Arduino core used + * by the processor. * * The real time clock libraries mostly document this, but the cores for the * various Arduino processors don't. The time.h file is not much more than a @@ -629,11 +647,25 @@ class loggerClock { static epochStart getProcessorEpochStart(); /** - * @brief The start of the epoch for the processor's internal time.h + * @brief Figure out the timezone offset defined for the Arduino core used + * by the processor. + * + * @return The timezone offset in seconds from UTC + */ + static int32_t getProcessorTimeZone(); + + /** + * @brief The start of the epoch for the processor core's internal time.h * library. */ static epochStart _core_epoch; + /** + * @brief The timezone used by the processor core's internal time.h library, + * in seconds from UTC. + */ + static int32_t _core_tz; + /** * @brief The static offset data of the real time clock from UTC in hours */ diff --git a/src/KnownProcessors.h b/src/KnownProcessors.h new file mode 100644 index 000000000..c575de773 --- /dev/null +++ b/src/KnownProcessors.h @@ -0,0 +1,524 @@ +/** + * @file KnownProcessors.h + * @copyright Stroud Water Research Center + * @author Sara Damiano (sdamiano@stroudcenter.org) + * + * @brief Defines for parameters of known processor types for the EnviroDIY + * ModularSensors library + */ + +// Header Guards +#ifndef SRC_KNOWN_PROCESSORS_H_ +#define SRC_KNOWN_PROCESSORS_H_ + +/** + * @def LOGGER_BOARD + * @brief Pretty text for the board name derived from the board's compiler + * define. + * + * @def OPERATING_VOLTAGE + * @brief The operating voltage of the board in volts. + * + * @def BATTERY_PIN + * @brief The analog pin of the processor tied directly to the main battery + * input and used to measure the battery voltage. + * @note The battery pin is not available on all boards and may vary by board + * version. Where the battery pin is not available, it is set to -1. Where + * it's variable, it must be fixed in the ProcessorStats module or constructor. + * + * @def BATTERY_MULTIPLIER + * @brief The multiplier to convert the raw "bits" measured on the battery pin + * to voltage. This value is based on any resistors or voltage dividers between + * the battery and the pin. + * @note The battery multiplier is not available on all boards and may vary by + * board version. Where the battery multiplier is not available, it is set to + * -1. Where it's variable, it must be fixed in the ProcessorStats module or + * constructor. + * + * @def BUILT_IN_ALS_POWER_PIN + * @brief The digital pin controlling power to the built-in ambient light sensor + * (ALS) on EnviroDIY boards. + * @note Set to -1 when the ALS is always powered. *Leave undefined* when no + * built-in ALS is available on the board. + * + * @def BUILT_IN_ALS_DATA_PIN + * @brief The analog pin connected to the built-in ambient light sensor (ALS) + * on EnviroDIY boards. + * @note The data pin varies by board model and version. *Leave undefined* when + * no built-in ALS is available on the board. + * + * @def BUILT_IN_ALS_SUPPLY_VOLTAGE + * @brief The supply voltage for the built-in ambient light sensor (ALS) on + * EnviroDIY boards, in volts. + * @note Typically matches the board's operating voltage. *Leave undefined* when + * no built-in ALS is available on the board. + * + * @def BUILT_IN_ALS_LOADING_RESISTANCE + * @brief The loading resistance for the built-in ambient light sensor (ALS) + * on EnviroDIY boards, in kΩ. + * @note The loading resistance affects light measurement calculations. *Leave + * undefined* when no built-in ALS is available on the board. + */ + + +//============================================================== +// EnviroDIY boards +//============================================================== + +// https://envirodiy.org/mayfly/ +#if defined(ARDUINO_AVR_ENVIRODIY_MAYFLY) +#define LOGGER_BOARD "EnviroDIY Mayfly" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN A6 +#define BATTERY_MULTIPLIER 4.7 // for v0.5 and later +#define BUILT_IN_ALS_POWER_PIN -1 +#define BUILT_IN_ALS_DATA_PIN A4 +#define BUILT_IN_ALS_SUPPLY_VOLTAGE 3.3 +#define BUILT_IN_ALS_LOADING_RESISTANCE 10 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// Built in DS3231 RTC +#if (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) == 0 +#define MS_USE_DS3231 +#endif + +// https://envirodiy.org/stonefly/ +#elif defined(ENVIRODIY_STONEFLY_M4) +#define LOGGER_BOARD "EnviroDIY Stonefly" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN A9 +#define BATTERY_MULTIPLIER 4.7 +#define BUILT_IN_ALS_POWER_PIN -1 +#define BUILT_IN_ALS_DATA_PIN A8 +#define BUILT_IN_ALS_SUPPLY_VOLTAGE 3.3 +#define BUILT_IN_ALS_LOADING_RESISTANCE 10 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// Built in RV-8803 RTC +#if (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) == 0 +#define MS_USE_RV8803 +#endif + + +//============================================================== +// Sodaq boards +//============================================================== + +// https://learn.sodaq.com/Boards/ExpLoRer/ (Discontinued) +#elif defined(ARDUINO_SODAQ_EXPLORER) +#define LOGGER_BOARD "SODAQ ExpLoRer" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://learn.sodaq.com/Boards/Autonomo/ (Discontinued) +#elif defined(ARDUINO_SODAQ_AUTONOMO) +#define LOGGER_BOARD "SODAQ Autonomo" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 48 // for version v0.1 +#define BATTERY_MULTIPLIER 1.47 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://learn.sodaq.com/Boards/One/base/ (Discontinued) +#elif defined(ARDUINO_SODAQ_ONE_BETA) +#define LOGGER_BOARD "SODAQ ONE Beta" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 10 +#define BATTERY_MULTIPLIER 2 // for version v0.1 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://learn.sodaq.com/Boards/One/base/ (Discontinued) +#elif defined(ARDUINO_SODAQ_ONE) +#define LOGGER_BOARD "SODAQ ONE" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 10 +#define BATTERY_MULTIPLIER 2 // for version v0.1 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://learn.sodaq.com/Boards/Mbili/ (Discontinued) +#elif defined(ARDUINO_AVR_SODAQ_MBILI) +#define LOGGER_BOARD "SODAQ Mbili" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN A6 +#define BATTERY_MULTIPLIER 1.47 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// Built in DS3231 RTC +#if (defined(MS_USE_RV8803) + defined(MS_USE_DS3231) + \ + defined(MS_USE_RTC_ZERO)) == 0 +#define MS_USE_DS3231 +#endif + +// https://support.sodaq.com/Boards/NDOGO (Discontinued) +#elif defined(ARDUINO_AVR_SODAQ_NDOGO) +#define LOGGER_BOARD "SODAQ Ndogo" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 10 +#define BATTERY_MULTIPLIER 1.47 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://support.sodaq.com/Boards/TATU (Discontinued) +#elif defined(ARDUINO_AVR_SODAQ_TATU) +#define LOGGER_BOARD "SODAQ Tatu" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://support.sodaq.com/Boards/MOJA (Discontinued) +#elif defined(ARDUINO_AVR_SODAQ_MOJA) +#define LOGGER_BOARD "SODAQ Moja" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + + +//============================================================== +// Adafruit boards +//============================================================== + +// https://www.adafruit.com/product/3458 +#elif defined(ARDUINO_AVR_FEATHER328P) +#define LOGGER_BOARD "Adafruit Feather 328p" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://www.adafruit.com/product/2771 +#elif defined(ARDUINO_AVR_FEATHER32U4) +#define LOGGER_BOARD "Adafruit Feather 32u4" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://www.adafruit.com/product/3403 +#elif defined(ARDUINO_SAMD_FEATHER_M0_EXPRESS) || \ + defined(ADAFRUIT_FEATHER_M0_EXPRESS) +#define LOGGER_BOARD "Adafruit Feather M0 Express" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://www.adafruit.com/product/2772 +#elif defined(ARDUINO_SAMD_FEATHER_M0) || defined(ADAFRUIT_FEATHER_M0) +#define LOGGER_BOARD "Adafruit Feather M0" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://www.adafruit.com/product/2796 +#elif defined(ADAFRUIT_FEATHER_M0_ADALOGGER) +#define LOGGER_BOARD "Adafruit Feather M0 Adalogger" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + +// https://www.adafruit.com/product/3857 +#elif defined(ARDUINO_FEATHER_M4) || defined(ADAFRUIT_FEATHER_M4_EXPRESS) +#define LOGGER_BOARD "Adafruit Feather M4" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// An external RTC is required + +// https://www.adafruit.com/product/4759 +#elif defined(ARDUINO_FEATHER_M4_CAN) || defined(ADAFRUIT_FEATHER_M4_CAN) +#define LOGGER_BOARD "Feather M4 CAN" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN 9 +#define BATTERY_MULTIPLIER 2 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// An external RTC is required + +// https://www.adafruit.com/product/4064 +#elif defined(ADAFRUIT_GRAND_CENTRAL_M4) +#define LOGGER_BOARD "Adafruit Grand Central" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// An external RTC is required + + +//============================================================== +// Official Arduino boards +//============================================================== + +// https://docs.arduino.cc/retired/boards/arduino-mega-adk-rev3/ (Retired) +#elif defined(ARDUINO_AVR_ADK) +#define LOGGER_BOARD "Arduino Mega ADK" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-mega/ (Retired) +#elif defined(ARDUINO_AVR_MEGA) +#define LOGGER_BOARD "Arduino Mega" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://store-usa.arduino.cc/products/arduino-mega-2560-rev3 +#elif defined(ARDUINO_AVR_MEGA2560) +#define LOGGER_BOARD "Arduino Mega 2560" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/getting-started-guides/ArduinoBT/ (Retired) +#elif defined(ARDUINO_AVR_BT) +#define LOGGER_BOARD "Arduino BT" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-duemilanove/ (Retired) +#elif defined(ARDUINO_AVR_DUEMILANOVE) +#define LOGGER_BOARD "Arduino Duemilanove" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-esplora/ (Retired) +#elif defined(ARDUINO_AVR_ESPLORA) +#define LOGGER_BOARD "Arduino Esplora" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-ethernet-rev3-without-poe/ +// (Retired) +#elif defined(ARDUINO_AVR_ETHERNET) +#define LOGGER_BOARD "Arduino Ethernet" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-fio/ (Retired) +#elif defined(ARDUINO_AVR_FIO) +#define LOGGER_BOARD "Arduino Fio" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// Arduino version: https://docs.arduino.cc/retired/boards/arduino-gemma/ +// (Retired) +// Adafruit version: https://www.adafruit.com/product/1222 +#elif defined(ARDUINO_AVR_GEMMA) +#define LOGGER_BOARD "Arduino Gemma" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/hardware/leonardo/ (Retired) +#elif defined(ARDUINO_AVR_LEONARDO) +#define LOGGER_BOARD "Arduino Leonardo" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://store-usa.arduino.cc/products/arduino-micro +#elif defined(ARDUINO_AVR_MICRO) +#define LOGGER_BOARD "Arduino Micro" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-mini-05/ (Retired) +#elif defined(ARDUINO_AVR_MINI) +#define LOGGER_BOARD "Arduino Mini 05" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://store-usa.arduino.cc/products/arduino-nano +#elif defined(ARDUINO_AVR_NANO) +#define LOGGER_BOARD "Arduino Nano" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://store-usa.arduino.cc/products/arduino-uno-rev3 +#elif defined(ARDUINO_AVR_UNO) +#define LOGGER_BOARD "Arduino Uno" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/retired/boards/arduino-yun/ (Retired) +#elif defined(ARDUINO_AVR_YUN) +#define LOGGER_BOARD "Arduino Yun" +#define OPERATING_VOLTAGE 5 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for an AVR processor +// Use log buffer size defaults +// An external RTC is required + +// https://docs.arduino.cc/hardware/zero/ +#elif defined(ARDUINO_SAMD_ZERO) +#define LOGGER_BOARD "Arduino Zero" +#define OPERATING_VOLTAGE 3.3 +#define BATTERY_PIN -1 +#define BATTERY_MULTIPLIER -1 +// Use ADC defaults for a SAMD processor +// Use log buffer size defaults +// The processor can be used as an RTC, but the user must manually select it + + +#endif + +// Default ADC settings for unknown processors +// These provide fallbacks when board-specific settings aren't available +#ifndef MS_PROCESSOR_ADC_RESOLUTION +// Fallback ADC resolution based on processor architecture +#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) +#define MS_PROCESSOR_ADC_RESOLUTION 10 +#elif defined(ARDUINO_ARCH_SAMD) +#define MS_PROCESSOR_ADC_RESOLUTION 12 +#else +#define MS_PROCESSOR_ADC_RESOLUTION 10 // Conservative default +#endif +#endif + +#ifndef MS_PROCESSOR_ADC_REFERENCE_MODE +// Fallback ADC reference mode based on processor architecture +#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) +#define MS_PROCESSOR_ADC_REFERENCE_MODE DEFAULT +#elif defined(ARDUINO_ARCH_SAMD) +#define MS_PROCESSOR_ADC_REFERENCE_MODE AR_DEFAULT +#else +#define MS_PROCESSOR_ADC_REFERENCE_MODE DEFAULT // Conservative default +#endif +#endif + +#ifndef MS_LOG_DATA_BUFFER_SIZE +// Fallback log buffer size based on processor type +#if defined(__SAMD51__) +#define MS_LOG_DATA_BUFFER_SIZE 8192 +#elif defined(ARDUINO_ARCH_SAMD) +#define MS_LOG_DATA_BUFFER_SIZE 4096 +#elif defined(__AVR_ATmega1284P__) +#define MS_LOG_DATA_BUFFER_SIZE 1024 // 1284p has good memory +#elif defined(__AVR_ATmega2560__) +#define MS_LOG_DATA_BUFFER_SIZE 512 // Mega has moderate memory +#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) +#define MS_LOG_DATA_BUFFER_SIZE 256 // 328p has limited memory +#else +#define MS_LOG_DATA_BUFFER_SIZE 1024 // Conservative default +#endif +#endif + +// Print warnings if expected processor defines are missing +#ifndef LOGGER_BOARD +#define LOGGER_BOARD "Unknown" +#pragma message "Warning: LOGGER_BOARD is not defined for this processor.\n" \ + "The board name can be added by editing KnownProcessors.h." +#endif +#ifndef OPERATING_VOLTAGE +#define OPERATING_VOLTAGE 3.3 +#pragma message \ + "Warning: OPERATING_VOLTAGE is not defined for this processor.\n" \ + "If you have specified the operating voltage in your code, you can ignore this message.\n" \ + "The operating voltage can be added by editing KnownProcessors.h." +#endif +#ifndef BATTERY_PIN +#define BATTERY_PIN -1 +#pragma message \ + "Warning: BATTERY_PIN is not defined for this processor.\n" \ + "If your processor does not have a built-in pin for measuring the battery voltage, " \ + "or you have specified a different pin in your code, you can ignore this message.\n" \ + "The battery pin can be added by editing KnownProcessors.h." +#endif + +#ifndef BATTERY_MULTIPLIER +#define BATTERY_MULTIPLIER -1 +#pragma message \ + "Warning: BATTERY_MULTIPLIER is not defined for this processor.\n" \ + "If your processor does not have a built-in pin for measuring the battery voltage, " \ + "or you have specified the multiplier in your code, you can ignore this message.\n" \ + "The battery multiplier can be added by editing KnownProcessors.h." +#endif + +#endif + +// cSpell:words Tatu Moja Adalogger Duemilanove Esplora diff --git a/src/LogBuffer.cpp b/src/LogBuffer.cpp index ca4eab926..3db071f81 100644 --- a/src/LogBuffer.cpp +++ b/src/LogBuffer.cpp @@ -14,8 +14,6 @@ // Constructor LogBuffer::LogBuffer() {} -// Destructor -LogBuffer::~LogBuffer() {} void LogBuffer::setNumVariables(uint8_t numVariables_) { // each record is one uint32_t to hold the timestamp, plus N floats to hold @@ -27,7 +25,7 @@ void LogBuffer::setNumVariables(uint8_t numVariables_) { clear(); } -void LogBuffer::clear(void) { +void LogBuffer::clear() { // clear out the buffer numRecords = 0; dataBufferTail = 0; @@ -35,19 +33,21 @@ void LogBuffer::clear(void) { _bufferOverflow = false; } -uint8_t LogBuffer::getNumVariables(void) { +uint8_t LogBuffer::getNumVariables() { return numVariables; } -int LogBuffer::getNumRecords(void) { +int LogBuffer::getNumRecords() { return numRecords; } -uint8_t LogBuffer::getPercentFull(void) { - uint32_t bytesFull = (uint32_t)numRecords * (uint32_t)recordSize; - uint32_t bytesTotal = MS_LOG_DATA_BUFFER_SIZE; - - return (uint8_t)((bytesFull * (uint32_t)100) / bytesTotal); +uint8_t LogBuffer::getPercentFull() { + uint32_t bytesFull = static_cast(numRecords) * + static_cast(recordSize); + uint32_t percent = (bytesFull * static_cast(100)) / + MS_LOG_DATA_BUFFER_SIZE; + // Cap the result at 100% to handle potential buffer overflow scenarios + return static_cast(percent > 100 ? 100 : percent); } int LogBuffer::addRecord(uint32_t timestamp) { diff --git a/src/LogBuffer.h b/src/LogBuffer.h index 49e7035f7..79f94ee7d 100644 --- a/src/LogBuffer.h +++ b/src/LogBuffer.h @@ -13,28 +13,9 @@ #ifndef SRC_LOGBUFFER_H_ #define SRC_LOGBUFFER_H_ -#ifndef MS_LOG_DATA_BUFFER_SIZE -#ifdef ARDUINO_AVR_MEGA2560 -#define MS_LOG_DATA_BUFFER_SIZE 512 -#elif defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) -#define MS_LOG_DATA_BUFFER_SIZE 256 -#elif defined(__AVR_ATmega1284P__) -#define MS_LOG_DATA_BUFFER_SIZE 2048 -#else -/** - * @brief Log Data Buffer - * - * This determines how much RAM is reserved to buffer log records before - * transmission. Each record consumes 4 bytes for the timestamp plus 4 bytes - * for each logged variable. Increasing this value too far can crash the - * device! The number of log records buffered is controlled by sendEveryX. - * - * This can be changed by setting the build flag MS_LOG_DATA_BUFFER_SIZE when - * compiling. 8192 bytes is a safe value for the Mayfly 1.1 with six variables. - */ -#define MS_LOG_DATA_BUFFER_SIZE 8192 -#endif -#endif +// Include ModSensorConfig.h which defines MS_LOG_DATA_BUFFER_SIZE +// (set automatically in KnownProcessors.h for supported boards) +#include "ModSensorConfig.h" #include #include @@ -54,7 +35,7 @@ class LogBuffer { /** * @brief Destroys the buffer. */ - virtual ~LogBuffer(); + virtual ~LogBuffer() = default; /** * @brief Sets the number of variables the buffer will store in each record. @@ -69,26 +50,26 @@ class LogBuffer { * * @return The variable count. */ - uint8_t getNumVariables(void); + uint8_t getNumVariables(); /** * @brief Clears all records from the log. */ - void clear(void); + void clear(); /** * @brief Gets the number of records currently in the log. * * @return The number of records. */ - int getNumRecords(void); + int getNumRecords(); /** * @brief Computes the percentage full of the buffer. * * @return The current percent full. */ - uint8_t getPercentFull(void); + uint8_t getPercentFull(); /** * @brief Adds a new record with the given timestamp. diff --git a/src/LoggerBase.cpp b/src/LoggerBase.cpp index a1e1e6b01..906cada55 100644 --- a/src/LoggerBase.cpp +++ b/src/LoggerBase.cpp @@ -178,8 +178,6 @@ Logger::Logger() { // Set a datetime callback for automatic time-stamping of files by SdFat SdFile::dateTimeCallback(fileDateTimeCallback); } -// Destructor -Logger::~Logger() {} // ===================================================================== // @@ -197,9 +195,9 @@ void Logger::setLoggingInterval(int16_t loggingIntervalMinutes) { } -// Sets the number of initial short intervals -void Logger::setinitialShortIntervals(int16_t initialShortIntervals) { - _remainingShortIntervals = initialShortIntervals; +// Sets the number of startup measurements +void Logger::setStartupMeasurements(int16_t startupMeasurements) { + _startupMeasurements = startupMeasurements; } @@ -518,7 +516,7 @@ loggerModem* Logger::registerDataPublisher(dataPublisher* publisher) { return _logModem; } -bool Logger::checkRemotesConnectionNeeded(void) { +bool Logger::checkRemotesConnectionNeeded() { MS_DBG(F("Asking publishers if they need a connection.")); bool needed = false; @@ -543,7 +541,7 @@ void Logger::publishDataToRemotes(bool forceFlush) { } } } -void Logger::sendDataToRemotes(void) { +void Logger::sendDataToRemotes() { publishDataToRemotes(); } void Logger::publishMetadataToRemotes() { @@ -579,7 +577,7 @@ void Logger::setLoggerTimeZone(int8_t timeZone) { } #endif } -int8_t Logger::getLoggerTimeZone(void) { +int8_t Logger::getLoggerTimeZone() { return Logger::_loggerUTCOffset; } // Sets the static timezone that the RTC is programmed in @@ -589,7 +587,7 @@ int8_t Logger::getLoggerTimeZone(void) { void Logger::setRTCTimeZone(int8_t timeZone) { loggerClock::setRTCOffset(timeZone); } -int8_t Logger::getRTCTimeZone(void) { +int8_t Logger::getRTCTimeZone() { return loggerClock::getRTCOffset(); } @@ -615,7 +613,7 @@ void Logger::setTZOffset(int8_t offset) { F("hours behind the logging timezone")); } } -int8_t Logger::getTZOffset(void) { +int8_t Logger::getTZOffset() { return Logger::_loggerRTCOffset; } time_t Logger::getNowLocalEpoch() { @@ -647,19 +645,19 @@ void Logger::formatDateTime(char* buffer, const char* fmt, Logger::_loggerEpoch); } // This checks that the logger time is within a "sane" range -bool Logger::isRTCSane(void) { +bool Logger::isRTCSane() { return loggerClock::isRTCSane(); } // This sets static variables for the date/time - this is needed so that all -// data outputs (SD, EnviroDIY, serial printing, etc) print the same time +// data outputs (SD, publishers, serial printing, etc) print the same time // for updating the sensors - even though the routines to update the sensors // and to output the data may take several seconds. // It is not currently possible to output the instantaneous time an individual // sensor was updated, just a single marked time. By custom, this should be // called before updating the sensors, not after. -void Logger::markTime(void) { +void Logger::markTime() { MS_DEEP_DBG(F("Marking time...")); Logger::markedUTCUnixTime = getNowUTCEpoch(); Logger::markedLocalUnixTime = markedUTCUnixTime + @@ -669,11 +667,11 @@ void Logger::markTime(void) { // This checks to see if the CURRENT time is an even interval of the logging // rate -bool Logger::checkInterval(void) { +bool Logger::checkInterval() { bool retval; uint32_t checkTime = static_cast(getNowLocalEpoch()); int16_t interval = _loggingIntervalMinutes; - if (_remainingShortIntervals > 0) { + if (_startupMeasurements > 0) { // log the first few samples at an interval of 1 minute so that // operation can be quickly verified in the field interval = 1; @@ -686,7 +684,7 @@ bool Logger::checkInterval(void) { #if MS_LOGGERBASE_BUTTON_BENCH_TEST == 0 // If the person has set the button pin **NOT** to be used for "bench - // testing" (ie, immediate rapid logging) then we instead read this button + // testing" (i.e., immediate rapid logging) then we instead read this button // testing flag to mean "log now." To make that happen, we mark the time // here and return true if the flag is set. bool testing = Logger::startTesting; @@ -705,15 +703,15 @@ bool Logger::checkInterval(void) { static_cast(Logger::markedLocalUnixTime)); MS_DBG(F("Time to log!")); #if MS_LOGGERBASE_BUTTON_BENCH_TEST == 0 - if ((_remainingShortIntervals > 0) && (!testing)) { + if ((_startupMeasurements > 0) && (!testing)) { #else - if ((_remainingShortIntervals > 0)) { + if ((_startupMeasurements > 0)) { #endif - MS_DBG(F("Within initial 1-minute intervals; "), - _remainingShortIntervals, F("left.")); + MS_DBG(F("Within initial 1-minute measurements; "), + _startupMeasurements, F("left.")); // once we've marked the time, we need to decrement the remaining - // short intervals by one. (IFF not in "log now" testing mode.) - _remainingShortIntervals -= 1; + // startup measurements by one. (IFF not in "log now" testing mode.) + _startupMeasurements -= 1; } retval = true; } else { @@ -725,11 +723,11 @@ bool Logger::checkInterval(void) { // This checks to see if the MARKED time is an even interval of the logging rate -bool Logger::checkMarkedInterval(void) { +bool Logger::checkMarkedInterval() { int16_t interval = _loggingIntervalMinutes; - // If we're within the range of our initial short intervals, we're logging, + // If we're within the range of our startup measurements, we're logging, // then set the interval to 1. - if (_remainingShortIntervals > 0) { interval = 1; } + if (_startupMeasurements > 0) { interval = 1; } bool retval; MS_DBG( @@ -741,11 +739,11 @@ bool Logger::checkMarkedInterval(void) { if (Logger::markedLocalUnixTime != 0 && (Logger::markedLocalUnixTime % (interval * 60) == 0)) { MS_DBG(F("Time to log!")); - // De-increment the number of short intervals after marking - if (_remainingShortIntervals > 0) { - MS_DBG(F("Within initial 1-minute intervals. There are "), - _remainingShortIntervals, F("left.")); - _remainingShortIntervals -= 1; + // Decrement the number of startup measurements after marking + if (_startupMeasurements > 0) { + MS_DBG(F("Within startup measurements. There are "), + _startupMeasurements, F("left.")); + _startupMeasurements -= 1; } retval = true; } else { @@ -764,14 +762,14 @@ bool Logger::checkMarkedInterval(void) { // In this case, we're doing nothing, we just want the processor to wake // This must be a static function (which means it can only call other static // functions.) -void Logger::wakeISR(void) { +void Logger::wakeISR() { MS_DEEP_DBG(F("\nInterrupt on wake pin!")); } // Puts the system to sleep to conserve battery life. // This DOES NOT sleep or wake the sensors!! -void Logger::systemSleep(void) { +void Logger::systemSleep() { MS_DBG(F("\n\nEntering system sleep function. ZZzzz...")); #if !defined(MS_USE_RTC_ZERO) @@ -820,10 +818,6 @@ void Logger::systemSleep(void) { digitalWrite(SCL, LOW); #endif - // Disable the watch-dog timer - MS_DEEP_DBG(F("Disabling the watchdog")); - extendedWatchDog::disableWatchDog(); - #if defined(ARDUINO_ARCH_SAMD) // force all pins to minimum power draw levels (tri-state) @@ -978,6 +972,12 @@ void Logger::systemSleep(void) { SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; #endif + // Disable the watch-dog timer - the very last thing before sleeping just in + // case anything else goes wrong in between. + // NOTE: Because this is last, we can't print a message after disabling the + // watch-dog. + extendedWatchDog::disableWatchDog(); + __DSB(); // Data sync barrier - to ensure outgoing memory accesses // complete __WFI(); // wait for interrupt @@ -1027,7 +1027,7 @@ void Logger::systemSleep(void) { sleep_bod_disable(); #endif - // disable all power-reduction modules (ie, the processor module clocks) + // disable all power-reduction modules (i.e., the processor module clocks) // NOTE: This only shuts down the various clocks on the processor via // the power reduction register! It does NOT actually disable the // modules themselves or set the pins to any particular state! This @@ -1059,6 +1059,12 @@ void Logger::systemSleep(void) { MS_2ND_OUTPUT.flush(); #endif + // Disable the watch-dog timer - the very last thing before sleeping just in + // case anything else goes wrong in between. + // NOTE: Because this is last, we can't print a message after disabling the + // watch-dog. + extendedWatchDog::disableWatchDog(); + // Actually put the processor into sleep mode. // This must happen after the SE bit is set. sleep_cpu(); @@ -1070,10 +1076,13 @@ void Logger::systemSleep(void) { // --------------------------------------------------------------------- // -- The portion below this happens on wake up, after any wake ISR's -- + // Re-enable the watch-dog timer right away! + extendedWatchDog::enableWatchDog(); + #if defined(ENVIRODIY_STONEFLY_M4) pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, - HIGH); // turn off the built-in LED if using a Stonefly M4 + HIGH); // turn on the built-in LED if using a Stonefly M4 #endif #if defined(ARDUINO_ARCH_SAMD) @@ -1102,6 +1111,14 @@ void Logger::systemSleep(void) { // ^^ Restarts the bus, including re-attaching the NVIC interrupts USBDevice.attach(); // ^^ USB->DEVICE.CTRLB.bit.DETACH = 0; enables USB interrupts +#if (defined(MS_LOGGERBASE_DEBUG_DEEP) || defined(MS_LOGGERBASE_DEBUG)) && \ + defined(SERIAL_PORT_USBVIRTUAL) + // if debugging is enabled, wait for the USB port to connect + uint32_t startSerialWait = millis(); + SERIAL_PORT_USBVIRTUAL.begin(0); // baud rate is ignored on USB + while (!SERIAL_PORT_USBVIRTUAL && (millis() - startSerialWait < 250)); + MS_DEEP_DBG(F("USBDevice reattached")); +#endif #endif // USE_TINYUSB #endif // ARDUINO_ARCH_SAMD @@ -1111,7 +1128,7 @@ void Logger::systemSleep(void) { // to the processor registers noInterrupts(); - // Re-enable all power modules (ie, the processor module clocks) + // Re-enable all power modules (i.e., the processor module clocks) // NOTE: This only re-enables the various clocks on the processor! // The modules may need to be re-initialized after the clocks re-start. power_all_enable(); @@ -1127,10 +1144,6 @@ void Logger::systemSleep(void) { #endif - // Re-enable the watch-dog timer - MS_DEEP_DBG(F("Re-enabling the watchdog")); - extendedWatchDog::enableWatchDog(); - // Re-start the I2C interface MS_DEEP_DBG(F("Restarting I2C")); // The Wire.begin() function will set the propper pin modes for SCL and SDA @@ -1226,7 +1239,7 @@ String Logger::generateFileName(bool include_time, const char* extension, // This generates a file name from the logger id and the current date // This will be used if the setFileName function is not called before the // begin() function is called. -void Logger::generateAutoFileName(void) { +void Logger::generateAutoFileName() { // Generate the file name from logger ID and date auto fileName = generateFileName(false, ".csv"); setFileName(fileName); @@ -1260,8 +1273,8 @@ void Logger::printFileHeader(Stream* stream) { stream->print(F("Data Logger File: ")); stream->println(_fileName); - // Adding the sampling feature UUID (only applies to EnviroDIY logger) - if (strlen(_samplingFeatureUUID) > 1) { + // Adding the sampling feature UUID + if (_samplingFeatureUUID != nullptr && strlen(_samplingFeatureUUID) > 1) { stream->print(F("Sampling Feature UUID: ")); stream->print(_samplingFeatureUUID); stream->println(','); @@ -1274,12 +1287,15 @@ void Logger::printFileHeader(Stream* stream) { // Next comes the ODM2 unit name STREAM_CSV_ROW(F("Result Unit:"), getVarUnitAtI(i)) // Next comes the variable UUIDs - // We'll only add UUID's if we see a UUID for the first variable - if (getVarUUIDAtI(0) != nullptr && strlen(getVarUUIDAtI(0)) > 1) { - STREAM_CSV_ROW(F("Result UUID:"), getVarUUIDAtI(i)) - } - - // We'll finish up the the custom variable codes + /// @todo Currrently the file header will always have a UUID row, but it + /// will be blank if the user doesn't set any UUIDs. Versions 0.37.0 and + /// prior only printed the row if the **first** UUID existed. It might be + /// better to only print the UUID row if at least one variable has a UUID + /// even if that single variable with a UUID isn't the first one. + STREAM_CSV_ROW(F("Result UUID:"), + getVarUUIDAtI(i) != nullptr ? getVarUUIDAtI(i) : "") + + // We'll finish up with the custom variable codes String dtRowHeader = F("Date and Time in UTC"); if (_loggerUTCOffset > 0) { dtRowHeader += '+' + _loggerUTCOffset; @@ -1292,7 +1308,7 @@ void Logger::printFileHeader(Stream* stream) { // This prints a comma separated list of values of sensor data - including the // time - out over an Arduino stream -void Logger::printSensorDataCSV(Stream* stream) { +void Logger::printVariableValuesCSV(Stream* stream) { String csvString = ""; String iso8601 = formatDateTime_ISO8601(Logger::markedLocalUnixTime); iso8601.replace("T", " "); @@ -1306,8 +1322,13 @@ void Logger::printSensorDataCSV(Stream* stream) { stream->println(); } +// Backward compatibility wrapper +void Logger::printSensorDataCSV(Stream* stream) { + printVariableValuesCSV(stream); +} + // Protected helper function - This checks if the SD card is available and ready -bool Logger::initializeSDCard(void) { +bool Logger::initializeSDCard() { // If we don't know the slave select of the sd card, we can't use it if (_SDCardSSPin < 0) { PRINTOUT(F("Slave/Chip select pin for SD card has not been set.")); @@ -1450,7 +1471,7 @@ bool Logger::openFile(String& filename, bool createFile, // These functions create a file on the SD card with the given filename and // set the proper timestamps to the file. // The filename may either be the one set by -// setFileName(String)/setFileName(void) or can be specified in the function. If +// setFileName(String)/setFileName() or can be specified in the function. If // specified, it will also write a header to the file based on the sensors in // the group. This can be used to force a logger to create a file with a // secondary file name. @@ -1475,7 +1496,7 @@ bool Logger::createLogFile(bool writeDefaultHeader) { // These functions write a file on the SD card with the given filename and // set the proper timestamps to the file. // The filename may either be the one set by -// setFileName(String)/setFileName(void) or can be specified in the function. If +// setFileName(String)/setFileName() or can be specified in the function. If // the file does not already exist, the file will be created. This can be used // to force a logger to write to a file with a secondary file name. bool Logger::logToSD(String& filename, String& rec) { @@ -1508,7 +1529,7 @@ bool Logger::logToSD(String& rec) { } // NOTE: This is structured differently than the version with a string input // record. This is to avoid the creation/passing of very long strings. -bool Logger::logToSD(void) { +bool Logger::logToSD() { // Get a new file name if the name is blank if (_fileName == "") generateAutoFileName(); @@ -1525,13 +1546,13 @@ bool Logger::logToSD(void) { } // Write the data - printSensorDataCSV(&logFile); + printVariableValuesCSV(&logFile); // Echo the line to the serial port #if !defined(MS_SILENT) PRINTOUT(F("\n \\/---- Line Saved to SD Card ----\\/")); - printSensorDataCSV(&MS_OUTPUT); + printVariableValuesCSV(&MS_OUTPUT); #if defined(MS_2ND_OUTPUT) - printSensorDataCSV(&MS_2ND_OUTPUT); + printVariableValuesCSV(&MS_2ND_OUTPUT); #endif PRINTOUT('\n'); #endif @@ -1604,18 +1625,18 @@ void Logger::benchTestingMode(bool sleepBeforeReturning) { extendedWatchDog::resetWatchDog(); // Update the values from all attached sensors - // NOTE: NOT using complete update because we want the sensors to be - // left on between iterations in testing mode. - _internalArray->updateAllSensors(); + // NOTE: Use completeUpdate with all flags false so sensors stay + // powered and awake between iterations in testing mode. + _internalArray->completeUpdate(false, false, false, false); // Print out the current logger time PRINTOUT(F("Current logger time is"), formatDateTime_ISO8601(getNowLocalEpoch())); PRINTOUT(F("-----------------------")); // Print out the sensor data #if !defined(MS_SILENT) - _internalArray->printSensorData(&MS_OUTPUT); + _internalArray->printVariableData(&MS_OUTPUT); #if defined(MS_2ND_OUTPUT) - _internalArray->printSensorData(&MS_2ND_OUTPUT); + _internalArray->printVariableData(&MS_2ND_OUTPUT); #endif #endif PRINTOUT(F("-----------------------")); @@ -1838,9 +1859,9 @@ void Logger::logData(bool sleepBeforeReturning) { // Print out the sensor data #if !defined(MS_SILENT) PRINTOUT(" "); - _internalArray->printSensorData(&MS_OUTPUT); + _internalArray->printVariableData(&MS_OUTPUT); #if defined(MS_2ND_OUTPUT) - _internalArray->printSensorData(&MS_2ND_OUTPUT); + _internalArray->printVariableData(&MS_2ND_OUTPUT); #endif PRINTOUT(" "); #endif @@ -1910,9 +1931,9 @@ void Logger::logDataAndPublish(bool sleepBeforeReturning) { // Print out the sensor data #if !defined(MS_SILENT) PRINTOUT(" "); - _internalArray->printSensorData(&MS_OUTPUT); + _internalArray->printVariableData(&MS_OUTPUT); #if defined(MS_2ND_OUTPUT) - _internalArray->printSensorData(&MS_2ND_OUTPUT); + _internalArray->printVariableData(&MS_2ND_OUTPUT); #endif PRINTOUT(" "); #endif diff --git a/src/LoggerBase.h b/src/LoggerBase.h index 0222b9d53..4441fde99 100644 --- a/src/LoggerBase.h +++ b/src/LoggerBase.h @@ -178,7 +178,7 @@ class Logger { /** * @brief Destroy the Logger object - takes no action. */ - virtual ~Logger(); + virtual ~Logger() = default; // ===================================================================== // /** @@ -223,25 +223,41 @@ class Logger { } /** - * @brief Set the number of initial datapoints to log (and publish) at - * 1-minute intervals before beginning logging on the regular logging - * interval. + * @brief Set the number of startup measurements to take at 1-minute + * intervals before beginning logging on the regular logging interval. * - * @param initialShortIntervals The number of 1-minute intervals. This - * number of transmissions will be performed with an interval of 1 minute - * regardless of the programmed interval. Useful for fast field - * verification. + * Whether the data is published or not depends on the settings of the data + * publishers. + * + * @param startupMeasurements The number of measurements to take at + * 1-minute intervals regardless of the programmed interval. Useful for + * fast field verification. */ - void setinitialShortIntervals(int16_t initialShortIntervals); + void setStartupMeasurements(int16_t startupMeasurements); /** - * @brief Get the number of 1-minute intervals at the start before logging - * on the regular logging interval + * @brief Get the remaining number of startup measurements * - * @return The number of 1-minute intervals at the start before logging - * on the regular logging interval + * @return The remaining number of startup measurements + */ + int16_t getStartupMeasurements() { + return _startupMeasurements; + } + // Backwards-compatibility shims + /** + * @brief Deprecated alias for setStartupMeasurements + * @param initialShortIntervals The number of startup measurements + * @m_deprecated_since{0,38,0} use setStartupMeasurements + */ + void setinitialShortIntervals(int16_t initialShortIntervals) { + setStartupMeasurements(initialShortIntervals); + } + /** + * @brief Deprecated alias for getStartupMeasurements + * @return The remaining number of startup measurements + * @m_deprecated_since{0,38,0} use getStartupMeasurements */ int16_t getinitialShortIntervals() { - return _remainingShortIntervals; + return getStartupMeasurements(); } /** @@ -388,9 +404,9 @@ class Logger { * Because this sets the pin mode, this function should only be called * during the `setup()` or `loop()` portion of an Arduino program. * - * Once in testing mode, the logger will attempt to connect the the internet + * Once in testing mode, the logger will attempt to connect to the internet * and take 25 measurements spaced at 5 second intervals writing the results - * to the main output destination (ie, Serial). Testing mode cannot be + * to the main output destination (i.e., Serial). Testing mode cannot be * entered while the logger is taking a scheduled measurement. No data is * written to the SD card in testing mode. * @@ -463,10 +479,10 @@ class Logger { */ int16_t _loggingIntervalMinutes = 5; /** - * @brief The initial number of samples to log at an interval of 1 minute + * @brief The number of startup measurements to log at 1-minute intervals * for fast field verification */ - int8_t _remainingShortIntervals = 5; + int16_t _startupMeasurements = 5; /** * @brief Digital pin number on the mcu controlling the SD card slave * select. @@ -736,7 +752,7 @@ class Logger { * * @return True if any remotes need a connection. */ - bool checkRemotesConnectionNeeded(void); + bool checkRemotesConnectionNeeded(); /** * @brief Publish data to all registered data publishers. * @@ -749,7 +765,7 @@ class Logger { * * @m_deprecated_since{0,22,5} */ - void sendDataToRemotes(void); + void sendDataToRemotes(); /** * @brief Publish **metadata** to all registered data publishers. * @@ -801,7 +817,7 @@ class Logger { * @return The timezone data is be saved to the SD card in. This * is not be the same as the timezone of the real time clock. */ - static int8_t getLoggerTimeZone(void); + static int8_t getLoggerTimeZone(); /** * @brief A passthrough to loggerClock::setRTCOffset(int8_t offsetHours); @@ -826,7 +842,7 @@ class Logger { * * @return The offset of the real-time clock (RTC) from UTC in hours */ - static int8_t getRTCTimeZone(void); + static int8_t getRTCTimeZone(); /** * @brief Set the offset between the built-in clock and the time zone * where the data is being recorded. @@ -846,7 +862,7 @@ class Logger { * @return The offset between the built-in clock and the time * zone where the data is being recorded. */ - static int8_t getTZOffset(void); + static int8_t getTZOffset(); /** * @brief Get the current epoch time from the RTC and correct it to the @@ -953,19 +969,19 @@ class Logger { * @return True if the current time on the RTC passes sanity range * checking */ - static bool isRTCSane(void); + static bool isRTCSane(); /** * @brief Set static variables for the date/time * - * This is needed so that all data outputs (SD, EnviroDIY, serial printing, + * This is needed so that all data outputs (SD, publishers, serial printing, * etc) print the same time for updating the sensors - even though the * routines to update the sensors and to output the data may take several * seconds. It is not currently possible to output the instantaneous time * an individual sensor was updated, just a single marked time. By custom, * this should be called before updating the sensors, not after. */ - static void markTime(void); + static void markTime(); /** * @brief Check if the CURRENT time is an even interval of the logging rate @@ -973,21 +989,21 @@ class Logger { * @return True if the current time on the RTC is an even interval * of the logging rate. */ - bool checkInterval(void); + bool checkInterval(); /** * @brief Check if the MARKED time is an even interval of the logging rate - * That is the value saved in the static variable markedLocalUnixTime. * * This should be used in conjunction with markTime() to ensure that all - * data outputs from a single data update session (SD, EnviroDIY, serial + * data outputs from a single data update session (SD, publishers, serial * printing, etc) have the same timestamp even though the update routine may * take several (or many) seconds. * * @return True if the marked time is an even interval of the * logging rate. */ - bool checkMarkedInterval(void); + bool checkMarkedInterval(); protected: /** @@ -1102,7 +1118,7 @@ class Logger { * * Use loggerClock::rtcISR() in new code! */ - static void wakeISR(void); + static void wakeISR(); /** * @brief Put the mcu to sleep to conserve battery life and handle @@ -1112,10 +1128,10 @@ class Logger { * * @warning During sleep, the I2C/Wire interface is disabled and the SCL and * SDA pins are forced low to save power. Any attempt to communicate with an - * I2C device during sleep (ie, thorough an interrupt) will cause the board - * to hang indefinitely. + * I2C device during sleep (i.e., through an interrupt) will cause the + * board to hang indefinitely. */ - void systemSleep(void); + void systemSleep(); #if defined(ARDUINO_ARCH_SAMD) public: @@ -1216,7 +1232,7 @@ class Logger { * * @return The name of the file data is currently being saved to. */ - String getFileName(void) { + String getFileName() { return _fileName; } @@ -1232,12 +1248,22 @@ class Logger { virtual void printFileHeader(Stream* stream); /** - * @brief Print a comma separated list of values of sensor data - - * including the time in the logging timezone - out over an Arduino stream + * @brief Print a comma separated list of the values of the variables in the + * underlying variable array - along with the logged time in the logger's + * timezone - out over an Arduino stream * * @param stream An Arduino stream instance - expected to be an SdFat file - * but could also be the "main" Serial port for debugging. */ + void printVariableValuesCSV(Stream* stream); + + /** + * @brief Print a comma separated list of values of variable data - + * including the time in the logging timezone - out over an Arduino stream + * + * @m_deprecated_since{0,38,0} Use printVariableValuesCSV() instead. + * @param stream An Arduino stream instance + */ void printSensorDataCSV(Stream* stream); /** @@ -1248,7 +1274,7 @@ class Logger { * * @return True if the SD card is ready */ - bool initializeSDCard(void); + bool initializeSDCard(); /** * @brief Create a file on the SD card and set the created, modified, and @@ -1319,7 +1345,7 @@ class Logger { * @return True if the file was successfully accessed or created * _and_ data appended to it. */ - bool logToSD(void); + bool logToSD(); /** * @brief Generate a file name with the current date and time appended to @@ -1357,7 +1383,7 @@ class Logger { * * @note This cannot be called until *after* the RTC is started */ - void generateAutoFileName(void); + void generateAutoFileName(); /** * @brief This function is used to automatically mark files as @@ -1411,7 +1437,7 @@ class Logger { * @brief The interrupt service routine called when an interrupt is detected * on the pin assigned for "testing" mode. */ - static void testingISR(void); + static void testingISR(); /** * @brief Execute bench testing mode if enabled. @@ -1423,7 +1449,7 @@ class Logger { * to the internet. It then powers up all sensors tied to variable in the * internal variable array. The logger then updates readings from all * sensors 25 times with a 5 second wait in between. All results are output - * to the "main" output - ie Serial - and NOT to the SD card. After 25 + * to the "main" output - i.e., Serial - and NOT to the SD card. After 25 * measurements, the sensors are put to sleep, the modem is disconnected * from the internet, and the logger goes back to sleep. * diff --git a/src/LoggerModem.cpp b/src/LoggerModem.cpp index 225f33b16..b2fc32a8b 100644 --- a/src/LoggerModem.cpp +++ b/src/LoggerModem.cpp @@ -12,12 +12,12 @@ #include "ClockSupport.h" // Initialize the static members -int16_t loggerModem::_priorRSSI = -9999; -int16_t loggerModem::_priorSignalPercent = -9999; -float loggerModem::_priorModemTemp = -9999; -float loggerModem::_priorBatteryState = -9999; -float loggerModem::_priorBatteryPercent = -9999; -float loggerModem::_priorBatteryVoltage = -9999; +int16_t loggerModem::_priorRSSI = MS_INVALID_VALUE; +int16_t loggerModem::_priorSignalPercent = MS_INVALID_VALUE; +float loggerModem::_priorModemTemp = MS_INVALID_VALUE; +float loggerModem::_priorBatteryState = MS_INVALID_VALUE; +float loggerModem::_priorBatteryPercent = MS_INVALID_VALUE; +float loggerModem::_priorBatteryVoltage = MS_INVALID_VALUE; // Constructor loggerModem::loggerModem(int8_t powerPin, int8_t statusPin, bool statusLevel, @@ -43,9 +43,6 @@ loggerModem::loggerModem(int8_t powerPin, int8_t statusPin, bool statusLevel, _max_at_response_time_ms(max_at_response_time_ms), _modemName("unspecified modem") {} -// Destructor -loggerModem::~loggerModem() {} - void loggerModem::setModemLED(int8_t modemLEDPin) { _modemLEDPin = modemLEDPin; @@ -54,22 +51,22 @@ void loggerModem::setModemLED(int8_t modemLEDPin) { digitalWrite(_modemLEDPin, LOW); } } -void loggerModem::modemLEDOn(void) { +void loggerModem::modemLEDOn() { if (_modemLEDPin >= 0) { pinMode(_modemLEDPin, OUTPUT); digitalWrite(_modemLEDPin, HIGH); } } -void loggerModem::modemLEDOff(void) { +void loggerModem::modemLEDOff() { if (_modemLEDPin >= 0) { digitalWrite(_modemLEDPin, LOW); } } -String loggerModem::getModemName(void) { +String loggerModem::getModemName() { return _modemName; } // @todo Implement this for all modems -String loggerModem::getModemDevId(void) { +String loggerModem::getModemDevId() { return _modemName + F(" Sn ") + _modemSerialNumber + F(" HwVer ") + _modemHwVersion + F(" FwVer ") + _modemFwVersion; } @@ -78,7 +75,7 @@ void loggerModem::setModemTimeZone(int8_t timeZone) { _modemUTCOffset = timeZone; } -void loggerModem::modemPowerUp(void) { +void loggerModem::modemPowerUp() { if (_powerPin >= 0) { if (_modemSleepRqPin >= 0) { // For most modules, the sleep pin should be held high during power @@ -102,7 +99,7 @@ void loggerModem::modemPowerUp(void) { } } -void loggerModem::modemPowerDown(void) { +void loggerModem::modemPowerDown() { if (_powerPin >= 0) { MS_DBG(F("Turning off power to"), getModemName(), F("with pin"), _powerPin); @@ -115,7 +112,7 @@ void loggerModem::modemPowerDown(void) { } } -bool loggerModem::modemSetup(void) { +bool loggerModem::modemSetup() { // NOTE: Set flag FIRST to stop infinite loop between modemSetup() and // modemWake() bool success = true; @@ -180,7 +177,7 @@ bool loggerModem::modemSetup(void) { } // Nicely put the modem to sleep and power down -bool loggerModem::modemSleep(void) { +bool loggerModem::modemSleep() { bool success = true; MS_DBG(F("Putting"), getModemName(), F("to sleep.")); @@ -189,7 +186,7 @@ bool loggerModem::modemSleep(void) { // process of turning on and thus status pin isn't valid yet. In that case, // we wouldn't yet know it's coming on and so we'd mistakenly assume it's // already off and not turn it back off. This only applies to modules with a - // pulse wake (ie, non-zero wake time). For all modules that do pulse on, + // pulse wake (i.e., non-zero wake time). For all modules that do pulse on, // where possible I've selected a pulse time that is sufficient to wake but // not quite long enough to put it to sleep and am using AT commands to // sleep. This *should* keep everything lined up. @@ -206,7 +203,7 @@ bool loggerModem::modemSleep(void) { } // Nicely put the modem to sleep and power down -bool loggerModem::modemSleepPowerDown(void) { +bool loggerModem::modemSleepPowerDown() { bool success = true; uint32_t start = millis(); MS_DBG(F("Turning"), getModemName(), F("off.")); @@ -259,7 +256,7 @@ bool loggerModem::modemSleepPowerDown(void) { } // Perform a hard/panic reset for when the modem is completely unresponsive -bool loggerModem::modemHardReset(void) { +bool loggerModem::modemHardReset() { if (_modemResetPin >= 0) { MS_DBG(F("Doing a hard reset on the modem by setting pin"), _modemResetPin, _resetLevel ? F("HIGH") : F("LOW"), F("for"), @@ -284,7 +281,7 @@ void loggerModem::setModemResetLevel(bool level) { } -void loggerModem::setModemPinModes(void) { +void loggerModem::setModemPinModes() { // Set-up pin modes if (_statusPin >= 0) { MS_DEEP_DBG(F("Initializing pin"), _statusPin, @@ -326,20 +323,20 @@ void loggerModem::setMetadataPolling(uint8_t pollingBitmask) { } -bool loggerModem::updateModemMetadata(void) { +bool loggerModem::updateModemMetadata() { bool success = true; // Unset whatever we had previously - loggerModem::_priorRSSI = -9999; - loggerModem::_priorSignalPercent = -9999; - loggerModem::_priorBatteryState = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorModemTemp = -9999; + loggerModem::_priorRSSI = MS_INVALID_VALUE; + loggerModem::_priorSignalPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryState = MS_INVALID_VALUE; + loggerModem::_priorBatteryPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryVoltage = MS_INVALID_VALUE; + loggerModem::_priorModemTemp = MS_INVALID_VALUE; // Initialize variable - int16_t rssi = -9999; - int16_t percent = -9999; + int16_t rssi = MS_INVALID_VALUE; + int16_t percent = MS_INVALID_VALUE; int8_t state = 99; int8_t bpercent = -99; int16_t volt = 9999; @@ -356,9 +353,9 @@ bool loggerModem::updateModemMetadata(void) { success &= getModemSignalQuality(rssi, percent); loggerModem::_priorRSSI = rssi; loggerModem::_priorSignalPercent = percent; - if (rssi != 0 && rssi != -9999) break; + if (rssi != 0 && rssi != MS_INVALID_VALUE) break; delay(250); - } while ((rssi == 0 || rssi == -9999) && + } while ((rssi == 0 || rssi == MS_INVALID_VALUE) && millis() - startMillis < 15000L && success); MS_DBG(F("CURRENT RSSI:"), rssi); MS_DBG(F("CURRENT Percent signal strength:"), percent); @@ -379,17 +376,20 @@ bool loggerModem::updateModemMetadata(void) { if (state != 99) loggerModem::_priorBatteryState = static_cast(state); else - loggerModem::_priorBatteryState = static_cast(-9999); + loggerModem::_priorBatteryState = + static_cast(MS_INVALID_VALUE); if (bpercent != -99) loggerModem::_priorBatteryPercent = static_cast(bpercent); else - loggerModem::_priorBatteryPercent = static_cast(-9999); + loggerModem::_priorBatteryPercent = + static_cast(MS_INVALID_VALUE); if (volt != 9999) loggerModem::_priorBatteryVoltage = static_cast(volt); else - loggerModem::_priorBatteryVoltage = static_cast(-9999); + loggerModem::_priorBatteryVoltage = + static_cast(MS_INVALID_VALUE); } else { MS_DBG(F("Polling for all modem battery parameters is disabled")); } diff --git a/src/LoggerModem.h b/src/LoggerModem.h index 7eee5d6d6..affeb0587 100644 --- a/src/LoggerModem.h +++ b/src/LoggerModem.h @@ -72,7 +72,7 @@ */ /**@{*/ /** - * @brief Decimals places in string representation; RSSI should have 0. + * @brief Decimal places in string representation; RSSI should have 0. * * RSSI is a rough calculation, so it has 0 decimal place resolution */ @@ -100,7 +100,7 @@ */ /**@{*/ /** - * @brief Decimals places in string representation; percent signal should have + * @brief Decimal places in string representation; percent signal should have * 0. * * Percent signal is a rough calculation, so it has 0 decimal place resolution @@ -134,7 +134,7 @@ */ /**@{*/ /** - * @brief Decimals places in string representation; battery state should have 0. + * @brief Decimal places in string representation; battery state should have 0. * * Battery state is a code value; it has 0 decimal place resolution */ @@ -166,7 +166,7 @@ * {{ @ref Modem_BatteryPercent::Modem_BatteryPercent }} */ /**@{*/ -/// @brief Decimals places in string representation; battery charge percent +/// @brief Decimal places in string representation; battery charge percent /// should have 0. #define MODEM_BATTERY_PERCENT_RESOLUTION 0 /// @brief The bit mask for loggerModem::_pollModemMetaData to enable modem @@ -196,7 +196,7 @@ */ /**@{*/ /** - * @brief Decimals places in string representation; battery voltage should have + * @brief Decimal places in string representation; battery voltage should have * 0. * * No supported module has higher than 1mV resolution in battery reading. @@ -228,7 +228,7 @@ */ /**@{*/ /** - * @brief Decimals places in string representation; temperature should + * @brief Decimal places in string representation; temperature should * have 1. * * Most modules that can measure temperature measure to 0.1°C @@ -260,7 +260,7 @@ * {{ @ref Modem_ActivationDuration::Modem_ActivationDuration }} */ /**@{*/ -/// @brief Decimals places in string representation; total active time should +/// @brief Decimal places in string representation; total active time should /// have 3. #define MODEM_ACTIVATION_RESOLUTION 3 /// @brief The bit mask for loggerModem::_pollModemMetaData to enable modem @@ -287,7 +287,7 @@ * {{ @ref Modem_PoweredDuration::Modem_PoweredDuration }} */ /**@{*/ -/// @brief Decimals places in string representation; total powered time should +/// @brief Decimal places in string representation; total powered time should /// have 3. #define MODEM_POWERED_RESOLUTION 3 /// @brief The bit mask for loggerModem::_pollModemMetaData to enable modem @@ -357,7 +357,7 @@ class loggerModem { /** * @brief Destroy the logger Modem object - no action taken. */ - virtual ~loggerModem(); + virtual ~loggerModem() = default; /** * @brief Set an LED to turn on (pin will be `HIGH`) when the modem is on. @@ -371,7 +371,7 @@ class loggerModem { * * @return The modem name */ - String getModemName(void); + String getModemName(); /** * @brief Get a detailed printable description of the modem. @@ -383,7 +383,7 @@ class loggerModem { * * @todo Implement this for modems other than the XBee WiFi */ - String getModemDevId(void); + String getModemDevId(); /** * @brief Set the timezone that the modem will attempt to sync itself to. * @@ -408,7 +408,7 @@ class loggerModem { * * @return True if setup was successful */ - virtual bool modemSetup(void); + virtual bool modemSetup(); /** * @brief Retained for backwards compatibility; use modemSetup() in new * code. @@ -417,7 +417,7 @@ class loggerModem { * * @return True if setup was successful */ - bool setup(void) { + bool setup() { return modemSetup(); } @@ -440,7 +440,7 @@ class loggerModem { * * @return True if the modem is responsive and ready for action. */ - virtual bool modemWake(void) = 0; + virtual bool modemWake() = 0; /** * @brief Retained for backwards compatibility; use modemWake() in new code. * @@ -449,14 +449,14 @@ class loggerModem { * @return True if wake was successful, modem should be ready to * communicate */ - bool wake(void) { + bool wake() { return modemWake(); } /** * @brief Power the modem by setting the modem power pin high. */ - virtual void modemPowerUp(void); + virtual void modemPowerUp(); /** * @brief Cut power to the modem by setting the modem power pin low. * @@ -464,14 +464,14 @@ class loggerModem { * allows for graceful shut down. You should use modemSleepPowerDown() * whenever possible. */ - virtual void modemPowerDown(void); + virtual void modemPowerDown(); /** * @brief Request that the modem enter its lowest possible power state. * * @return True if the modem has successfully entered low power * state */ - virtual bool modemSleep(void); + virtual bool modemSleep(); /** * @brief Request that the modem enter its lowest possible power state and * then set the power pin low after the modem has indicated it has @@ -483,7 +483,7 @@ class loggerModem { * @return True if the modem has successfully entered low power * state _and_ then powered off */ - virtual bool modemSleepPowerDown(void); + virtual bool modemSleepPowerDown(); /**@}*/ /** @@ -497,13 +497,13 @@ class loggerModem { * reset failed to fix the communication issue or because a reset is not * possible with the current pin/modem configuration. */ - virtual bool modemHardReset(void); + virtual bool modemHardReset(); /** * @anchor modem_pin_functions * @name Pin setting functions - * Functions to set or re-set the the pin numbers for the connection between + * Functions to set or re-set the pin numbers for the connection between * the modem module and the logger MCU. */ /**@{*/ @@ -567,7 +567,7 @@ class loggerModem { * @brief Detach from EPS or GPRS data connection and then deregister from * the cellular network. */ - virtual void disconnectInternet(void) = 0; + virtual void disconnectInternet() = 0; /** * @brief Create a new client object using the default socket number @@ -606,9 +606,9 @@ class loggerModem { * * @return A new secure client object */ - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) = 0; + virtual Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) = 0; /** * @brief Create a new secure client object using the default socket number * @@ -618,9 +618,9 @@ class loggerModem { * * @return A new secure client object */ - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) = 0; + virtual Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) = 0; /** * @brief Attempts to delete a created TinyGsmClient object. We need to do * this to close memory leaks from the create client because we can't delete @@ -650,7 +650,7 @@ class loggerModem { * * @return The number of seconds since Jan 1, 1970 IN UTC */ - virtual uint32_t getNISTTime(void) = 0; + virtual uint32_t getNISTTime() = 0; /**@}*/ @@ -702,7 +702,7 @@ class loggerModem { * * @return The temperature in degrees Celsius */ - virtual float getModemChipTemperature(void) = 0; + virtual float getModemChipTemperature() = 0; /** @@ -756,7 +756,7 @@ class loggerModem { * was successful and the values of the internal static variables should * be valid. */ - virtual bool updateModemMetadata(void); + virtual bool updateModemMetadata(); /**@}*/ /** @@ -867,16 +867,16 @@ class loggerModem { /** * @brief Turn on the modem LED/alert pin - sets it `HIGH` */ - void modemLEDOn(void); + void modemLEDOn(); /** * @brief Turn off the modem LED/alert pin - sets it `LOW` */ - void modemLEDOff(void); + void modemLEDOff(); /** * @brief Set the processor pin modes (input vs output, with and without * pull-up) for all pins connected between the modem module and the mcu. */ - virtual void setModemPinModes(void); + virtual void setModemPinModes(); /**@}*/ /** @@ -890,7 +890,7 @@ class loggerModem { * @return True if there is an active data connection to the * internet. */ - virtual bool isInternetAvailable(void) = 0; + virtual bool isInternetAvailable() = 0; /** * @brief Perform the parts of the modem sleep process that are unique to a * specific module, as opposed to the parts of setup that are common to all @@ -899,7 +899,7 @@ class loggerModem { * @return True if the unique part of the sleep function ran * successfully. */ - virtual bool modemSleepFxn(void) = 0; + virtual bool modemSleepFxn() = 0; /** * @brief Perform the parts of the modem wake up process that are unique to * a specific module, as opposed to the parts of setup that are common to @@ -908,7 +908,7 @@ class loggerModem { * @return True if the unique part of the wake function ran * successfully - does _NOT_ indicate that the modem is now responsive. */ - virtual bool modemWakeFxn(void) = 0; + virtual bool modemWakeFxn() = 0; /** * @brief Perform the parts of the modem set up process that are unique to a * specific module, as opposed to the parts of setup that are common to all @@ -919,7 +919,7 @@ class loggerModem { * * @return True if the extra setup succeeded. */ - virtual bool extraModemSetup(void) = 0; + virtual bool extraModemSetup() = 0; /** * @brief Check if the modem was awake using all possible means. * @@ -933,7 +933,7 @@ class loggerModem { * * @note It's possible that the status pin is on, but the modem is actually * mid-shutdown. In that case, we'll mistakenly skip re-waking it. This - * only applies to modules with a pulse wake (ie, non-zero wake time). For + * only applies to modules with a pulse wake (i.e., non-zero wake time). For * all modules that do pulse on, where possible I've selected a pulse time * that is sufficient to wake but not quite long enough to put it to sleep * and am using AT commands to sleep. This *should* keep everything lined @@ -941,7 +941,7 @@ class loggerModem { * * @return True if the modem is already awake. */ - virtual bool isModemAwake(void) = 0; + virtual bool isModemAwake() = 0; /**@}*/ /** @@ -988,7 +988,7 @@ class loggerModem { */ bool _statusLevel; /** - * @brief The digital pin number of the pin on the mcu attached the the hard + * @brief The digital pin number of the pin on the mcu attached to the hard * or panic reset pin of the modem. * * Should be set to a negative number if the modem reset pin is not @@ -1225,8 +1225,6 @@ class loggerModem { uint8_t _pollModemMetaData = 0; }; -// typedef float (loggerModem::_*loggerGetValueFxn)(void); - // Classes for the modem variables /** @@ -1249,7 +1247,7 @@ class Modem_RSSI : public Variable { */ explicit Modem_RSSI(loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_RSSI_DEFAULT_CODE) - : Variable(&parentModem->getModemRSSI, (uint8_t)MODEM_RSSI_RESOLUTION, + : Variable(&parentModem->getModemRSSI, MODEM_RSSI_RESOLUTION, &*MODEM_RSSI_VAR_NAME, &*MODEM_RSSI_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling(MODEM_RSSI_ENABLE_BITMASK); @@ -1257,7 +1255,7 @@ class Modem_RSSI : public Variable { /** * @brief Destroy the Modem_RSSI object - no action needed. */ - ~Modem_RSSI() {} + ~Modem_RSSI() override = default; }; @@ -1284,7 +1282,7 @@ class Modem_SignalPercent : public Variable { loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_PERCENT_SIGNAL_DEFAULT_CODE) : Variable(&parentModem->getModemSignalPercent, - (uint8_t)MODEM_PERCENT_SIGNAL_RESOLUTION, + MODEM_PERCENT_SIGNAL_RESOLUTION, &*MODEM_PERCENT_SIGNAL_VAR_NAME, &*MODEM_PERCENT_SIGNAL_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling(MODEM_PERCENT_SIGNAL_ENABLE_BITMASK); @@ -1292,7 +1290,7 @@ class Modem_SignalPercent : public Variable { /** * @brief Destroy the Modem_SignalPercent object - no action needed. */ - ~Modem_SignalPercent() {} + ~Modem_SignalPercent() override = default; }; @@ -1322,7 +1320,7 @@ class Modem_BatteryState : public Variable { loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_BATTERY_STATE_DEFAULT_CODE) : Variable(&parentModem->getModemBatteryChargeState, - (uint8_t)MODEM_BATTERY_STATE_RESOLUTION, + MODEM_BATTERY_STATE_RESOLUTION, &*MODEM_BATTERY_STATE_VAR_NAME, &*MODEM_BATTERY_STATE_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling(MODEM_BATTERY_STATE_ENABLE_BITMASK); @@ -1330,7 +1328,7 @@ class Modem_BatteryState : public Variable { /** * @brief Destroy the Modem_BatteryState object - no action needed. */ - ~Modem_BatteryState() {} + ~Modem_BatteryState() override = default; }; @@ -1360,7 +1358,7 @@ class Modem_BatteryPercent : public Variable { loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_BATTERY_PERCENT_DEFAULT_CODE) : Variable(&parentModem->getModemBatteryChargePercent, - (uint8_t)MODEM_BATTERY_PERCENT_RESOLUTION, + MODEM_BATTERY_PERCENT_RESOLUTION, &*MODEM_BATTERY_PERCENT_VAR_NAME, &*MODEM_BATTERY_PERCENT_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling( @@ -1369,7 +1367,7 @@ class Modem_BatteryPercent : public Variable { /** * @brief Destroy the Modem_BatteryPercent object - no action needed. */ - ~Modem_BatteryPercent() {} + ~Modem_BatteryPercent() override = default; }; @@ -1399,7 +1397,7 @@ class Modem_BatteryVoltage : public Variable { loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_BATTERY_VOLTAGE_DEFAULT_CODE) : Variable(&parentModem->getModemBatteryVoltage, - (uint8_t)MODEM_BATTERY_VOLTAGE_RESOLUTION, + MODEM_BATTERY_VOLTAGE_RESOLUTION, &*MODEM_BATTERY_VOLTAGE_VAR_NAME, &*MODEM_BATTERY_VOLTAGE_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling( @@ -1408,7 +1406,7 @@ class Modem_BatteryVoltage : public Variable { /** * @brief Destroy the Modem_BatteryVoltage object - no action needed. */ - ~Modem_BatteryVoltage() {} + ~Modem_BatteryVoltage() override = default; }; @@ -1437,15 +1435,14 @@ class Modem_Temp : public Variable { explicit Modem_Temp(loggerModem* parentModem, const char* uuid = "", const char* varCode = MODEM_TEMPERATURE_DEFAULT_CODE) : Variable(&parentModem->getModemTemperature, - (uint8_t)MODEM_TEMPERATURE_RESOLUTION, - &*MODEM_TEMPERATURE_VAR_NAME, &*MODEM_TEMPERATURE_UNIT_NAME, - varCode, uuid) { + MODEM_TEMPERATURE_RESOLUTION, &*MODEM_TEMPERATURE_VAR_NAME, + &*MODEM_TEMPERATURE_UNIT_NAME, varCode, uuid) { parentModem->enableMetadataPolling(MODEM_TEMPERATURE_ENABLE_BITMASK); } /** * @brief Destroy the Modem_Temp object - no action needed. */ - ~Modem_Temp() {} + ~Modem_Temp() override = default; }; #endif // SRC_LOGGERMODEM_H_ diff --git a/src/ModSensorConfig.h b/src/ModSensorConfig.h index 5484a612c..b1cc37b2c 100644 --- a/src/ModSensorConfig.h +++ b/src/ModSensorConfig.h @@ -6,7 +6,13 @@ * @author Sara Geleskie Damiano * * @brief This file is used to configure the clock and other library - * settings/preferences for the Modular Sensors Library. + * settings/preferences for the Modular Sensors Library and some of the + * underlying libraries. + * + * @note This file must be included in every header file in the library - before + * including anything else - to ensure that the settings are applied + * consistently across all files and that compile time settings trickle down to + * the underlying libraries. * * For the Arduino IDE, this is the only way to configure these settings. If you * are using PlatformIO, you have the option of using this file or changing your @@ -23,138 +29,174 @@ // Include Arduino.h to ensure variants file is pulled first #include +// Include the known processors for default values +#include "KnownProcessors.h" + //============================================================== // Select the Real Time Clock to use by uncommenting one of the options below. -// NOTE: This is optional for a EnviroDIY Mayfly or Stonefly but **REQUIRED** -// for all other boards! +// NOTE: For supported boards (EnviroDIY Mayfly, Stonefly, SAMD21 boards), +// appropriate RTC defaults are automatically selected in KnownProcessors.h. +// Only uncomment these options to OVERRIDE the automatic selection. +// For unsupported boards, you MUST uncomment one option. // #define MS_USE_RV8803 // #define MS_USE_DS3231 // #define MS_USE_RTC_ZERO //============================================================== -//============================================================== -// Select ADS1015 instead of the ADS1115, if desired -// This is for sensors that use the external ADC for analog voltage -// measurements. -// #define MS_USE_ADS1015 -//============================================================== //============================================================== -// Disable concurrent polling of SDI-12 sensors, if needed -// Concurrent measurement support was introduced in version 1.2 (April 12, 1996) -// of the specification and all sensors that claim to use version 1.2 or higher -// must support it. -// That being said.. some new sensors are fussy and will incorrectly abort -// concurrent measurements due to noise or other sensors on the bus. -// NOTE: By default, concurrent measurements are used for all SDI-12 sensors! -// NOTE: This single setting applies to all SDI-12 sensors; it cannot be set on -// a per-sensor basis. -// #define MS_SDI12_NON_CONCURRENT - -// Disable CRC checking for SDI-12 sensors, if needed -// This may be necessary if using older or fussy sensors -// CRC support in SDI-12 was implemented with version 1.3 (April 7, 2000) and -// all sensors that claim to use version 1.3 or higher must support it. -// NOTE: By default, CRCs are used for all SDI-12 sensors! -// NOTE: This single setting applies to all SDI-12 sensors; it cannot be set on -// a per-sensor basis. -// #define MS_SDI12_NO_CRC_CHECK +// Time-stamp configurations //============================================================== +#if !defined(MS_LOGGER_EPOCH) || defined(DOXYGEN) +/** + * @def MS_LOGGER_EPOCH + * @brief The epoch start to use for the logger + */ +#define MS_LOGGER_EPOCH epochStart::unix_epoch +#endif -//============================================================== -#ifndef MS_LOGGERBASE_BUTTON_BENCH_TEST +#if !defined(EARLIEST_SANE_UNIX_TIMESTAMP) || defined(DOXYGEN) /** - * @brief Enable bench testing mode for the testing button. + * @def EARLIEST_SANE_UNIX_TIMESTAMP + * @brief The earliest unix timestamp that can be considered sane. * - * When enabled, the testing button uses the benchTestingMode() function to - * repeatedly read and print out sensor data. When disabled (default), the - * testing button causes a reading to be taken and transmitted immediately - * using the normal procedure. + * January 1, 2025 */ -#define MS_LOGGERBASE_BUTTON_BENCH_TEST false +#define EARLIEST_SANE_UNIX_TIMESTAMP 1735689600 #endif -//============================================================== - -//============================================================== -// SPI Configuration, iff needed -//============================================================== -#if !defined(SDCARD_SPI) +#if !defined(LATEST_SANE_UNIX_TIMESTAMP) || defined(DOXYGEN) /** - * @brief The SPI port to use for the SD card. + * @def LATEST_SANE_UNIX_TIMESTAMP + * @brief The latest unix timestamp that can be considered sane. * - * This is typically defined in the board variant file. + * January 1, 2035 */ -#define SDCARD_SPI SPI +#define LATEST_SANE_UNIX_TIMESTAMP 2051222400 #endif +// Static assert to validate timestamp bounds relationship +static_assert(EARLIEST_SANE_UNIX_TIMESTAMP < LATEST_SANE_UNIX_TIMESTAMP, + "EARLIEST_SANE_UNIX_TIMESTAMP must be less than " + "LATEST_SANE_UNIX_TIMESTAMP"); //============================================================== //============================================================== -// Time-stamp configurations +// Variable configurations //============================================================== -#ifndef MS_LOGGER_EPOCH +#if !defined(MS_INVALID_VALUE) || defined(DOXYGEN) /** - * @brief The epoch start to use for the logger + * @def MS_INVALID_VALUE + * @brief The value used to represent an invalid or missing measurement. + * + * Every sensor will use this value to indicate that a measurement is invalid + * or missing. */ -#define MS_LOGGER_EPOCH epochStart::unix_epoch +#define MS_INVALID_VALUE -9999 #endif -#ifndef EARLIEST_SANE_UNIX_TIMESTAMP +#if !defined(MAX_NUMBER_VARS) || defined(DOXYGEN) /** - * @brief The earliest unix timestamp that can be considered sane. + * @def MAX_NUMBER_VARS + * @brief The largest number of variables from a single sensor. * - * January 1, 2025 + * Every sensor will create a buffer of this length for holding variable values. + * Decrease this value to save memory. + * + * @note This is the maximum number of variables that can be tied to any one + * sensor, not the maximum number of variables in a variable array. */ -#define EARLIEST_SANE_UNIX_TIMESTAMP 1735689600 +#define MAX_NUMBER_VARS 21 +// GroPoint Profile GPLP-8 has 8 Moisture and 13 Temperature values #endif +// Static assert to the maximum number of variables is no more than the largest +// number of variables from any sensor. Anything more is a waste of memory. +static_assert(MAX_NUMBER_VARS > 0 && MAX_NUMBER_VARS <= 21, + "MAX_NUMBER_VARS must be between 1 and 21"); -#ifndef LATEST_SANE_UNIX_TIMESTAMP +#if !defined(MAX_NUMBER_SENSORS) || defined(DOXYGEN) /** - * @brief The latest unix timestamp that can be considered sane. + * @def MAX_NUMBER_SENSORS + * @brief The largest number of sensors in a single variable array. * - * January 1, 2035 + * @note This is **not** the same as the maximum number of variables in a + * variable array. One sensor may return many variables but only counts as one + * sensor. + * + * Decrease this value to save memory if you know you'll use fewer sensors. + * Increase if you need to support more sensors in a single array. */ -#define LATEST_SANE_UNIX_TIMESTAMP 2051222400 +#define MAX_NUMBER_SENSORS 20 #endif +// Static assert to ensure the maximum number of sensors is reasonable +static_assert(MAX_NUMBER_SENSORS > 0 && MAX_NUMBER_SENSORS <= 50, + "MAX_NUMBER_SENSORS must be between 1 and 50"); //============================================================== //============================================================== -// Variable configurations +// User button functionality //============================================================== -#ifndef MAX_NUMBER_VARS +#if !defined(MS_LOGGERBASE_BUTTON_BENCH_TEST) || defined(DOXYGEN) /** - * @brief The largest number of variables from a single sensor. + * @def MS_LOGGERBASE_BUTTON_BENCH_TEST + * @brief Enable bench testing mode for the testing button. * - * Every sensor will create a buffer of this length for holding variable values. - * Decrease this value to save a memory. + * When enabled, the testing button uses the benchTestingMode() function to + * repeatedly read and print out sensor data. When disabled (default), the + * testing button causes a reading to be taken and transmitted immediately + * using the normal procedure. */ -#define MAX_NUMBER_VARS 21 -// GroPoint Profile GPLP-8 has 8 Moisture and 13 Temperature values +#define MS_LOGGERBASE_BUTTON_BENCH_TEST false +#endif +//============================================================== + + +//============================================================== +// SPI Configuration, iff needed +//============================================================== +#if !defined(SDCARD_SPI) || defined(DOXYGEN) +/** + * @def SDCARD_SPI + * @brief The SPI port to use for the SD card. + * + * This is typically defined in the board variant file. + */ +#define SDCARD_SPI SPI #endif +//============================================================== + -#ifndef MS_PROCESSOR_ADC_RESOLUTION +//============================================================== +// Processor ADC configuration +//============================================================== +#if !defined(MS_PROCESSOR_ADC_RESOLUTION) || defined(DOXYGEN) /** + * @def MS_PROCESSOR_ADC_RESOLUTION * @brief Select or adjust the processor analog resolution. * * This is the resolution of the **built-in** processor ADC and it cannot be set * higher than what your processor actually supports. This does **not** apply to * the TI ADS1115 or ADS1015 external ADS. * - * The default for AVR boards is 10 and for other boards is 12. + * For supported boards, appropriate defaults are set in KnownProcessors.h: + * - AVR boards: 10 bits + * - SAMD boards: 12 bits + * + * Uncomment the line below only to OVERRIDE the automatic selection. + * The library currently only supports AVR and SAMD platforms. * * Future note: The ESP32 has a 12 bit ADC and the ESP8266 has a 10 bit ADC. */ -#if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) -#define MS_PROCESSOR_ADC_RESOLUTION 10 -#else -#define MS_PROCESSOR_ADC_RESOLUTION 12 +// #define MS_PROCESSOR_ADC_RESOLUTION 12 #endif #if !defined(MS_PROCESSOR_ADC_RESOLUTION) #error The processor ADC resolution must be defined! #endif // MS_PROCESSOR_ADC_RESOLUTION -#endif +// Static assert to validate ADC resolution is reasonable +static_assert(MS_PROCESSOR_ADC_RESOLUTION >= 8 && + MS_PROCESSOR_ADC_RESOLUTION <= 16, + "MS_PROCESSOR_ADC_RESOLUTION must be between 8 and 16 bits"); /// @brief The maximum possible value of the ADC - one less than the resolution /// shifted up one bit. @@ -162,79 +204,162 @@ /// @brief The maximum possible range of the ADC - the resolution shifted up one /// bit. #define PROCESSOR_ADC_RANGE (1 << MS_PROCESSOR_ADC_RESOLUTION) - -#if !defined(MS_PROCESSOR_ADC_REFERENCE_MODE) || defined(DOXYGEN) -#if defined(ARDUINO_ARCH_AVR) || defined(DOXYGEN) +#if !defined(MS_PROCESSOR_ANALOG_MAX_CHANNEL) || defined(DOXYGEN) /** - * @brief The voltage reference mode for the processor's ADC. + * @def MS_PROCESSOR_ANALOG_MAX_CHANNEL + * @brief Upper bound used to sanity-check analog channel numbers at runtime. * - * For an AVR board, this must be one of: - * - `DEFAULT`: the default built-in analog reference of 5 volts (on 5V Arduino - * boards) or 3.3 volts (on 3.3V Arduino boards) - * - `INTERNAL`: a built-in reference, equal to 1.1 volts on the ATmega168 or - * ATmega328P and 2.56 volts on the ATmega32U4 and ATmega8 (not available on the - * Arduino Mega) - * - `INTERNAL1V1`: a built-in 1.1V reference (Arduino Mega only) - * - `INTERNAL2V56`: a built-in 2.56V reference (Arduino Mega only) - * - `EXTERNAL`: the voltage applied to the AREF pin (0 to 5V only) is used as - * the reference. - * - * If not set on an AVR board `DEFAULT` is used. - * - * For the best accuracy, use an `EXTERNAL` reference with the AREF pin - * connected to the power supply for the EC sensor. + * This is not a hardware limit but a validation ceiling that exceeds the + * largest channel index found on any supported Arduino platform (e.g. Mega: + * A0–A15). Override with -D MS_PROCESSOR_ANALOG_MAX_CHANNEL=x if needed. */ -#define MS_PROCESSOR_ADC_REFERENCE_MODE DEFAULT -#endif -#if defined(ARDUINO_ARCH_SAMD) || defined(DOXYGEN) +#define MS_PROCESSOR_ANALOG_MAX_CHANNEL 100 +#endif // MS_PROCESSOR_ANALOG_MAX_CHANNEL +// Static assert to validate analog channel maximum is reasonable +static_assert(MS_PROCESSOR_ANALOG_MAX_CHANNEL > 0 && + MS_PROCESSOR_ANALOG_MAX_CHANNEL <= 255, + "MS_PROCESSOR_ANALOG_MAX_CHANNEL must be between 1 and 255"); + +#if !defined(MS_PROCESSOR_ADC_REFERENCE_MODE) || defined(DOXYGEN) /** + * @def MS_PROCESSOR_ADC_REFERENCE_MODE * @brief The voltage reference mode for the processor's ADC. * - * For a SAMD board, this must be one of: - * - `AR_DEFAULT`: the default built-in analog reference of 3.3V - * - `AR_INTERNAL`: a built-in 2.23V reference - * - `AR_INTERNAL1V0`: a built-in 1.0V reference - * - `AR_INTERNAL1V65`: a built-in 1.65V reference - * - `AR_INTERNAL2V23`: a built-in 2.23V reference - * - `AR_EXTERNAL`: the voltage applied to the AREF pin is used as the reference + * For supported boards, appropriate defaults are set in KnownProcessors.h. + * Uncomment one of the options below only to OVERRIDE the automatic selection. + * + * For AVR boards, valid options are: + * - `DEFAULT`: default built-in analog reference (5V or 3.3V depending on + * board) + * - `INTERNAL`: built-in reference (1.1V on ATmega168/328P, 2.56V on + * ATmega32U4) + * - `INTERNAL1V1`: built-in 1.1V reference (Arduino Mega only) + * - `INTERNAL2V56`: built-in 2.56V reference (Arduino Mega only) + * - `EXTERNAL`: voltage applied to AREF pin (0 to 5V) * - * If not set on an SAMD board `AR_DEFAULT` is used. + * For SAMD boards, valid options are: + * - `AR_DEFAULT`: default built-in analog reference (3.3V) + * - `AR_INTERNAL`: built-in 2.23V reference + * - `AR_INTERNAL1V0`: built-in 1.0V reference + * - `AR_INTERNAL1V65`: built-in 1.65V reference + * - `AR_INTERNAL2V23`: built-in 2.23V reference + * - `AR_EXTERNAL`: voltage applied to AREF pin * - * For the best accuracy, use an `AR_EXTERNAL` reference with the AREF pin - * connected to the power supply for the EC sensor. On most Adafruit SAMD51 - * boards, there is an optional solder jumper to connect the AREF pin to - * the 3.3V supply. I suggest you close the jumper! On an EnviroDIY Stonefly, - * there is also a solder jumper, but on the Stonefly the jumper is *closed by - * default.* + * For best accuracy, use EXTERNAL/AR_EXTERNAL with AREF connected to sensor + * supply. * * @see * https://www.arduino.cc/reference/en/language/functions/analog-io/analogreference/ */ -#if defined(ENVIRODIY_STONEFLY_M4) -#define MS_PROCESSOR_ADC_REFERENCE_MODE AR_EXTERNAL -#else -#define MS_PROCESSOR_ADC_REFERENCE_MODE AR_DEFAULT -#endif +// #define MS_PROCESSOR_ADC_REFERENCE_MODE AR_EXTERNAL #endif #if !defined(MS_PROCESSOR_ADC_REFERENCE_MODE) #error The processor ADC reference type must be defined! #endif // MS_PROCESSOR_ADC_REFERENCE_MODE -#endif // ARDUINO_ARCH_SAMD +//============================================================== + + +//============================================================== +// External ADC configuration and defaults +//============================================================== +// Select ADS1015 instead of the ADS1115, if desired +// This is for sensors that use the external ADC for analog voltage +// measurements. +// #define MS_USE_ADS1015 + +#if !defined(MS_DEFAULT_ADS1X15_ADDRESS) || defined(DOXYGEN) +/** + * @brief The default I²C address of the ADS1115 or ADS1015 external ADC. + * + * Valid addresses depend on the ADDR pin connection: + * - `0x48` – ADDR to GND (default) + * - `0x49` – ADDR to VDD + * - `0x4A` – ADDR to SDA + * - `0x4B` – ADDR to SCL + * + * Override with a build flag: `-DMS_DEFAULT_ADS1X15_ADDRESS=0x49` + */ +#define MS_DEFAULT_ADS1X15_ADDRESS 0x48 +#endif +// Static assert to validate ADS1X15 I2C address is valid +static_assert(MS_DEFAULT_ADS1X15_ADDRESS == 0x48 || + MS_DEFAULT_ADS1X15_ADDRESS == 0x49 || + MS_DEFAULT_ADS1X15_ADDRESS == 0x4A || + MS_DEFAULT_ADS1X15_ADDRESS == 0x4B, + "MS_DEFAULT_ADS1X15_ADDRESS must be 0x48, 0x49, 0x4A, or 0x4B " + "for ADS1X15"); +//============================================================== + + +//============================================================== +// SDI-12 Configuration +//============================================================== +// Disable concurrent polling of SDI-12 sensors, if needed +// Concurrent measurement support was introduced in version 1.2 (April 12, 1996) +// of the specification and all sensors that claim to use version 1.2 or higher +// must support it. +// That being said.. some new sensors are fussy and will incorrectly abort +// concurrent measurements due to noise or other sensors on the bus. +// NOTE: By default, concurrent measurements are used for all SDI-12 sensors! +// NOTE: This single setting applies to all SDI-12 sensors; it cannot be set on +// a per-sensor basis. +// #define MS_SDI12_NON_CONCURRENT + +// Disable CRC checking for SDI-12 sensors, if needed +// This may be necessary if using older or fussy sensors +// CRC support in SDI-12 was implemented with version 1.3 (April 7, 2000) and +// all sensors that claim to use version 1.3 or higher must support it. +// NOTE: By default, CRCs are used for all SDI-12 sensors! +// NOTE: This single setting applies to all SDI-12 sensors; it cannot be set on +// a per-sensor basis. +// #define MS_SDI12_NO_CRC_CHECK +//============================================================== + + +//============================================================== +// Environmental sensor configuration +//============================================================== +#if !defined(MS_SEA_LEVEL_PRESSURE_HPA) || defined(DOXYGEN) +/** + * @brief The atmospheric pressure at sea level in hPa for barometric sensors. + * + * This value is used by environmental sensors (BME280, BMP3xx, MS5837) for + * calculating altitude and depth measurements. The default value is standard + * atmospheric pressure at sea level (1013.25 hPa). Adjust this value based on + * local atmospheric conditions for more accurate calculations. + * + * @note In library versions prior to 0.37.0, this variable was named + * SEALEVELPRESSURE_HPA and was defined in the header files for the BME280 and + * BMP3xx sensors. + */ +#define MS_SEA_LEVEL_PRESSURE_HPA 1013.25f +#endif +// Static assert to validate sea level pressure is reasonable +static_assert(MS_SEA_LEVEL_PRESSURE_HPA >= 800.0f && + MS_SEA_LEVEL_PRESSURE_HPA <= 1200.0f, + "MS_SEA_LEVEL_PRESSURE_HPA must be between 800 and 1200 hPa " + "(reasonable atmospheric pressure range)"); //============================================================== //============================================================== // Publisher configuration //============================================================== -#ifndef MAX_NUMBER_SENDERS +#if !defined(MAX_NUMBER_SENDERS) || defined(DOXYGEN) /** + * @def MAX_NUMBER_SENDERS * @brief The largest number of publishers that can be attached to a logger */ #define MAX_NUMBER_SENDERS 4 #endif +// Static assert to validate MAX_NUMBER_SENDERS is reasonable +// Upper limit of 16 is set to constrain memory usage and array sizing +static_assert(MAX_NUMBER_SENDERS >= 0 && MAX_NUMBER_SENDERS <= 16, + "MAX_NUMBER_SENDERS must be between 0 and 16"); -#ifndef MS_ALWAYS_FLUSH_PUBLISHERS +#if !defined(MS_ALWAYS_FLUSH_PUBLISHERS) || defined(DOXYGEN) /** + * @def MS_ALWAYS_FLUSH_PUBLISHERS * @brief Set this to true to always force publishers to attempt to transmit * data. If false, publishers will only transmit data at the sendEveryX * interval or when the buffer fills. @@ -242,8 +367,40 @@ #define MS_ALWAYS_FLUSH_PUBLISHERS false #endif -#ifndef MS_SEND_BUFFER_SIZE +#if !defined(MS_LOG_DATA_BUFFER_SIZE) || defined(DOXYGEN) +/** + * @def MS_LOG_DATA_BUFFER_SIZE + * @brief Log Data Buffer + * + * This determines how much RAM is reserved to buffer log records before + * transmission. Each record consumes 4 bytes for the timestamp plus 4 bytes + * for each logged variable. Increasing this value too far can crash the + * device! The number of log records buffered is controlled by sendEveryX. + * + * For supported boards, appropriate defaults are set in KnownProcessors.h: + * - ATmega1284p (Mayfly): 8192 bytes + * - ATmega2560 (Mega): 512 bytes + * - ATmega328p (Uno/Nano): 256 bytes + * - SAMD21/SAMD51: 1024 bytes (default) + * + * Uncomment the line below only to OVERRIDE the automatic selection. + * This can also be changed by setting the build flag MS_LOG_DATA_BUFFER_SIZE + * when compiling. 8192 bytes is a safe value for the Mayfly 1.1 with six + * variables. + */ +// #define MS_LOG_DATA_BUFFER_SIZE 1024 +#endif +#if !defined(MS_LOG_DATA_BUFFER_SIZE) +#error The log data buffer size must be defined! +#endif // MS_LOG_DATA_BUFFER_SIZE +// Static assert to validate log buffer size is reasonable +static_assert(MS_LOG_DATA_BUFFER_SIZE >= 64 && MS_LOG_DATA_BUFFER_SIZE <= 16384, + "MS_LOG_DATA_BUFFER_SIZE must be between 64 and 16384 bytes"); + + +#if !defined(MS_SEND_BUFFER_SIZE) || defined(DOXYGEN) /** + * @def MS_SEND_BUFFER_SIZE * @brief Send Buffer * * This determines how many characters to set out at once over the TCP @@ -259,9 +416,13 @@ */ #define MS_SEND_BUFFER_SIZE 1360 #endif +// Static assert to validate send buffer size is reasonable +static_assert(MS_SEND_BUFFER_SIZE >= 32 && MS_SEND_BUFFER_SIZE <= 2048, + "MS_SEND_BUFFER_SIZE must be between 32 and 2048 bytes"); -#ifndef TINY_GSM_RX_BUFFER +#if !defined(TINY_GSM_RX_BUFFER) || defined(DOXYGEN) /** + * @def TINY_GSM_RX_BUFFER * @brief The size of the buffer for incoming data. * * If using a module that buffers data internally, this can be 64 or lower. If @@ -270,9 +431,30 @@ */ #define TINY_GSM_RX_BUFFER 64 #endif +// Static assert to validate GSM RX buffer size is reasonable +static_assert(TINY_GSM_RX_BUFFER >= 16 && TINY_GSM_RX_BUFFER <= 2048, + "TINY_GSM_RX_BUFFER must be between 16 and 2048 bytes"); + -#ifndef MS_MQTT_MAX_PACKET_SIZE +#if !defined(TINY_GSM_YIELD_MS) || defined(DOXYGEN) /** + * @def TINY_GSM_YIELD_MS + * @brief The number of milliseconds to yield to the GSM module when using + * TinyGSM. + * + * If you are using a slow baud rate to communicate with your module, this delay + * is set to prevent command responses from being spliced apart. This is + * especially important when using a faster processor. + */ +#define TINY_GSM_YIELD_MS 2 +#endif +// Static assert to validate GSM yield time is reasonable +static_assert(TINY_GSM_YIELD_MS >= 0 && TINY_GSM_YIELD_MS <= 1000, + "TINY_GSM_YIELD_MS must be between 0 and 1000 milliseconds"); + +#if !defined(MS_MQTT_MAX_PACKET_SIZE) || defined(DOXYGEN) +/** + * @def MS_MQTT_MAX_PACKET_SIZE * @brief Configure the size of the PubSubClient buffer for MQTT publishers. * * This is the maximum size of any single MQTT message - incoming or outgoing. @@ -285,14 +467,19 @@ */ #define MS_MQTT_MAX_PACKET_SIZE 1536 #endif +// Static assert to validate MQTT packet size is reasonable +static_assert(MS_MQTT_MAX_PACKET_SIZE >= 128 && MS_MQTT_MAX_PACKET_SIZE <= 4096, + "MS_MQTT_MAX_PACKET_SIZE must be between 128 and 4096 bytes"); + //============================================================== //============================================================== // Special configurations for the AWS IoT Core publisher //============================================================== -#ifndef MS_AWS_IOT_PUBLISHER_SUB_COUNT +#if !defined(MS_AWS_IOT_PUBLISHER_SUB_COUNT) || defined(DOXYGEN) /** + * @def MS_AWS_IOT_PUBLISHER_SUB_COUNT * @brief The maximum number of extra subscriptions that can be added to the AWS * IoT Core publisher. * @@ -300,15 +487,25 @@ */ #define MS_AWS_IOT_PUBLISHER_SUB_COUNT 4 #endif -#ifndef MS_AWS_IOT_PUBLISHER_PUB_COUNT +#if !defined(MS_AWS_IOT_PUBLISHER_PUB_COUNT) || defined(DOXYGEN) /** + * @def MS_AWS_IOT_PUBLISHER_PUB_COUNT * @brief The maximum number of extra publish messages that can be added to the * AWS IoT Core publisher. */ #define MS_AWS_IOT_PUBLISHER_PUB_COUNT 4 #endif -#ifndef MS_AWS_IOT_MAX_CONNECTION_TIME +// Static asserts to validate AWS IoT publisher counts are reasonable +static_assert( + MS_AWS_IOT_PUBLISHER_SUB_COUNT >= 0 && MS_AWS_IOT_PUBLISHER_SUB_COUNT <= 8, + "MS_AWS_IOT_PUBLISHER_SUB_COUNT must be between 0 and 8 (AWS limit)"); +static_assert(MS_AWS_IOT_PUBLISHER_PUB_COUNT >= 0 && + MS_AWS_IOT_PUBLISHER_PUB_COUNT <= 16, + "MS_AWS_IOT_PUBLISHER_PUB_COUNT must be between 0 and 16"); + +#if !defined(MS_AWS_IOT_MAX_CONNECTION_TIME) || defined(DOXYGEN) /** + * @def MS_AWS_IOT_MAX_CONNECTION_TIME * @brief The maximum time to wait for subscriptions after publishing data to * AWS IoT Core. * @@ -316,14 +513,20 @@ */ #define MS_AWS_IOT_MAX_CONNECTION_TIME 30000L #endif +// Static assert to validate AWS IoT connection timeout is reasonable +static_assert(MS_AWS_IOT_MAX_CONNECTION_TIME > 0 && + MS_AWS_IOT_MAX_CONNECTION_TIME <= 600000L, + "MS_AWS_IOT_MAX_CONNECTION_TIME must be between 1 and 600000 " + "milliseconds (10 minutes max)"); //============================================================== //============================================================== // Special configurations for the S3 publisher //============================================================== -#ifndef S3_DEFAULT_FILE_EXTENSION +#if !defined(S3_DEFAULT_FILE_EXTENSION) || defined(DOXYGEN) /** + * @def S3_DEFAULT_FILE_EXTENSION * @brief The default file extension to use to send to S3: ".jpg" * * This assumes you are using S3 to send images. If you want to put your basic @@ -345,4 +548,7 @@ //============================================================== +// cSpell:words SEALEVELPRESSURE + + #endif // SRC_MODSENSORCONFIG_H_ diff --git a/src/ModSensorDebugConfig.h b/src/ModSensorDebugConfig.h index 5c805be65..aebb5a0a9 100644 --- a/src/ModSensorDebugConfig.h +++ b/src/ModSensorDebugConfig.h @@ -59,7 +59,7 @@ // #define MS_DATAPUBLISHERBASE_DEBUG // #define MS_DATAPUBLISHERBASE_DEBUG_DEEP -// #define MS_ENVIRODIYPUBLISHER_DEBUG +// #define MS_MONITORMYWATERSHEDPUBLISHER_DEBUG // #define MS_THINGSPEAKPUBLISHER_DEBUG // #define MS_UBIDOTSPUBLISHER_DEBUG // #define MS_DREAMHOSTPUBLISHER_DEBUG @@ -125,6 +125,7 @@ // #define MS_MAXIMDS18_DEBUG // #define MS_MAXIMDS3231_DEBUG // #define MS_MEASPECMS5803_DEBUG +// #define MS_TECONNECTIVITYMS5837_DEBUG // #define MS_PALEOTERRAREDOX_DEBUG // #define MS_PROCESSORSTATS_DEBUG // #define MS_RAINCOUNTERI2C_DEBUG diff --git a/src/SensorBase.cpp b/src/SensorBase.cpp index 2d36484ff..1c60ab936 100644 --- a/src/SensorBase.cpp +++ b/src/SensorBase.cpp @@ -22,48 +22,61 @@ Sensor::Sensor(const char* sensorName, const uint8_t totalReturnedValues, uint8_t measurementsToAverage, uint8_t incCalcValues) : _dataPin(dataPin), _powerPin(powerPin), + _powerPin2(-1), _sensorName(sensorName), _numReturnedValues(totalReturnedValues), - _measurementsToAverage(measurementsToAverage), _incCalcValues(incCalcValues), + _measurementsToAverage(measurementsToAverage), _warmUpTime_ms(warmUpTime_ms), _stabilizationTime_ms(stabilizationTime_ms), _measurementTime_ms(measurementTime_ms) { // Clear arrays for (uint8_t i = 0; i < MAX_NUMBER_VARS; i++) { - variables[i] = nullptr; - sensorValues[i] = -9999; - numberGoodMeasurementsMade[i] = 0; + variables[i] = nullptr; + sensorValues[i] = MS_INVALID_VALUE; + validCount[i] = 0; } } -// Destructor -Sensor::~Sensor() {} -// This gets the place the sensor is installed ON THE MAYFLY (ie, pin number) -String Sensor::getSensorLocation(void) { - String senseLoc = F("Pin"); +// This gets the place the sensor is installed ON THE MAYFLY (i.e., pin number) +String Sensor::getSensorLocation() { + String senseLoc; + senseLoc.reserve(10); // Reserve for "Pin" + pin number + senseLoc = F("Pin"); senseLoc += String(_dataPin); return senseLoc; } // This returns the name of the sensor. -String Sensor::getSensorName(void) { +String Sensor::getSensorName() { return _sensorName; } // This concatenates and returns the name and location. -String Sensor::getSensorNameAndLocation(void) { +String Sensor::getSensorNameAndLocation() { return getSensorName() + " at " + getSensorLocation(); } // This returns the number of the power pin -int8_t Sensor::getPowerPin(void) { +int8_t Sensor::getPowerPin() { return _powerPin; } +// This sets the power pin +void Sensor::setPowerPin(int8_t pin) { + _powerPin = pin; +} +// This returns the number of the secondary power pin +int8_t Sensor::getSecondaryPowerPin() { + return _powerPin2; +} +// This sets the secondary power pin +void Sensor::setSecondaryPowerPin(int8_t pin) { + _powerPin2 = pin; +} // These functions get and set the number of readings to average for a sensor @@ -71,11 +84,42 @@ int8_t Sensor::getPowerPin(void) { void Sensor::setNumberMeasurementsToAverage(uint8_t nReadings) { _measurementsToAverage = nReadings; } -uint8_t Sensor::getNumberMeasurementsToAverage(void) { +uint8_t Sensor::getNumberMeasurementsToAverage() { return _measurementsToAverage; } +uint8_t Sensor::getCompletedMeasurements() { + return _completedMeasurements; +} +uint8_t Sensor::getCurrentRetries() { + return _currentRetries; +} +void Sensor::setMaxRetries(uint8_t maxRetries) { + _maxRetries = maxRetries; +} +uint8_t Sensor::getMaxRetries() { + return _maxRetries; +} +void Sensor::setWarmUpTime(uint32_t warmUpTime_ms) { + _warmUpTime_ms = warmUpTime_ms; +} +uint32_t Sensor::getWarmUpTime() { + return _warmUpTime_ms; +} +void Sensor::setStabilizationTime(uint32_t stabilizationTime_ms) { + _stabilizationTime_ms = stabilizationTime_ms; +} +uint32_t Sensor::getStabilizationTime() { + return _stabilizationTime_ms; +} +void Sensor::setMeasurementTime(uint32_t measurementTime_ms) { + _measurementTime_ms = measurementTime_ms; +} +uint32_t Sensor::getMeasurementTime() { + return _measurementTime_ms; +} + // This returns the 8-bit code for the current status of the sensor. // Bit 0 - 0=Has NOT been set up, 1=Has been setup // Bit 1 - 0=No attempt made to power sensor, 1=Attempt made to power sensor @@ -85,7 +129,7 @@ uint8_t Sensor::getNumberMeasurementsToAverage(void) { // bit 5 - 0=Measurement start attempted, 1=No measurements have been requested // bit 6 - 0=Measurement start failed, 1=Measurement attempt succeeded // Bit 7 - 0=No known errors, 1=Some sort of error has occurred -uint8_t Sensor::getStatus(void) { +uint8_t Sensor::getStatus() { return _sensorStatus; } @@ -101,14 +145,22 @@ void Sensor::clearStatusBit(sensor_status_bits bitToClear) { // This turns on sensor power -void Sensor::powerUp(void) { - if (_powerPin >= 0) { +void Sensor::powerUp() { + if (_powerPin >= 0 || _powerPin2 >= 0) { // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); + // on sleep on SAMD boards + if (_powerPin >= 0) { + pinMode(_powerPin, OUTPUT); + MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), + _powerPin); + digitalWrite(_powerPin, HIGH); + } + if (_powerPin2 >= 0) { + pinMode(_powerPin2, OUTPUT); + MS_DBG(F("Giving secondary power to"), getSensorNameAndLocation(), + F("with pin"), _powerPin2); + digitalWrite(_powerPin2, HIGH); + } // Mark the time that the sensor was powered _millisPowerOn = millis(); } else { @@ -123,11 +175,22 @@ void Sensor::powerUp(void) { // This turns off sensor power -void Sensor::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); +void Sensor::powerDown() { + if (_powerPin >= 0 || _powerPin2 >= 0) { + // Reset power pin mode every pin access because pins are set to + // tri-state on sleep on SAMD boards + if (_powerPin >= 0) { + pinMode(_powerPin, OUTPUT); + MS_DBG(F("Turning off"), getSensorNameAndLocation(), F("with pin"), + _powerPin); + digitalWrite(_powerPin, LOW); + } + if (_powerPin2 >= 0) { + pinMode(_powerPin2, OUTPUT); + MS_DBG(F("Turning off secondary power to"), + getSensorNameAndLocation(), F("with pin"), _powerPin2); + digitalWrite(_powerPin2, LOW); + } // Unset the power-on time _millisPowerOn = 0; // Unset the activation time @@ -150,7 +213,7 @@ void Sensor::powerDown(void) { // The function to set up connection to a sensor. // By default, sets pin modes and returns true -bool Sensor::setup(void) { +bool Sensor::setup() { MS_DBG(F("Setting up"), getSensorName(), F("attached at"), getSensorLocation(), F("which can return up to"), _numReturnedValues, F("variable[s]"), _incCalcValues, @@ -178,9 +241,8 @@ bool Sensor::setup(void) { // The function to wake up a sensor -bool Sensor::wake(void) { - MS_DBG(F("Waking"), getSensorNameAndLocation(), - F("using default wake of taking no action!")); +bool Sensor::wake() { + MS_DBG(F("Waking"), getSensorNameAndLocation(), F("by doing nothing!")); // Set the status bit for sensor activation attempt (bit 3) // Setting this bit even if the activation failed, to show the attempt was // made @@ -196,13 +258,17 @@ bool Sensor::wake(void) { return false; } // Set the data pin mode on every wake because pins are set to tri-state on - // sleep + // sleep on SAMD boards if (_dataPin >= 0) { pinMode(_dataPin, INPUT); } // NOTE: Not turning on processor pull-up or pull-down! // Mark the time that the sensor was activated - _millisSensorActivated = millis(); + // NOTE: If we didn't do anything to wake the sensor, we **don't** + // want to mark the time as **now** but as the last time we did do + // something. Since we didn't actively wake the sensor, we assume the + // measurement was started at power on. + _millisSensorActivated = _millisPowerOn; // Set the status bit for sensor wake/activation success (bit 4) setStatusBit(WAKE_SUCCESSFUL); @@ -212,7 +278,7 @@ bool Sensor::wake(void) { // The function to put a sensor to sleep // Does NOT power down the sensor! -bool Sensor::sleep(void) { +bool Sensor::sleep() { // If nothing needs to be done to make the sensor go to sleep, we'll leave // the bits and time stamps set because running the sleep function doesn't // do anything. If the sensor has a power pin and it is powered down, then @@ -223,7 +289,7 @@ bool Sensor::sleep(void) { // This is a place holder for starting a single measurement, for those sensors // that need no instructions to start a measurement. -bool Sensor::startSingleMeasurement(void) { +bool Sensor::startSingleMeasurement() { bool success = true; // check if the sensor was successfully set up, run set up if not @@ -235,7 +301,7 @@ bool Sensor::startSingleMeasurement(void) { } MS_DBG(F("Starting measurement on"), getSensorNameAndLocation(), - F("using default start of taking no action!")); + F("by doing nothing!")); // Set the status bits for measurement requested (bit 5) // Setting this bit even if we failed to start a measurement to show that an // attempt was made. @@ -245,7 +311,18 @@ bool Sensor::startSingleMeasurement(void) { // Only mark the measurement request time if it is if (getStatusBit(WAKE_SUCCESSFUL)) { // Mark the time that a measurement was requested - _millisMeasurementRequested = millis(); + // NOTE: If we didn't do anything to start a measurement, we **don't** + // want to mark the time as **now** but as the last time we did do + // something. Since we didn't actively start the measurement, we assume + // the measurement was started either at wake or at the time the last + // measurement was finished. + if (_millisMeasurementCompleted != 0) { + _millisMeasurementRequested = + _millisMeasurementCompleted; // immediately after last + // measurement + } else { + _millisMeasurementRequested = _millisSensorActivated; // at wake + } // Set the status bit for measurement start success (bit 6) setStatusBit(MEASUREMENT_SUCCESSFUL); } else { @@ -265,7 +342,7 @@ void Sensor::registerVariable(int sensorVarNum, Variable* var) { variables[sensorVarNum] = var; } -void Sensor::notifyVariables(void) { +void Sensor::notifyVariables() { MS_DBG(F("Notifying variables registered to"), getSensorNameAndLocation(), F("of value update.")); @@ -286,48 +363,89 @@ void Sensor::notifyVariables(void) { } -// This function just empties the value array -void Sensor::clearValues(void) { +// This function resets the value array by setting all values to invalid. +void Sensor::clearValues() { MS_DBG(F("Clearing value array for"), getSensorNameAndLocation()); for (uint8_t i = 0; i < _numReturnedValues; i++) { - sensorValues[i] = -9999; - numberGoodMeasurementsMade[i] = 0; + sensorValues[i] = MS_INVALID_VALUE; + validCount[i] = 0; } } +// This function resets all measurement counts. +void Sensor::resetMeasurementCounts() { + MS_DBG(F("Resetting measurement counts for"), getSensorNameAndLocation()); + // Reset measurement attempt counters + _completedMeasurements = 0; + _currentRetries = 0; +} + +// This clears power-related status bits and resets power timing. +void Sensor::clearPowerStatus() { + // Reset power timing value + _millisPowerOn = 0; + // Unset power status bits + clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); +} + +// This clears wake-related status bits and resets wake timing. +void Sensor::clearWakeStatus() { + // Reset wake timing value + _millisSensorActivated = 0; + // Unset wake status bits + clearStatusBits(WAKE_ATTEMPTED, WAKE_SUCCESSFUL); +} + +// This clears measurement-related status bits and resets measurement timing. +void Sensor::clearMeasurementStatus() { + // Reset measurement timing values + _millisMeasurementRequested = 0; + _millisMeasurementCompleted = 0; + // Unset measurement status bits + clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); +} + +// This clears all status bits except the setup and error bit and sets the +// timing values to 0. +void Sensor::clearStatus() { + // Use the individual clear functions for better maintainability + clearPowerStatus(); + clearWakeStatus(); + clearMeasurementStatus(); +} + // This verifies that a measurement is good before adding it to the values to be // averaged void Sensor::verifyAndAddMeasurementResult(uint8_t resultNumber, float resultValue) { + bool prevResultGood = (sensorValues[resultNumber] != MS_INVALID_VALUE && + !isnan(sensorValues[resultNumber])); + bool newResultGood = (resultValue != MS_INVALID_VALUE && + !isnan(resultValue)); // If the new result is good and there was were only bad results, set the // result value as the new result and add 1 to the good result total - if ((sensorValues[resultNumber] == -9999 || - isnan(sensorValues[resultNumber])) && - (resultValue != -9999 && !isnan(resultValue))) { + if (!prevResultGood && newResultGood) { MS_DBG(F("Putting"), resultValue, F("in result array for variable"), resultNumber, F("from"), getSensorNameAndLocation()); sensorValues[resultNumber] = resultValue; - numberGoodMeasurementsMade[resultNumber] += 1; - } else if ((sensorValues[resultNumber] == -9999 || - isnan(sensorValues[resultNumber])) && - (resultValue != -9999 && !isnan(resultValue))) { + validCount[resultNumber] += 1; + } else if (prevResultGood && newResultGood) { // If the new result is good and there were already good results in // place add the new results to the total and add 1 to the good result // total MS_DBG(F("Adding"), resultValue, F("to result array for variable"), resultNumber, F("from"), getSensorNameAndLocation()); sensorValues[resultNumber] += resultValue; - numberGoodMeasurementsMade[resultNumber] += 1; - } else if (sensorValues[resultNumber] == -9999 && resultValue == -9999) { - // If the new result is bad and there were only bad results, do nothing + validCount[resultNumber] += 1; + } else if (!prevResultGood && !newResultGood) { + // If the new result is bad and there were only bad results, only print + // debugging MS_DBG(F("Ignoring bad result for variable"), resultNumber, F("from"), getSensorNameAndLocation(), F("; no good results yet.")); - } else if ((sensorValues[resultNumber] == -9999 || - isnan(sensorValues[resultNumber])) && - resultValue == -9999) { - // If the new result is bad and there were already good results, do - // nothing + } else if (prevResultGood && !newResultGood) { + // If the new result is bad and there were already good results, only + // print debugging MS_DBG(F("Ignoring bad result for variable"), resultNumber, F("from"), getSensorNameAndLocation(), F("; good results already in array.")); @@ -348,28 +466,73 @@ void Sensor::verifyAndAddMeasurementResult(uint8_t resultNumber, } -void Sensor::averageMeasurements(void) { +void Sensor::averageMeasurements() { MS_DBG(F("Averaging results from"), getSensorNameAndLocation(), F("over"), _measurementsToAverage, F("reading[s]")); for (uint8_t i = 0; i < _numReturnedValues; i++) { - if (numberGoodMeasurementsMade[i] > 0) - sensorValues[i] /= numberGoodMeasurementsMade[i]; + if (validCount[i] > 0) sensorValues[i] /= validCount[i]; MS_DBG(F(" ->Result #"), i, ':', sensorValues[i]); } } +void Sensor::printData(Stream* stream, bool printStatusBits) { + for (uint8_t i = 0; i < _numReturnedValues; i++) { + stream->print(getSensorNameAndLocation()); + if (printStatusBits) { + stream->print(F(" with status 0b")); + stream->print(getStatusBit(ERROR_OCCURRED)); + stream->print(getStatusBit(MEASUREMENT_SUCCESSFUL)); + stream->print(getStatusBit(MEASUREMENT_ATTEMPTED)); + stream->print(getStatusBit(WAKE_SUCCESSFUL)); + stream->print(getStatusBit(WAKE_ATTEMPTED)); + stream->print(getStatusBit(POWER_SUCCESSFUL)); + stream->print(getStatusBit(POWER_ATTEMPTED)); + stream->print(getStatusBit(SETUP_SUCCESSFUL)); + } + stream->print(F(" reports ")); + if (variables[i] != nullptr) { + stream->print(variables[i]->getVarName()); + stream->print(F(" (")); + stream->print(variables[i]->getVarCode()); + stream->print(F(")")); + stream->print(F(" is ")); + stream->print(variables[i]->getValueString()); + stream->print(F(" ")); + stream->print(variables[i]->getVarUnit()); + } else { + stream->print(F("variable #")); + stream->print(i); + stream->print(F(" is ")); + stream->print(sensorValues[i]); + } + stream->println(); + } +} + + // This updates a sensor value by checking it's power, waking it, taking as many // readings as requested, then putting the sensor to sleep and powering down. -bool Sensor::update(void) { +bool Sensor::update() { bool ret_val = true; - // Check if the power is on, turn it on if not + // Check if the sensor power is on, turn it on if not + // NOTE: The check power on function does **not** check the status bits or + // timing values to check if the power is on, but instead checks the actual + // state of the power pin(s) to determine if the power is on. This means + // it's safe to clear the power bits before running this check. The check + // function will correctly reset the bits as necessary. + clearPowerStatus(); bool wasOn = checkPowerOn(); if (!wasOn) { powerUp(); } - // Check if it's awake/active, activate it if not + // Check if sensor is awake/active, activate it if not + // We're checking the wake status bits here, so don't clear them before this + // check! bool wasActive = getStatusBit(WAKE_SUCCESSFUL); + + // NOTE: Don't clear the wake bits/timing! + if (!wasActive) { // NOT yet awake // wait for the sensor to have been powered for long enough to respond @@ -379,20 +542,45 @@ bool Sensor::update(void) { // bail if the wake failed if (!ret_val) return ret_val; - // Clear values before starting loop - clearValues(); + // Clear measurement related status bits and timing values before starting + // measurements + // NOTE: These bits are set and checked **after** starting a measurement to + // confirm that the measurement was actually started, so it's safe to clear + // them before starting a measurement. + clearMeasurementStatus(); + + // Reset the measurement attempt and retry counts before starting new + // measurements. + resetMeasurementCounts(); // Wait for the sensor to stabilize waitForStability(); - // loop through as many measurements as requested - for (uint8_t j = 0; j < _measurementsToAverage; j++) { + // loop through until we have the requested number of successful + // measurements + // NOTE: The number of measurement attempts completed is bumped by + // finalizeMeasurementAttempt() only after success or the last retry + // attempt, so it's safe to check this value to determine if we have the + // requested number of successful measurements. + while (_completedMeasurements < _measurementsToAverage) { // start a measurement ret_val &= startSingleMeasurement(); - // wait for the measurement to finish - waitForMeasurementCompletion(); - // get the measurement result - ret_val &= addSingleMeasurementResult(); + if (!getStatusBit(MEASUREMENT_SUCCESSFUL)) { + // If the measurement didn't start, bump the measurement attempt + // count to trigger retry logic, but skip waiting for measurement + // completion and adding a result since we didn't actually start a + // measurement + finalizeMeasurementAttempt(false); + } else { + // wait for the measurement to finish + waitForMeasurementCompletion(); + + // get the measurement result - this should call + // finalizeMeasurementAttempt() to update the measurement attempt + // and retry counts as needed so we don't call that function + // directly here + ret_val &= addSingleMeasurementResult(); + } } averageMeasurements(); @@ -416,31 +604,7 @@ bool Sensor::checkPowerOn(bool debug) { MS_DBG(F("Checking power status: Power to"), getSensorNameAndLocation()); } - if (_powerPin >= 0) { - auto powerBitNumber = - static_cast(log(digitalPinToBitMask(_powerPin)) / log(2)); - - if (bitRead(*portInputRegister(digitalPinToPort(_powerPin)), - powerBitNumber) == LOW) { - if (debug) { MS_DBG(F("was off.")); } - // Reset time of power on, in-case it was set to a value - _millisPowerOn = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - return false; - } else { - if (debug) { MS_DBG(" was on."); } - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - // Set the status bit for sensor power attempt (bit 1) and success - // (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); - return true; - } - } else { + if (_powerPin < 0 && _powerPin2 < 0) { if (debug) { MS_DBG(F("is not controlled by this library.")); } // Mark the power-on time, just in case it had not been marked if (_millisPowerOn == 0) _millisPowerOn = millis(); @@ -449,6 +613,32 @@ bool Sensor::checkPowerOn(bool debug) { setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); return true; } + bool pp1_off = isPinLow(_powerPin); + bool pp2_off = isPinLow(_powerPin2); + + if (pp1_off || pp2_off) { + if (debug) { MS_DBG(F("was off.")); } + // Unset time of power on, in-case it was set to a value + _millisPowerOn = 0; + // Unset the activation time + _millisSensorActivated = 0; + // Unset the measurement request time + _millisMeasurementRequested = 0; + // Unset the status bits for sensor power (bits 1 & 2), + // activation (bits 3 & 4), and measurement request (bits 5 & 6) + clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, + WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, + MEASUREMENT_SUCCESSFUL); + return false; + } else { + if (debug) { MS_DBG(F("was on.")); } + // Mark the power-on time, just in case it had not been marked + if (_millisPowerOn == 0) _millisPowerOn = millis(); + // Set the status bit for sensor power attempt (bit 1) and success + // (bit 2) + setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); + return true; + } } @@ -483,9 +673,9 @@ bool Sensor::isWarmedUp(bool debug) { // is - to be ready to communicate and to be asked to take readings // NOTE: This is "blocking" - that is, nothing else can happen during this // wait. -void Sensor::waitForWarmUp(void) { +void Sensor::waitForWarmUp() { while (!isWarmedUp()) { - // wait + yield(); // Allow other tasks to run } } @@ -502,6 +692,16 @@ bool Sensor::isStable(bool debug) { return true; } + // If we're taking a repeat measurement, we may have already waited for + // stabilization after the initial wake, so we can skip this wait. + if (_currentRetries != 0) { + if (debug) { + MS_DBG(getSensorNameAndLocation(), + F("is retrying and doesn't need to stabilize again.")); + } + return true; + } + uint32_t elapsed_since_wake_up = millis() - _millisSensorActivated; // If the sensor has been activated and enough time has elapsed, it's stable if (elapsed_since_wake_up > _stabilizationTime_ms) { @@ -521,9 +721,9 @@ bool Sensor::isStable(bool debug) { // taking readings // NOTE: This is "blocking" - that is, nothing else can happen during this // wait. -void Sensor::waitForStability(void) { +void Sensor::waitForStability() { while (!isStable()) { - // wait + yield(); // Allow other tasks to run } } @@ -560,8 +760,59 @@ bool Sensor::isMeasurementComplete(bool debug) { // This delays until enough time has passed for the sensor to give a new value // NOTE: This is "blocking" - that is, nothing else can happen during this // wait. -void Sensor::waitForMeasurementCompletion(void) { +void Sensor::waitForMeasurementCompletion() { while (!isMeasurementComplete()) { - // wait + yield(); // Allow other tasks to run + } +} + + +bool Sensor::finalizeMeasurementAttempt(bool wasSuccessful) { + // Record the time that the measurement was completed + _millisMeasurementCompleted = millis(); + // Unset the time stamp for the beginning of this measurement + _millisMeasurementRequested = 0; + // Unset the status bits for a measurement request (bits 5 & 6) + clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + + if (wasSuccessful || _currentRetries >= _maxRetries) { + // Bump the number of completed measurement attempts - we've succeeded + // or failed but reached the retry limit + _completedMeasurements++; + // Reset the number of retries made for the next measurement attempt + _currentRetries = 0; + } else { + // Bump the number of attempted retries + _currentRetries++; + } + + // Return the input parameter so it's easy to use this in a return statement + // to pass forward a value + return wasSuccessful; +} + + +// Common initialization for addSingleMeasurementResult implementations +bool Sensor::initializeMeasurementResult() { + // If it's the first attempt, clear all stale values in the value array + if (getCompletedMeasurements() == 0 && getCurrentRetries() == 0) { + clearValues(); + } + // Immediately quit if the measurement was not successfully started + if (!getStatusBit(MEASUREMENT_SUCCESSFUL)) { + finalizeMeasurementAttempt(false); + return false; + } + return true; +} + + +// Helper function to check if a pin is physically LOW +bool Sensor::isPinLow(int8_t pin) { + if (pin < 0) { + return false; // Unconfigured pins are treated as not LOW } + uint8_t bitMask = digitalPinToBitMask(pin); + uint8_t portValue = *portInputRegister(digitalPinToPort(pin)); + return (portValue & bitMask) == 0; } diff --git a/src/SensorBase.h b/src/SensorBase.h index 44e77fdc2..807559ef3 100644 --- a/src/SensorBase.h +++ b/src/SensorBase.h @@ -43,7 +43,8 @@ // Include other in-library and external dependencies #include -class Variable; // Forward declaration +class Variable; // Forward declaration +class VariableArray; // Forward declaration /** * @brief The "Sensor" class is used for all sensor-level operations - waking, @@ -62,6 +63,8 @@ class Variable; // Forward declaration */ class Sensor { public: + friend class Variable; + friend class VariableArray; /** * @brief Construct a new Sensor object. * @@ -115,7 +118,7 @@ class Sensor { /** * @brief Destroy the Sensor object - no action taken. */ - virtual ~Sensor(); + virtual ~Sensor() = default; // These functions are dependent on the constructor and return the // constructor values. @@ -127,26 +130,60 @@ class Sensor { * * @return Text describing how the sensor is attached to the mcu. */ - virtual String getSensorLocation(void); + virtual String getSensorLocation(); /** * @brief Get the name of the sensor. * * @return The sensor name as given in the constructor. */ - virtual String getSensorName(void); + virtual String getSensorName(); /** * @brief Concatenate and returns the name and location of the sensor. * * @return A concatenation of the sensor name and its "location" * - how it is connected to the mcu. */ - String getSensorNameAndLocation(void); + String getSensorNameAndLocation(); /** * @brief Get the pin number controlling sensor power. * * @return The pin on the mcu controlling power to the sensor. */ - virtual int8_t getPowerPin(void); + virtual int8_t getPowerPin(); + /** + * @brief Set the pin number controlling sensor power. + * + * @warning Only change power pins when the sensor is powered off to avoid + * leaving old pins HIGH and potentially damaging circuits or causing + * power management conflicts. + * + * @param pin The pin on the mcu controlling power to the sensor. + */ + virtual void setPowerPin(int8_t pin); + /** + * @brief Get the pin number controlling secondary sensor power. + * + * @return The pin on the mcu controlling secondary power + * + * This is for a second power needed to communicate with a sensor. Generally + * to an adapter or converter needed to talk to the sensor - i.e., an RS232 + * adapter, an RS485 adapter, or an IO multiplexer. + */ + virtual int8_t getSecondaryPowerPin(); + /** + * @brief Set the pin number controlling secondary sensor power. + * + * This is for a second power needed to communicate with a sensor. Generally + * to an adapter or converter needed to talk to the sensor - i.e., an RS232 + * adapter, an RS485 adapter, or an IO multiplexer. + * + * @warning Only change power pins when the sensor is powered off to avoid + * leaving old pins HIGH and potentially damaging circuits or causing + * power management conflicts. + * + * @param pin The pin on the mcu controlling secondary power + */ + virtual void setSecondaryPowerPin(int8_t pin); /** * @brief Set the number measurements to average. @@ -165,8 +202,80 @@ class Sensor { * * @copydetails _measurementsToAverage */ - uint8_t getNumberMeasurementsToAverage(void); + uint8_t getNumberMeasurementsToAverage(); + /** + * @brief Get the number of measurement attempts that have been made and + * completed (whether successful or not). + * + * @return The number of complete measurement attempts. + */ + uint8_t getCompletedMeasurements(); + /** + * @brief Get the number of retry attempts that have been made for the + * current measurement cycle + * + * @return The number of retries that have been made for the current + * measurement cycle. + * + * @note What is "successful" vs what qualifies for a retry varies by + * sensor. For some it may be that if any values were returned, for others + * that a specific value is in range, etc. + */ + uint8_t getCurrentRetries(); + /** + * @brief Get the maximum number of retries allowed if a measurement fails. + * + * @return The maximum number of allowed retries. + */ + uint8_t getMaxRetries(); + /** + * @brief Set the maximum number of retries if a measurement fails. + * + * @param maxRetries The maximum number of allowed retries. + */ + void setMaxRetries(uint8_t maxRetries); + + // _warmUpTime_ms _stabilizationTime_ms _measurementTime_ms + /** + * @brief Set the warm-up time for the sensor. + * @param warmUpTime_ms The warm-up time in milliseconds. + * @remark This is the time between when the sensor is powered on and when + * it is ready to receive a wake command. It should be set in the sensor + * constructor. Only change this if you know what you're doing! + */ + void setWarmUpTime(uint32_t warmUpTime_ms); + /** + * @brief Get the warm-up time for the sensor. + * @return The warm-up time in milliseconds. + */ + uint32_t getWarmUpTime(); + /** + * @brief Set the stabilization time for the sensor. + * @param stabilizationTime_ms The stabilization time in milliseconds. + * @remark This is the time between when the sensor receives a wake command + * and when it is able to return stable values. It should be set in the + * sensor constructor. Only change this if you know what you're doing! + */ + void setStabilizationTime(uint32_t stabilizationTime_ms); + /** + * @brief Get the stabilization time for the sensor. + * @return The stabilization time in milliseconds. + */ + uint32_t getStabilizationTime(); + /** + * @brief Set the measurement time for the sensor. + * @param measurementTime_ms The measurement time in milliseconds. + * @remark This is the time between when a measurement is started and when + * the result value is available. It should be set in the sensor + * constructor. Only change this if you know what you're doing! + */ + void setMeasurementTime(uint32_t measurementTime_ms); + /** + * @brief Get the measurement time for the sensor. + * @return The measurement time in milliseconds. + */ + uint32_t getMeasurementTime(); /// @brief The significance of the various status bits typedef enum { @@ -198,8 +307,8 @@ class Sensor { * ready for sensor communication. * * Bit 3 - * - 0 => Activation/wake attempt made - * - 1 => No activation/wake attempt made + * - 0 => No Activation/wake attempt made + * - 1 => Activation/wake attempt made * - check _millisSensorActivated or bit 4 to see if wake() attempt was * successful * - a failed activation attempt will give _millisSensorActivated = 0 @@ -211,8 +320,8 @@ class Sensor { * a measurement. * * Bit 5 - * - 0 => Start measurement requested attempt made - * - 1 => No measurements have been requested + * - 0 => No measurements have been requested + * - 1 => Measurement start requested * - check _millisMeasurementRequested or bit 6 to see if * startSingleMeasurement() attempt was successful * - a failed request attempt will give _millisMeasurementRequested = 0 @@ -229,7 +338,7 @@ class Sensor { * * @return The status as a uint8_t. */ - uint8_t getStatus(void); + uint8_t getStatus(); /** * @brief Get the value of a specific status bit. @@ -263,7 +372,7 @@ class Sensor { } /** - * @brief Clear a specific status bit (ie, set it to 0). + * @brief Clear a specific status bit (i.e., set it to 0). * * @param bitToClear The status bit to clear. */ @@ -294,7 +403,7 @@ class Sensor { * * @return True if the setup was successful. */ - virtual bool setup(void); + virtual bool setup(); /** * @brief Update the sensor's values. @@ -316,23 +425,27 @@ class Sensor { * @return True if all steps of the sensor update completed * successfully. */ - virtual bool update(void); + virtual bool update(); /** * @brief Turn on the sensor power, if applicable. * * Generally this is done by setting the #_powerPin `HIGH`. Also sets the * #_millisPowerOn timestamp and updates the #_sensorStatus. + * + * @todo Universally support power pins that are active LOW. */ - virtual void powerUp(void); + virtual void powerUp(); /** * @brief Turn off the sensor power, if applicable. * * Generally this is done by setting the #_powerPin `LOW`. Also un-sets * the #_millisPowerOn timestamp (sets #_millisPowerOn to 0) and updates the * #_sensorStatus. + * + * @todo Universally support power pins that are inactive HIGH. */ - virtual void powerDown(void); + virtual void powerDown(); /** * @brief Wake the sensor up, if necessary. Do whatever it takes to get a @@ -341,11 +454,18 @@ class Sensor { * Verifies that the power is on and updates the #_sensorStatus. This also * sets the #_millisSensorActivated timestamp. * - * @note This does NOT include any wait for sensor readiness. + * @note This does NOT include any wait time for sensor warm-up/readiness. + * Between powering the sensor and calling the wake function, most sensors + * have a required warm up time. Use the separate isWarmedUp() function to + * check if the sensor is warmed up and use waitForWarmUp() to hold until + * the sensor is warmed up if needed. The warm up time required for each + * sensor is held in the _warmUpTime_ms variable. For *most* sensors, the + * warm up is complete when millis() - _millisPowerOn > _warmUpTime_ms. The + * _millisPowerOn variable is set in the powerUp() function. * * @return True if the wake function completed successfully. */ - virtual bool wake(void); + virtual bool wake(); /** * @brief Puts the sensor to sleep, if necessary. * @@ -355,7 +475,7 @@ class Sensor { * * @return True if the sleep function completed successfully. */ - virtual bool sleep(void); + virtual bool sleep(); /** * @brief Tell the sensor to start a single measurement, if needed. @@ -364,12 +484,32 @@ class Sensor { * #_sensorStatus. * * @note This function does NOT include any waiting for the sensor to be - * warmed up or stable! + * warmed up or stable. Between waking and starting measurements, most + * sensors have a required stabilization time. Use the separate isStable() + * function to check if the sensor ready to start a measurement and use + * waitForStability() to hold until the sensor is ready if needed. The + * stabilization time required for each sensor is held in the + * _stabilizationTime_ms variable. For *most* sensors, the warm up is + * complete when millis() - _millisSensorActivated > _stabilizationTime_ms. + * The _millisSensorActivated variable is set in the wake() function. * * @return True if the start measurement function completed * successfully. */ - virtual bool startSingleMeasurement(void); + virtual bool startSingleMeasurement(); + + /** + * @brief Common initialization logic for addSingleMeasurementResult(). + * + * This function performs the standard checks and initialization required + * at the start of every addSingleMeasurementResult() implementation: + * - Clears values array on first measurement attempt + * - Checks if measurement was successfully started + * + * @return True if initialization succeeded and measurement processing + * should continue, false if the measurement attempt should be aborted. + */ + bool initializeMeasurementResult(); /** * @brief Get the results from a single measurement. @@ -385,36 +525,89 @@ class Sensor { * * @return True if the function completed successfully. */ - virtual bool addSingleMeasurementResult(void) = 0; + virtual bool addSingleMeasurementResult() = 0; /** * @brief The array of result values for each sensor. * + * New valid values are summed with the current values by + * verifyAndAddMeasurementResult(). Values are set to the default invalid + * value by clearValues(). + * * @todo Support int16_t and int32_t directly in the value array so no * casting is needed. This could be done using a template or a union similar - * to the modbus library's leFrame union. + * to the SensorModbusMaster library's leFrame union. + * + * @note The values in this array will not be usable until after the sensor + * completes all requested measurements! Prior to that, the values in this + * array will be the **sum** of all good values measured so far (or + * #MS_INVALID_VALUE if no good values have been measured yet). */ float sensorValues[MAX_NUMBER_VARS]; /** - * @brief Clear the values array - that is, sets all values to -9999. + * @brief Clear the values array and the count of values to average. + * + * This clears the values array by setting all values in sensorValues to + * #MS_INVALID_VALUE and the count of values to average in validCount to 0. */ void clearValues(); + /** - * @brief Verify that a measurement is OK (ie, not -9999) before adding it - * to the result array + * @brief Reset all measurement counts. * - * @param resultNumber The position of the result within the result array. - * @param resultValue The value of the result. + * Resets the attempt (#_completedMeasurements) and retry (#_currentRetries) + * counts. */ - void verifyAndAddMeasurementResult(uint8_t resultNumber, float resultValue); + void resetMeasurementCounts(); + /** - * @brief Verify that a measurement is OK (ie, not -9999) before adding it - * to the result array + * @brief This clears all of the status bits and resets timing values. + * + * This clears all status bits except the setup bit (bit 0) - and the error + * bit (bit 7) - that is, it sets bits 1-6 to 0. It also sets all timing + * variables to 0. This is intended to be used at the start of an update + * cycle to clear any old values before beginning a cycle. + */ + void clearStatus(); + /** + * @brief Clears power-related status bits and resets power timing. + * + * This clears the POWER_ATTEMPTED and POWER_SUCCESSFUL status bits and + * resets the _millisPowerOn timing variable to 0. This is useful when + * you need to clear only the power-related status without affecting + * wake or measurement status. + */ + void clearPowerStatus(); + /** + * @brief Clears wake-related status bits and resets wake timing. + * + * This clears the WAKE_ATTEMPTED and WAKE_SUCCESSFUL status bits and + * resets the _millisSensorActivated timing variable to 0. This is useful + * when you need to clear only the wake-related status without affecting + * power or measurement status. + */ + void clearWakeStatus(); + /** + * @brief Clears measurement-related status bits and resets measurement + * timing. + * + * This clears the MEASUREMENT_ATTEMPTED and MEASUREMENT_SUCCESSFUL status + * bits and resets both _millisMeasurementRequested and + * _millisMeasurementCompleted timing variables to 0. This is useful when + * you need to clear only the measurement-related status without affecting + * power or wake status. + */ + void clearMeasurementStatus(); + /** + * @brief Verify that a measurement is OK (i.e., not #MS_INVALID_VALUE) + * before adding it to the result array * * @param resultNumber The position of the result within the result array. * @param resultValue The value of the result. */ + void verifyAndAddMeasurementResult(uint8_t resultNumber, float resultValue); + /// @copydoc verifyAndAddMeasurementResult(uint8_t, float) void verifyAndAddMeasurementResult(uint8_t resultNumber, int16_t resultValue); /** @@ -430,7 +623,31 @@ class Sensor { * @brief Average the results of all measurements by dividing the sum of * all measurements by the number of measurements taken. */ - void averageMeasurements(void); + void averageMeasurements(); + + /** + * @brief Print out the results from a measurement - whether or not there is + * a variable attached to the sensor to receive the results. + * + * If there is a variable attached to the sensor, the printed info will + * include the variable name, units, and code and the value will be + * formatted with the correct number of decimal places. If there is no + * variable attached to the sensor, the printed info will only include the + * variable number position and the value will printed to two decimal + * places. + * + * @todo Figure out a way to have a sensor figure out the metadata about its + * own variables. Currently that is stored in the variable objects, but it + * is also a property of the sensor's results. + * See issue [498](https://github.com/EnviroDIY/ModularSensors/issues/498) + * for more discussion. + * + * @param stream An Arduino Stream instance + * @param printStatusBits True to include the sensor status bits in the + * printout, false to only print values and metadata. + */ + virtual void printData(Stream* stream = &Serial, + bool printStatusBits = false); /** * @brief Register a variable object to a sensor. @@ -445,7 +662,7 @@ class Sensor { /** * @brief Notify attached variables of new values. */ - void notifyVariables(void); + void notifyVariables(); /** @@ -471,7 +688,7 @@ class Sensor { * @brief Hold all further program execution until this sensor is ready to * receive commands. */ - void waitForWarmUp(void); + void waitForWarmUp(); /** * @brief Check whether or not enough time has passed between the sensor @@ -490,7 +707,7 @@ class Sensor { * @brief Hold all further program execution until this sensor is reporting * stable values. */ - void waitForStability(void); + void waitForStability(); /** * @brief Check whether or not enough time has passed between when the @@ -510,7 +727,7 @@ class Sensor { * @brief Hold all further program execution until this sensor is has * finished the current measurement. */ - void waitForMeasurementCompletion(void); + void waitForMeasurementCompletion(); protected: @@ -526,6 +743,16 @@ class Sensor { * @note SIGNED int, to allow negative numbers for unused pins */ int8_t _powerPin; + /** + * @brief Digital pin number on the mcu controlling secondary power + * + * This is for a second power needed to communicate with a sensor. Generally + * to an adapter or converter needed to talk to the sensor - i.e., an RS232 + * adapter, an RS485 adapter, or an IO multiplexer. + * + * @note SIGNED int, to allow negative numbers for unused pins + */ + int8_t _powerPin2 = -1; /** * @brief The sensor name. */ @@ -538,31 +765,68 @@ class Sensor { * are *included* in this total. */ const uint8_t _numReturnedValues; + /** + * @brief The number of included calculated variables from the sensor, if + * any. + * + * These are used for values that we would always calculate for a sensor and + * depend only on the raw results of that single sensor. This is separate + * from any calculated variables that are created on-the-fly and depend on + * multiple other sensors. + */ + const uint8_t _incCalcValues; /** * @brief The number of measurements from the sensor to average. * * This will become the number of readings actually taken by a sensor prior - * to data averaging. Any "bad" (-9999) values returned by the sensor will - * not be included in the final averaging. This means that the actual - * number of "good" values that are averaged may be less than what was - * requested. + * to data averaging. Any "bad" (#MS_INVALID_VALUE) values returned by the + * sensor will not be included in the final averaging. This means that the + * actual number of "good" values that are averaged may be less than what + * was requested. */ uint8_t _measurementsToAverage; /** - * @brief The number of included calculated variables from the - * sensor, if any. + * @brief The number of measurement cycles completed in the current update + * cycle (reset by resetMeasurementCounts()). * - * These are used for values that we would always calculate for a sensor and - * depend only on the raw results of that single sensor. This is separate - * from any calculated variables that are created on-the-fly and depend on - * multiple other sensors. + * A single completed measurement may include many attempts if some attempts + * fail and require retries. This count is incremented when a measurement + * attempt is completed, regardless of whether it was successful or not. + * Multiple completed measurement attempts may be needed to get the + * requested number of good measurements to average, depending on the sensor + * and the environment. + */ + uint8_t _completedMeasurements = 0; + /** + * @brief The number of retries that have been attempted so far for the + * current measurement cycle (reset by resetMeasurementCounts()). + * + * The number of retries is separate from the number of completed + * measurements. Many retries may be needed to complete a single + * measurement. Many measurements may need to be taken in order to get + * values to average. */ - uint8_t _incCalcValues; + uint8_t _currentRetries = 0; /** - * @brief Array with the number of valid measurement values taken by the - * sensor in the current update cycle. + * @brief The maximum number of retries allowed if a measurement fails. + * Setting max retries to 1 means that if a measurement fails, it will be + * retried once. Setting max retries to 0 means that if a measurement + * fails, it will not be retried at all. */ - uint8_t numberGoodMeasurementsMade[MAX_NUMBER_VARS]; + uint8_t _maxRetries = 1; + /** + * @brief Array with the number of valid measurement values per variable + * that have been summed into the sensorValues array. + * + * This is bumped by verifyAndAddMeasurementResult and reset by + * clearValues(). + * + * @note The number of good measurements may vary between variables if + * some values are more likely to be invalid than others - i.e., a pH sensor + * may also measure temperature and report a valid temperature when the pH + * is junk. + */ + uint8_t validCount[MAX_NUMBER_VARS]; /** * @brief The time needed from the when a sensor has power until it's ready @@ -584,7 +848,7 @@ class Sensor { */ uint32_t _stabilizationTime_ms; /** - * @brief The processor elapsed time when the sensor was activated - ie, + * @brief The processor elapsed time when the sensor was activated - i.e., * when the wake() function was run. * * The #_millisSensorActivated value is *usually* set in the wake() @@ -599,7 +863,7 @@ class Sensor { */ uint32_t _measurementTime_ms; /** - * @brief The processor elapsed time when a measurement was started - ie, + * @brief The processor elapsed time when a measurement was started - i.e., * when the startSingleMeasurement() function was run. * * The #_millisMeasurementRequested value is set in the @@ -607,6 +871,14 @@ class Sensor { * addSingleMeasurementResult() function. */ uint32_t _millisMeasurementRequested = 0; + /** + * @brief The processor elapsed time when a measurement was completed - + * i.e., when the addSingleMeasurementResult() function was run. + * + * The #_millisMeasurementCompleted value is set in the + * addSingleMeasurementResult() function. + */ + uint32_t _millisMeasurementCompleted = 0; /** * @brief An 8-bit code for the sensor status @@ -615,11 +887,43 @@ class Sensor { /** * @brief An array for each sensor containing pointers to the variable - * objects tied to that sensor. The #MAX_NUMBER_VARS cannot be determined - * on a per-sensor basis, because of the way memory is used on an Arduino. - * It must be defined once for the whole class. + * objects tied to that sensor. + * + * The #MAX_NUMBER_VARS cannot be determined on a per-sensor basis; it must + * be defined at compile time and applied to the entire class. */ Variable* variables[MAX_NUMBER_VARS]; + + /** + * @brief Finalizes a measurement attempt by updating timestamps, status + * bits, and retry counts. + * + * This method performs multiple cleanup actions after a measurement + * attempt: + * - Records measurement completion timestamp + * - Clears measurement request status bits + * - Updates retry counters with proper semantics + * - Conditionally increments completed measurements based on success or + * retry limit + * + * @param wasSuccessful True if the measurement was successful, false + * otherwise + * @return The input parameter for easy chaining in return statements + */ + bool finalizeMeasurementAttempt(bool wasSuccessful); + + private: + /** + * @brief Helper function to check if a pin is physically LOW + * + * Reads the current physical state of a pin using direct port access. + * Returns true if the pin is configured and physically LOW, false if + * the pin is unconfigured (< 0) or HIGH. + * + * @param pin The pin number to check + * @return True if pin is configured and physically LOW, false otherwise + */ + bool isPinLow(int8_t pin); }; #endif // SRC_SENSORBASE_H_ diff --git a/src/VariableArray.cpp b/src/VariableArray.cpp index b713b4e69..0d0baeb57 100644 --- a/src/VariableArray.cpp +++ b/src/VariableArray.cpp @@ -12,51 +12,59 @@ // Constructors -VariableArray::VariableArray() {} -VariableArray::VariableArray(uint8_t variableCount, Variable* variableList[]) - : arrayOfVars(variableList), - _variableCount(variableCount) { - _maxSamplesToAverage = countMaxToAverage(); - _sensorCount = getSensorCount(); -} +// Primary constructor with all parameters - ensures proper initialization order VariableArray::VariableArray(uint8_t variableCount, Variable* variableList[], const char* uuids[]) : arrayOfVars(variableList), _variableCount(variableCount) { - _maxSamplesToAverage = countMaxToAverage(); - _sensorCount = getSensorCount(); - matchUUIDs(uuids); + // Match UUIDs first, before populating sensor list + if (uuids && arrayOfVars != nullptr && _variableCount > 0) + matchUUIDs(uuids); + populateSensorList(); } -// Destructor -VariableArray::~VariableArray() {} +// Delegating constructor - delegates to primary constructor with null UUIDs +VariableArray::VariableArray(uint8_t variableCount, Variable* variableList[]) + : VariableArray(variableCount, variableList, nullptr) {} +// Default constructor with no arguments - delegates to ensure all members are +// initialized +VariableArray::VariableArray() : VariableArray(0, nullptr) {} + void VariableArray::begin(uint8_t variableCount, Variable* variableList[], const char* uuids[]) { _variableCount = variableCount; arrayOfVars = variableList; - - _maxSamplesToAverage = countMaxToAverage(); - _sensorCount = getSensorCount(); + if (_variableCount == 0 || arrayOfVars == nullptr) { + MS_DBG(F("No variable array in the VariableArray object!")); + return; + } matchUUIDs(uuids); + if (!populateSensorList()) { + MS_DBG(F("Warning: Sensor list may be truncated - exceeded " + "MAX_NUMBER_SENSORS limit.")); + } checkVariableUUIDs(); } void VariableArray::begin(uint8_t variableCount, Variable* variableList[]) { _variableCount = variableCount; arrayOfVars = variableList; - - _maxSamplesToAverage = countMaxToAverage(); - _sensorCount = getSensorCount(); - checkVariableUUIDs(); + begin(); } void VariableArray::begin() { - _maxSamplesToAverage = countMaxToAverage(); - _sensorCount = getSensorCount(); + if (_variableCount == 0 || arrayOfVars == nullptr) { + MS_DBG(F("No variable array in the VariableArray object!")); + return; + } + if (!populateSensorList()) { + MS_DBG(F("Warning: Sensor list may be truncated - exceeded " + "MAX_NUMBER_SENSORS limit.")); + } checkVariableUUIDs(); } // This counts and returns the number of calculated variables -uint8_t VariableArray::getCalculatedVariableCount(void) { +uint8_t VariableArray::getCalculatedVariableCount() { uint8_t numCalc = 0; // Check for unique sensors for (uint8_t i = 0; i < _variableCount; i++) { @@ -67,29 +75,190 @@ uint8_t VariableArray::getCalculatedVariableCount(void) { } -// This counts and returns the number of sensors -uint8_t VariableArray::getSensorCount(void) { - uint8_t numSensors = 0; - // Check for unique sensors - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i)) numSensors++; - } - return numSensors; +// This returns the number of sensors +uint8_t VariableArray::getSensorCount() { + return _sensorCount; } -// This matches UUID's from an array of pointers to the variable array +// This matches UUIDs from an array of pointers to the variable array void VariableArray::matchUUIDs(const char* uuids[]) { + if (uuids == nullptr) return; for (uint8_t i = 0; i < _variableCount; i++) { arrayOfVars[i]->setVarUUID(uuids[i]); } } +// This populates the internal sensor list from the variable array +bool VariableArray::populateSensorList() { + uint8_t addedSensors = 0; + + // Clear the sensor list first + for (uint8_t i = 0; i < MAX_NUMBER_SENSORS; i++) { + _sensorList[i] = nullptr; + } + + // Early exit if no valid variable array + if (arrayOfVars == nullptr) { + _sensorCount = 0; + return true; + } + + for (uint8_t i = 0; i < _variableCount; i++) { + // Skip calculated variables since they don't have parent sensors + if (arrayOfVars[i]->isCalculated) { continue; } + + Sensor* currentSensor = arrayOfVars[i]->parentSensor; + + // Skip variables with null parent sensors to avoid crashes + if (currentSensor == nullptr) { continue; } + + // Check if this sensor is already in the list + bool alreadyInList = false; + for (uint8_t j = 0; j < addedSensors; j++) { + if (_sensorList[j] == currentSensor) { + alreadyInList = true; + break; + } + } + + // If not already in list, add it + if (!alreadyInList) { + if (addedSensors >= MAX_NUMBER_SENSORS) { + // Unfortunately silent so this can be run in the constructor + // before the serial port is set up. + return false; + } + _sensorList[addedSensors++] = currentSensor; + } + } + + // Update the sensor count to match what we actually found + _sensorCount = addedSensors; + + return true; +} + +// Helper function to check if sensor wake failed or is not ready +inline bool VariableArray::isSensorWakeFailure(uint8_t sensorIndex, bool wake) { + bool wakeAttempted = + _sensorList[sensorIndex]->getStatusBit(Sensor::WAKE_ATTEMPTED) == 1; + bool wakeSuccessful = + _sensorList[sensorIndex]->getStatusBit(Sensor::WAKE_SUCCESSFUL) == 1; + bool wakeFailedAfterAttempt = wakeAttempted && !wakeSuccessful; + bool notWakingButNotAwake = !wake && (!wakeAttempted || !wakeSuccessful); + return wakeFailedAfterAttempt || notWakingButNotAwake; +} + +// Helper function to check if sensor should be woken up +inline bool VariableArray::shouldWakeSensor(uint8_t sensorIndex, bool wake, + bool deepDebugTiming) { + bool wakeAttempted = + _sensorList[sensorIndex]->getStatusBit(Sensor::WAKE_ATTEMPTED) == 1; + return wake && !wakeAttempted && + _sensorList[sensorIndex]->isWarmedUp(deepDebugTiming); +} + +// Helper function to check if sensor is ready to start measurements +inline bool VariableArray::isSensorReadyToMeasure(uint8_t sensorIndex) { + bool wakeSuccessful = + _sensorList[sensorIndex]->getStatusBit(Sensor::WAKE_SUCCESSFUL) == 1; + bool measurementAttempted = _sensorList[sensorIndex]->getStatusBit( + Sensor::MEASUREMENT_ATTEMPTED) == 1; + bool measurementSuccessful = _sensorList[sensorIndex]->getStatusBit( + Sensor::MEASUREMENT_SUCCESSFUL) == 1; + return wakeSuccessful && !measurementAttempted && !measurementSuccessful; +} + +// Helper function to check if measurements have been attempted +inline bool VariableArray::isMeasurementAttempted(uint8_t sensorIndex) { + return _sensorList[sensorIndex]->getStatusBit( + Sensor::MEASUREMENT_ATTEMPTED) == 1; +} + +// Helper function to check if all measurements are complete +inline bool VariableArray::areMeasurementsComplete(uint8_t sensorIndex) { + return _sensorList[sensorIndex]->getCompletedMeasurements() >= + _sensorList[sensorIndex]->getNumberMeasurementsToAverage(); +} + +// Helper function to check if two sensors share any power pins +inline bool VariableArray::sharesPowerPin(Sensor* a, Sensor* b) { + // Check if sensor a's primary pin matches either of sensor b's pins + if (a->getPowerPin() >= 0 && + (a->getPowerPin() == b->getPowerPin() || + a->getPowerPin() == b->getSecondaryPowerPin())) { + return true; + } + // Check if sensor a's secondary pin matches either of sensor b's pins + if (a->getSecondaryPowerPin() >= 0 && + (a->getSecondaryPowerPin() == b->getPowerPin() || + a->getSecondaryPowerPin() == b->getSecondaryPowerPin())) { + return true; + } + return false; +} + +// Helper function to check if sensor can be powered down safely with debug +// output +bool VariableArray::canPowerDownSensor(uint8_t sensorIndex) { + // NOTE: We are NOT checking if the sleep command succeeded! + // Check if it's safe to cut power to this sensor and all that share the pin + bool canPowerDown = + true; // assume we can power down unless we find a conflict + + for (uint8_t k = 0; k < _sensorCount; k++) { + if (k == sensorIndex) continue; // Skip self-comparison + + if (sharesPowerPin(_sensorList[sensorIndex], _sensorList[k]) && + (_sensorList[k]->getCompletedMeasurements() < + _sensorList[k]->getNumberMeasurementsToAverage())) { + // If sensors share a power pin and sensor k still needs + // measurements, can't power down + canPowerDown = false; + + // Debug output for conflict detection + MS_DBG(sensorIndex, F("--->> All measurements from"), + _sensorList[sensorIndex]->getSensorNameAndLocation(), + F("are complete but other sensors on the same power pin " + "still need to take measurements. " + "Leaving power on pin"), + _sensorList[sensorIndex]->getPowerPin(), F("ON. <<---")); + MS_DBG(_sensorList[sensorIndex]->getSensorNameAndLocation(), + F("shares a power pin with"), + _sensorList[k]->getSensorNameAndLocation(), + F("which still needs to take"), + _sensorList[k]->getNumberMeasurementsToAverage() - + _sensorList[k]->getCompletedMeasurements(), + F("measurements.")); + MS_DEEP_DBG(_sensorList[sensorIndex]->getSensorNameAndLocation(), + '(', sensorIndex, ')', F("pins are"), + _sensorList[sensorIndex]->getPowerPin(), F("and"), + _sensorList[sensorIndex]->getSecondaryPowerPin()); + MS_DEEP_DBG(_sensorList[k]->getSensorNameAndLocation(), '(', k, ')', + F("pins are"), _sensorList[k]->getPowerPin(), F("and"), + _sensorList[k]->getSecondaryPowerPin()); + + break; // stop looping after finding a conflict + } + } + + if (canPowerDown) { + MS_DBG(sensorIndex, F("--->> All measurements from"), + _sensorList[sensorIndex]->getSensorNameAndLocation(), + F("are complete and no other sensors on the same power pin need " + "to take measurements. This sensor can be powered down."), + F("<<---")); + } + + return canPowerDown; +} + // Public functions for interfacing with a list of sensors // This sets up all of the sensors in the list // NOTE: Calculated variables will always be skipped in this process because // a calculated variable will never be marked as the last variable from a // sensor. -bool VariableArray::setupSensors(void) { +bool VariableArray::setupSensors() { bool success = true; // #ifdef MS_VARIABLEARRAY_DEBUG_DEEP @@ -103,17 +272,13 @@ bool VariableArray::setupSensors(void) { // Now run all the set-up functions MS_DBG(F("Running sensor setup functions.")); - // Check for any sensors that have been set up outside of this (ie, the + // Check for any sensors that have been set up outside of this (i.e., the // modem) uint8_t nSensorsSetup = 0; - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i) // Skip non-unique sensors - && getSensorStatusBit(i, Sensor::SETUP_SUCCESSFUL) == - 1 // already set up - ) { - MS_DBG(F(" "), arrayOfVars[i]->getParentSensorNameAndLocation(), + for (uint8_t i = 0; i < _sensorCount; i++) { + if (_sensorList[i]->getStatusBit(Sensor::SETUP_SUCCESSFUL) == 1) { + MS_DBG(F(" "), _sensorList[i]->getSensorNameAndLocation(), F("was already set up!")); - nSensorsSetup++; } } @@ -123,17 +288,12 @@ bool VariableArray::setupSensors(void) { // up and increment the counter marking that's been done. // We keep looping until they've all been done. while (nSensorsSetup < _sensorCount) { - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i) // Skip non-unique sensors - && getSensorStatusBit(i, Sensor::SETUP_SUCCESSFUL) == - 0 // only set up if it has not yet been set up - ) { + for (uint8_t i = 0; i < _sensorCount; i++) { + if (_sensorList[i]->getStatusBit(Sensor::SETUP_SUCCESSFUL) == 0) { MS_DBG(F(" Set up of"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); + _sensorList[i]->getSensorNameAndLocation(), F("...")); - bool sensorSuccess = - arrayOfVars[i]->parentSensor->setup(); // set it up + bool sensorSuccess = _sensorList[i]->setup(); // set it up success &= sensorSuccess; nSensorsSetup++; @@ -157,15 +317,12 @@ bool VariableArray::setupSensors(void) { // NOTE: Calculated variables will always be skipped in this process because // a calculated variable will never be marked as the last variable from a // sensor. -void VariableArray::sensorsPowerUp(void) { +void VariableArray::sensorsPowerUp() { MS_DBG(F("Powering up sensors...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i)) { // Skip non-unique sensors - MS_DBG(F(" Powering up"), - arrayOfVars[i]->getParentSensorNameAndLocation()); - - arrayOfVars[i]->parentSensor->powerUp(); - } + for (uint8_t i = 0; i < _sensorCount; i++) { + MS_DBG(F(" Powering up"), + _sensorList[i]->getSensorNameAndLocation()); + _sensorList[i]->powerUp(); } } @@ -175,7 +332,7 @@ void VariableArray::sensorsPowerUp(void) { // NOTE: Calculated variables will always be skipped in this process because // a calculated variable will never be marked as the last variable from a // sensor. -bool VariableArray::sensorsWake(void) { +bool VariableArray::sensorsWake() { MS_DBG(F("Waking sensors...")); bool success = true; uint8_t nSensorsAwake = 0; @@ -188,13 +345,10 @@ bool VariableArray::sensorsWake(void) { // Check for any sensors that are awake outside of being sent a "wake" // command - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i) // Skip non-unique sensors - && getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED) == - 1 // already attempted to wake - ) { + for (uint8_t i = 0; i < _sensorCount; i++) { + if (_sensorList[i]->getStatusBit(Sensor::WAKE_ATTEMPTED) == 1) { MS_DBG(F(" Wake up of"), - arrayOfVars[i]->getParentSensorNameAndLocation(), + _sensorList[i]->getSensorNameAndLocation(), F("has already been attempted.")); nSensorsAwake++; } @@ -205,20 +359,15 @@ bool VariableArray::sensorsWake(void) { // up and increment the counter marking that's been done. // We keep looping until they've all been done. while (nSensorsAwake < _sensorCount) { - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i) // Skip non-unique sensors - && getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED) == - 0 // If no attempts yet made to wake the sensor up - && arrayOfVars[i]->parentSensor->isWarmedUp( - deepDebugTiming) // and if it is already warmed up - ) { + for (uint8_t i = 0; i < _sensorCount; i++) { + if (_sensorList[i]->getStatusBit(Sensor::WAKE_ATTEMPTED) == 0 && + _sensorList[i]->isWarmedUp(deepDebugTiming)) { MS_DBG(F(" Wake up of"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); + _sensorList[i]->getSensorNameAndLocation(), F("...")); // Make a single attempt to wake the sensor after it is // warmed up - bool sensorSuccess = arrayOfVars[i]->parentSensor->wake(); + bool sensorSuccess = _sensorList[i]->wake(); success &= sensorSuccess; // We increment up the number of sensors awake/active, // even if the wake up command failed! @@ -242,22 +391,19 @@ bool VariableArray::sensorsWake(void) { // NOTE: Calculated variables will always be skipped in this process because // a calculated variable will never be marked as the last variable from a // sensor. -bool VariableArray::sensorsSleep(void) { +bool VariableArray::sensorsSleep() { MS_DBG(F("Putting sensors to sleep...")); bool success = true; - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i)) { // Skip non-unique sensors - MS_DBG(F(" "), arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); + for (uint8_t i = 0; i < _sensorCount; i++) { + MS_DBG(F(" "), _sensorList[i]->getSensorNameAndLocation(), F("...")); - bool sensorSuccess = arrayOfVars[i]->parentSensor->sleep(); - success &= sensorSuccess; + bool sensorSuccess = _sensorList[i]->sleep(); + success &= sensorSuccess; - if (sensorSuccess) { - MS_DBG(F(" ... successfully put to sleep.")); - } else { - MS_DBG(F(" ... failed to sleep!")); - } + if (sensorSuccess) { + MS_DBG(F(" ... successfully put to sleep.")); + } else { + MS_DBG(F(" ... failed to sleep!")); } } return success; @@ -269,15 +415,12 @@ bool VariableArray::sensorsSleep(void) { // NOTE: Calculated variables will always be skipped in this process because // a calculated variable will never be marked as the last variable from a // sensor. -void VariableArray::sensorsPowerDown(void) { +void VariableArray::sensorsPowerDown() { MS_DBG(F("Powering down sensors...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i)) { // Skip non-unique sensors - MS_DBG(F(" Powering down"), - arrayOfVars[i]->getParentSensorNameAndLocation()); - - arrayOfVars[i]->parentSensor->powerDown(); - } + for (uint8_t i = 0; i < _sensorCount; i++) { + MS_DBG(F(" Powering down"), + _sensorList[i]->getSensorNameAndLocation()); + _sensorList[i]->powerDown(); } } @@ -286,10 +429,12 @@ void VariableArray::sensorsPowerDown(void) { // Please note that this does NOT run the update functions, it instead uses // the startSingleMeasurement and addSingleMeasurementResult functions to // take advantage of the ability of sensors to be measuring concurrently. -// NOTE: Calculated variables will always be skipped in this process because -// a calculated variable will never be marked as the last variable from a -// sensor. -bool VariableArray::updateAllSensors(void) { +bool VariableArray::updateAllSensors() { + return completeUpdate(false, false, false, false); +} + +bool VariableArray::completeUpdate(bool powerUp, bool wake, bool sleep, + bool powerDown) { bool success = true; uint8_t nSensorsCompleted = 0; @@ -299,549 +444,224 @@ bool VariableArray::updateAllSensors(void) { bool deepDebugTiming = false; #endif - // Create an array with the unique-ness value (so we can skip the function - // calls later) - MS_DBG(F("Creating a mask array with the uniqueness for each sensor..")); - bool lastSensorVariable[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - lastSensorVariable[i] = isLastVarFromSensor(i); - } - - // Create an array for the number of measurements already completed and set - // all to zero - MS_DBG(F("Creating an array for the number of completed measurements..")); - uint8_t nMeasurementsCompleted[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - nMeasurementsCompleted[i] = 0; - } + MS_DBG(F("Using internal sensor list for measurements...")); - // Create an array for the number of measurements to average (another short - // cut) - MS_DBG(F("Creating an array with the number of measurements to average..")); - uint8_t nMeasurementsToAverage[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - nMeasurementsToAverage[i] = - arrayOfVars[i]->parentSensor->getNumberMeasurementsToAverage(); - } else { - nMeasurementsToAverage[i] = 0; - } +#if defined(MS_VARIABLEARRAY_DEBUG) || defined(MS_VARIABLEARRAY_DEBUG_DEEP) + for (uint8_t i = 0; i < _sensorCount; i++) { + MS_DBG(F(" Sensor"), i, F("is"), + _sensorList[i]->getSensorNameAndLocation()); } +#endif - // Clear the initial variable arrays - MS_DBG(F("----->> Clearing all results arrays before taking new " - "measurements. ...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - arrayOfVars[i]->parentSensor->clearValues(); + if (powerUp) { + // CLear power status bits before powering on. The powerUp function + // will check the actual power state of the sensor and set the bits + // accordingly, so it's safe to clear them here before powering up. + MS_DEEP_DBG( + F("----->> Clearing all power status bits before taking new " + "measurements. ...")); + for (uint8_t i = 0; i < _sensorCount; i++) { + _sensorList[i]->clearPowerStatus(); } - } - MS_DBG(F(" ... Complete. <<-----")); + MS_DEEP_DBG(F(" ... Complete. <<-----")); - // Check for any sensors that didn't wake up and mark them as "complete" so - // they will be skipped in further looping. - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i] // Skip non-unique sensors - && (getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED) == - 0 // No attempt made to wake the sensor up - || getSensorStatusBit(i, Sensor::WAKE_SUCCESSFUL) == - 0 // OR Wake up failed - )) { - MS_DBG(i, F("--->>"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("isn't awake/active! No measurements will be taken! " - "<<---"), - i); - - // Set the number of measurements already equal to whatever - // total number requested to ensure the sensor is skipped in - // further loops. - nMeasurementsCompleted[i] = nMeasurementsToAverage[i]; - // Bump up the finished count. - nSensorsCompleted++; + // power up all of the sensors together + MS_DBG(F("----->> Powering up all sensors together. ...")); + sensorsPowerUp(); + MS_DBG(F(" ... Complete. <<-----")); + } else { + // If this function isn't powering the sensors, check whether or not the + // sensors are actually powered on before trying to wake them or + // assuming they are awake. If the sensors are not powered, the + // checkPowerOn function will reset the power *and wake* bits so the + // wake check or wake function will work correctly. + MS_DBG(F("----->> Checking the power state of all sensors. ...")); + for (uint8_t i = 0; i < _sensorCount; i++) { + _sensorList[i]->checkPowerOn(); } + MS_DBG(F(" ... Complete. <<-----")); + } + + // NOTE: Don't clear the wake bits/timing! If the power up function found + // the sensor wasn't powered, it will have cleared the wake bits. If we + // clear the wake bits here before checking them, then we won't be able to + // tell if the sensor was already awake before this function was called. If + // this function is called with wake=false (i.e., expecting the sensors to + // have been awoken elsewhere), then we need to be able to check if the wake + // was successful before attempting readings, so we need to keep the wake + // bits intact. + + // Clear all measurement related status bits and timing values before + // starting measurements. NOTE: These bits are set and checked **after** + // starting a measurement to confirm that the measurement was actually + // started, so it's safe to clear them before starting a measurement. + MS_DEEP_DBG(F("----->> Clearing all measurement status bits before taking " + "new measurements. ...")); + for (uint8_t i = 0; i < _sensorCount; i++) { + _sensorList[i]->clearMeasurementStatus(); + } + MS_DEEP_DBG(F(" ... Complete. <<-----")); + + // Clear the initial variable values arrays and reset the measurement + // attempt and retry counts. + MS_DBG(F("----->> Resetting all measurement counts before taking new " + "measurements. ...")); + for (uint8_t i = 0; i < _sensorCount; i++) { + _sensorList[i]->resetMeasurementCounts(); } + MS_DBG(F(" ... Complete. <<-----")); while (nSensorsCompleted < _sensorCount) { - for (uint8_t i = 0; i < _variableCount; i++) { - /*** - // THIS IS PURELY FOR DEEP DEBUGGING OF THE TIMING! - // Leave this whole section commented out unless you want excessive - // printouts (ie, thousands of lines) of the timing information!! - if (lastSensorVariable[i] and - nMeasurementsToAverage[i] > nMeasurementsCompleted[i]) { - MS_DEEP_DBG( - i, '-', arrayOfVars[i]->getParentSensorNameAndLocation(), - F("- millis:"), millis(), F("- status: 0b"), - getSensorStatusBit(i, Sensor::ERROR_OCCURRED), - getSensorStatusBit(i, Sensor::MEASUREMENT_SUCCESSFUL), - getSensorStatusBit(i, Sensor::MEASUREMENT_ATTEMPTED), - getSensorStatusBit(i, Sensor::WAKE_SUCCESSFUL), - getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED), - getSensorStatusBit(i, Sensor::POWER_SUCCESSFUL), - getSensorStatusBit(i, Sensor::POWER_ATTEMPTED), - getSensorStatusBit(i, Sensor::SETUP_SUCCESSFUL), - F("-measurement #"), nMeasurementsCompleted[i] + 1); + for (uint8_t i = 0; i < _sensorCount; i++) { + uint8_t nReq = _sensorList[i]->getNumberMeasurementsToAverage(); +#if defined(MS_VARIABLEARRAY_DEBUG) || defined(MS_VARIABLEARRAY_DEBUG_DEEP) + String sName = _sensorList[i]->getSensorNameAndLocation(); + String cycCount = String(i) + '.' + + String(_sensorList[i]->getCompletedMeasurements() + 1) + '.' + + String(_sensorList[i]->getCurrentRetries()); +#endif + // Skip sensors that have already completed all their measurements + if (_sensorList[i]->getCompletedMeasurements() >= nReq) { + continue; } - // END CHUNK FOR DEBUGGING! - ***/ - - // Only do checks on sensors that still have measurements to finish - if (lastSensorVariable[i] && - nMeasurementsToAverage[i] > nMeasurementsCompleted[i]) { - // first, make sure the sensor is stable - if (arrayOfVars[i]->parentSensor->isStable(deepDebugTiming)) { - // now, if the sensor is not currently measuring... - if (getSensorStatusBit(i, Sensor::MEASUREMENT_ATTEMPTED) == - 0) { // NO attempt yet to start a measurement - // Start a reading - MS_DBG(i, '.', nMeasurementsCompleted[i] + 1, - F("--->> Starting reading"), - nMeasurementsCompleted[i] + 1, F("on"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - '-'); - - bool sensorSuccess_start = - arrayOfVars[i] - ->parentSensor->startSingleMeasurement(); - success &= sensorSuccess_start; - - if (sensorSuccess_start) { - MS_DBG(F(" ... reading started! <<---"), i, '.', - nMeasurementsCompleted[i] + 1); - } else { - MS_DBG(F(" ... failed to start reading! <<---"), - i, '.', nMeasurementsCompleted[i] + 1); - } - } - - // otherwise, it is currently measuring so... - // if a measurement is finished, get the result and tick up - // the number of finished measurements - // NOTE: isMeasurementComplete(deepDebugTiming) will - // immediately return true if the attempt to start a - // measurement failed (bit 6 not set). In that case, the - // addSingleMeasurementResult() will be "adding" -9999 - // values. - if (arrayOfVars[i]->parentSensor->isMeasurementComplete( - deepDebugTiming)) { - // Get the value - MS_DBG(i, '.', nMeasurementsCompleted[i] + 1, - F("--->> Collected result of reading"), - nMeasurementsCompleted[i] + 1, F("from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); - - bool sensorSuccess_result = - arrayOfVars[i] - ->parentSensor->addSingleMeasurementResult(); - success &= sensorSuccess_result; - nMeasurementsCompleted[i] += - 1; // increment the number of measurements that - // sensor has completed - - if (sensorSuccess_result) { - MS_DBG(F(" ... got measurement result. <<---"), i, - '.', nMeasurementsCompleted[i]); - } else { - MS_DBG(F(" ... failed to get measurement result! " - "<<---"), - i, '.', nMeasurementsCompleted[i]); - } - } - } - - // if all the measurements are done, mark the whole sensor as - // done - if (nMeasurementsCompleted[i] == nMeasurementsToAverage[i]) { - MS_DBG(F("--- Finished all measurements from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("---")); - nSensorsCompleted++; - MS_DBG(F("*****---"), nSensorsCompleted, - F("sensors now complete ---*****")); - } + // If attempts were made to wake the sensor, but they failed OR if + // we're not waking the sensor but it is not already awake or the + // previous wake attempts failed... + if (isSensorWakeFailure(i, wake)) { + MS_DBG(i, F("--->>"), sName, + F("failed to wake up! No measurements will be taken! " + "<<---"), + i); + success = false; + // Set the number of measurements already complete equal to + // whatever total number requested to ensure the sensor is + // skipped in further loops. NOTE: These are protected members + // of the Sensor class; we can only access them because the + // VariableArray class is a friend of the Sensor class. + _sensorList[i]->_completedMeasurements = + _sensorList[i]->_measurementsToAverage; } - } - } - - // Average measurements and notify variables of the updates - MS_DBG(F("----->> Averaging results and notifying all variables. ...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - MS_DEEP_DBG(F("--- Averaging results from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("---")); - arrayOfVars[i]->parentSensor->averageMeasurements(); - MS_DEEP_DBG(F("--- Notifying variables from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("---")); - arrayOfVars[i]->parentSensor->notifyVariables(); - } - } - MS_DBG(F("... Complete. <<-----")); - - MS_DBG(F("Updating calculated variables. ...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (arrayOfVars[i]->isCalculated) { arrayOfVars[i]->getValue(true); } - } - - return success; -} + if (shouldWakeSensor(i, wake, deepDebugTiming)) { + MS_DBG(i, F("--->> Waking"), sName, F("...")); -// This function is an even more complete version of the updateAllSensors -// function - it handles power up/down and wake/sleep. -bool VariableArray::completeUpdate(void) { - bool success = true; - uint8_t nSensorsCompleted = 0; - -#ifdef MS_VARIABLEARRAY_DEBUG_DEEP - bool deepDebugTiming = true; -#else - bool deepDebugTiming = false; -#endif - - // Create an array with the unique-ness value (so we can skip the function - // calls later) - MS_DBG(F("Creating a mask array with the uniqueness for each sensor..")); - bool lastSensorVariable[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - lastSensorVariable[i] = isLastVarFromSensor(i); - } - - // Create an array for the number of measurements already completed and set - // all to zero - MS_DBG(F("Creating an array for the number of completed measurements..")); - uint8_t nMeasurementsCompleted[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - nMeasurementsCompleted[i] = 0; - } - - // Create an array for the number of measurements to average (another short - // cut) - MS_DBG(F("Creating an array with the number of measurements to average..")); - uint8_t nMeasurementsToAverage[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - nMeasurementsToAverage[i] = - arrayOfVars[i]->parentSensor->getNumberMeasurementsToAverage(); - } else { - nMeasurementsToAverage[i] = 0; - } - } - - // Create an array of the power pins - MS_DBG(F("Creating an array of the power pins..")); - int8_t powerPins[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - powerPins[i] = arrayOfVars[i]->parentSensor->getPowerPin(); - } else { - powerPins[i] = 0; - } - } + // Make a single attempt to wake the sensor after it is + // warmed up + bool sensorSuccess_wake = _sensorList[i]->wake(); + success &= sensorSuccess_wake; - // Create an array of the last variable on each power pin - MS_DBG(F("Creating arrays of the power pin locations..")); - bool lastPinVariable[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { lastPinVariable[i] = true; } - // Create an array containing the index of the power pin in the powerPins - // array - int8_t powerPinIndex[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { powerPinIndex[i] = 0; } - // Create an array to tell us how many measurements must be taken - // before all the sensors attached to a power pin are done - uint8_t nMeasurementsOnPin[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - nMeasurementsOnPin[i] = nMeasurementsToAverage[i]; - } - // Now correctly populate the previous three arrays - for (uint8_t i = 0; i < _variableCount; i++) { - for (uint8_t j = i + 1; j < _variableCount; j++) { - if (!lastSensorVariable[i]) { - lastPinVariable[i] = false; - } else if (powerPins[i] == powerPins[j]) { - lastPinVariable[i] = false; - } - i++; - } - } - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastPinVariable[i]) { - powerPinIndex[i] = i; - nMeasurementsOnPin[i] = nMeasurementsToAverage[i]; - for (uint8_t j = 0; j < _variableCount; j++) { - if (powerPins[j] == powerPins[i] && i != j) { - powerPinIndex[j] = i; - nMeasurementsOnPin[i] += nMeasurementsToAverage[j]; - nMeasurementsOnPin[j] = 0; + if (sensorSuccess_wake) { + MS_DBG(F(" ... wake up succeeded. <<---"), i); + } else { + MS_DBG(F(" ... wake up failed! <<---"), i); } } - } - } -// This is just for debugging -#ifdef MS_VARIABLEARRAY_DEBUG_DEEP - uint8_t arrayPositions[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { arrayPositions[i] = i; } - String nameLocation[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { - // nameLocation[i] = arrayOfVars[i]->getParentSensorNameAndLocation(); - nameLocation[i] = arrayOfVars[i]->getParentSensorName(); - } - MS_DEEP_DBG(F("----------------------------------")); - MS_DEEP_DBG(F("arrayPositions:\t\t\t")); - prettyPrintArray(arrayPositions); - MS_DEEP_DBG(F("sensor:\t\t\t")); - prettyPrintArray(nameLocation); - MS_DEEP_DBG(F("lastSensorVariable:\t\t")); - prettyPrintArray(lastSensorVariable); - MS_DEEP_DBG(F("nMeasurementsToAverage:\t\t")); - prettyPrintArray(nMeasurementsToAverage); - MS_DEEP_DBG(F("nMeasurementsCompleted:\t\t")); - prettyPrintArray(nMeasurementsCompleted); - MS_DEEP_DBG(F("powerPins:\t\t\t")); - prettyPrintArray(powerPins); - MS_DEEP_DBG(F("lastPinVariable:\t\t")); - prettyPrintArray(lastPinVariable); - MS_DEEP_DBG(F("powerPinIndex:\t\t\t")); - prettyPrintArray(powerPinIndex); - MS_DEEP_DBG(F("nMeasurementsOnPin:\t\t")); - prettyPrintArray(nMeasurementsOnPin); - MS_DEEP_DBG(F("powerPinIndex:\t\t\t")); - prettyPrintArray(powerPinIndex); -#endif - - // Another array for the number of measurements already completed per power - // pin - uint8_t nCompletedOnPin[_variableCount]; - for (uint8_t i = 0; i < _variableCount; i++) { nCompletedOnPin[i] = 0; } - - // Clear the initial variable arrays - MS_DBG(F("----->> Clearing all results arrays before taking new " - "measurements. ...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - arrayOfVars[i]->parentSensor->clearValues(); - } - } - MS_DBG(F(" ... Complete. <<-----")); - - // power up all of the sensors together - MS_DBG(F("----->> Powering up all sensors together. ...")); - sensorsPowerUp(); - MS_DBG(F(" ... Complete. <<-----")); - - while (nSensorsCompleted < _sensorCount) { - for (uint8_t i = 0; i < _variableCount; i++) { - /*** - // THIS IS PURELY FOR DEEP DEBUGGING OF THE TIMING! - // Leave this whole section commented out unless you want excessive - // printouts (ie, thousands of lines) of the timing information!! - if (lastSensorVariable[i] and - nMeasurementsToAverage[i] > nMeasurementsCompleted[i]) { - MS_DEEP_DBG( - i, '-', arrayOfVars[i]->getParentSensorNameAndLocation(), - F("- millis:"), millis(), F("- status: 0b"), - getSensorStatusBit(i,7), - getSensorStatusBit(i,6), - getSensorStatusBit(i,5), - getSensorStatusBit(i,4), - getSensorStatusBit(i,3), - getSensorStatusBit(i,2), - getSensorStatusBit(i,1), - getSensorStatusBit(i,0), - F("- measurement #"), (nMeasurementsCompleted[i] + 1)); - } - MS_DEEP_DBG(F("----------------------------------")); - MS_DEEP_DBG(F("nMeasurementsToAverage:\t\t")); - prettyPrintArray(nMeasurementsToAverage); - MS_DEEP_DBG(F("nMeasurementsCompleted:\t\t")); - prettyPrintArray(nMeasurementsCompleted); - MS_DEEP_DBG(F("nMeasurementsOnPin:\t\t")); - prettyPrintArray(nMeasurementsOnPin); - MS_DEEP_DBG(F("nCompletedOnPin:\t\t\t")); - prettyPrintArray(nCompletedOnPin); - // END CHUNK FOR DEBUGGING! - ***/ - - // Only do checks on sensors that still have measurements to finish - if (lastSensorVariable[i] && - nMeasurementsToAverage[i] > nMeasurementsCompleted[i]) { - if (getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED) == - 0 // If no attempts yet made to wake the sensor up - && arrayOfVars[i]->parentSensor->isWarmedUp( - deepDebugTiming) // and if it is already warmed up - ) { - MS_DBG(i, F("--->> Waking"), - arrayOfVars[i]->getParentSensorNameAndLocation(), + // If the sensor was successfully awoken/activated, but no + // measurement was either started or finished ... + if (isSensorReadyToMeasure(i)) { + // .. check if it's stable + if (_sensorList[i]->isStable(deepDebugTiming)) { + MS_DBG(cycCount, F("--->> Starting reading on"), sName, F("...")); - // Make a single attempt to wake the sensor after it is - // warmed up - bool sensorSuccess_wake = - arrayOfVars[i]->parentSensor->wake(); - success &= sensorSuccess_wake; + bool sensorSuccess_start = + _sensorList[i]->startSingleMeasurement(); + success &= sensorSuccess_start; - if (sensorSuccess_wake) { - MS_DBG(F(" ... wake up success. <<---"), i); + if (sensorSuccess_start) { + MS_DBG(F(" ... start reading succeeded. <<---"), + cycCount); } else { - MS_DBG(F(" ... wake up failed! <<---"), i); + MS_DBG(F(" ... start reading failed! <<---"), + cycCount); } } + } - // If attempts were made to wake the sensor, but they failed - // then we're just bumping up the number of measurements to - // completion - if (getSensorStatusBit(i, Sensor::WAKE_ATTEMPTED) == 1 && - getSensorStatusBit(i, Sensor::WAKE_SUCCESSFUL) == 0) { - MS_DBG(i, F("--->>"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("did not wake up! No measurements will be taken! " - "<<---"), - i); - // Set the number of measurements already equal to whatever - // total number requested to ensure the sensor is skipped in - // further loops. - nMeasurementsCompleted[i] = nMeasurementsToAverage[i]; - // increment the number of measurements that the power pin - // has completed - nCompletedOnPin[powerPinIndex[i]] += - nMeasurementsToAverage[i]; - } - - // If the sensor was successfully awoken/activated, but no - // measurement was either started or finished ... - if (getSensorStatusBit(i, Sensor::WAKE_SUCCESSFUL) == 1 && - getSensorStatusBit(i, Sensor::MEASUREMENT_ATTEMPTED) == 0 && - getSensorStatusBit(i, Sensor::MEASUREMENT_SUCCESSFUL) == - 0) { - // .. check if it's stable - if (arrayOfVars[i]->parentSensor->isStable( - deepDebugTiming)) { - // Start a reading - MS_DBG(i, '.', nMeasurementsCompleted[i] + 1, - F("--->> Starting reading"), - nMeasurementsCompleted[i] + 1, F("on"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); - - bool sensorSuccess_start = - arrayOfVars[i] - ->parentSensor->startSingleMeasurement(); - success &= sensorSuccess_start; - - if (sensorSuccess_start) { - MS_DBG(F(" ... start reading succeeded. <<---"), - i, '.', nMeasurementsCompleted[i] + 1); - } else { - MS_DBG(F(" ... start reading failed! <<---"), i, - '.', nMeasurementsCompleted[i] + 1); - } - } - } + // if measurements have been started, whether or not + // successfully... + // We aren't checking if the measurement start was successful; + // isMeasurementComplete(deepDebugTiming) will do that. + if (isMeasurementAttempted(i)) { + // If a measurement is finished, get the result and tick up + // the number of finished measurements. + if (_sensorList[i]->isMeasurementComplete(deepDebugTiming)) { + // Get the value + MS_DBG(cycCount, + F("--->> Collected result of reading from"), sName, + F("...")); - // if measurements have been started, whether or not - // successfully... - // We aren't checking if the measurement start was successful; - // isMeasurementComplete(deepDebugTiming) will do that. - // We want the addSingleMeasurementResult() function to fill in - // the -9999 results for a failed measurement. - if (getSensorStatusBit(i, Sensor::MEASUREMENT_ATTEMPTED) == 1) { - // If a measurement is finished, get the result and tick up - // the number of finished measurements. - if (arrayOfVars[i]->parentSensor->isMeasurementComplete( - deepDebugTiming)) { - // Get the value - MS_DBG(i, '.', nMeasurementsCompleted[i] + 1, - F("--->> Collected result of reading"), - nMeasurementsCompleted[i] + 1, F("from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), - F("...")); + bool sensorSuccess_result = + _sensorList[i]->addSingleMeasurementResult(); + success &= sensorSuccess_result; - bool sensorSuccess_result = - arrayOfVars[i] - ->parentSensor->addSingleMeasurementResult(); - success &= sensorSuccess_result; - nMeasurementsCompleted[i] += - 1; // increment the number of measurements that - // sensor has completed - nCompletedOnPin[powerPinIndex[i]] += - 1; // increment the number of measurements that the - // power pin has completed - - if (sensorSuccess_result) { - MS_DBG(F(" ... got measurement result. <<---"), i, - '.', nMeasurementsCompleted[i]); - } else { - MS_DBG(F(" ... failed to get measurement result! " - "<<---"), - i, '.', nMeasurementsCompleted[i]); - } + if (sensorSuccess_result) { + MS_DBG(F(" ... got measurement result. <<---"), + cycCount); + } else { + MS_DBG(F(" ... failed to get measurement result! " + "<<---"), + cycCount); } } + } - // If all the measurements are done - if (nMeasurementsCompleted[i] == nMeasurementsToAverage[i]) { - MS_DBG(i, F("--->> Finished all measurements from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), + // If all the measurements are now complete + if (areMeasurementsComplete(i)) { + if (sleep) { + MS_DBG(i, F("--->> Finished all measurements from"), sName, F(", putting it to sleep. ...")); // Put the completed sensor to sleep - bool sensorSuccess_sleep = - arrayOfVars[i]->parentSensor->sleep(); + bool sensorSuccess_sleep = _sensorList[i]->sleep(); success &= sensorSuccess_sleep; if (sensorSuccess_sleep) { MS_DBG(F(" ... succeeded in putting sensor to sleep. " - "<<---"), - i); + "Total wake time was"), + millis() - + _sensorList[i]->_millisSensorActivated, + F("ms <<---"), i); } else { MS_DBG(F(" ... sleep failed! <<---"), i); } - - // Now cut the power, if ready, to this sensors and all that + } + // NOTE: We are NOT checking if the sleep command succeeded! + if (powerDown) { + // Cut the power, if ready, to this sensors and all that // share the pin - if (nCompletedOnPin[powerPinIndex[i]] == - nMeasurementsOnPin[powerPinIndex[i]]) { - for (uint8_t k = 0; k < _variableCount; k++) { - if (powerPinIndex[k] == powerPinIndex[i] && - lastSensorVariable[k]) { - arrayOfVars[k]->parentSensor->powerDown(); - MS_DBG(k, F("--->>"), - arrayOfVars[k] - ->getParentSensorNameAndLocation(), - F("powered down. <<---"), k); - } - } + if (canPowerDownSensor(i)) { + MS_DBG(F("Powering down all sensors on pin"), + _sensorList[i]->getPowerPin(), F("or pin"), + _sensorList[i]->getSecondaryPowerPin(), + F("...")); + _sensorList[i]->powerDown(); } - - nSensorsCompleted++; // mark the whole sensor as done - MS_DBG(F("*****---"), nSensorsCompleted, - F("sensors now complete ---*****")); } + nSensorsCompleted++; // mark the whole sensor as done + MS_DBG(F("*****---"), nSensorsCompleted, + F("sensors now complete ---*****")); + } else { + MS_DEEP_DBG(i, F("--->>"), sName, F("still needs to take"), + nReq - _sensorList[i]->getCompletedMeasurements(), + F("measurements.")); } } + MS_DEEP_DBG(F("xxxxx---"), _sensorCount - nSensorsCompleted, + F("sensors remaining ---xxxxx")); } - // // power down all the sensors again, just in case - // MS_DBG(F("----->> Running a final power-down of all the sensors. ...")); - // sensorsPowerDown(); - // MS_DBG(F(" ... Complete. <<-----")); - // Average measurements and notify variables of the updates MS_DBG(F("----->> Averaging results and notifying all variables. ...")); - for (uint8_t i = 0; i < _variableCount; i++) { - if (lastSensorVariable[i]) { - MS_DBG(F("--- Averaging results from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), F("---")); - arrayOfVars[i]->parentSensor->averageMeasurements(); - MS_DBG(F("--- Notifying variables from"), - arrayOfVars[i]->getParentSensorNameAndLocation(), F("---")); - arrayOfVars[i]->parentSensor->notifyVariables(); - } + for (uint8_t i = 0; i < _sensorCount; i++) { + MS_DBG(F("--- Averaging results from"), + _sensorList[i]->getSensorNameAndLocation(), F("---")); + _sensorList[i]->averageMeasurements(); + MS_DBG(F("--- Notifying variables from"), + _sensorList[i]->getSensorNameAndLocation(), F("---")); + _sensorList[i]->notifyVariables(); } MS_DBG(F("... Complete. <<-----")); @@ -853,16 +673,38 @@ bool VariableArray::completeUpdate(void) { return success; } +// Backward compatibility wrapper +void VariableArray::printSensorData(Stream* stream) { + printVariableData(stream); +} + // This function prints out the results for any connected sensors to a stream // Calculated Variable results will be included -void VariableArray::printSensorData(Stream* stream) { +void VariableArray::printVariableData(Stream* stream) { for (uint8_t i = 0; i < _variableCount; i++) { + if (i > 0) { + // Check if we need to add a line break between different sensors + // For calculated variables or when parentSensor pointers differ + bool differentSensors = false; + + // If either variable is calculated, treat as different sensors + if (arrayOfVars[i]->isCalculated || + arrayOfVars[i - 1]->isCalculated) { + differentSensors = true; + } else { + // Direct pointer comparison works safely even with nulls + differentSensors = (arrayOfVars[i]->parentSensor != + arrayOfVars[i - 1]->parentSensor); + } + + if (differentSensors) { stream->println(); } + } if (arrayOfVars[i]->isCalculated) { stream->print(arrayOfVars[i]->getVarName()); stream->print(F(" (")); stream->print(arrayOfVars[i]->getVarCode()); - stream->print(F(") ")); + stream->print(F(")")); stream->print(F(" is calculated to be ")); stream->print(arrayOfVars[i]->getValueString()); stream->print(F(" ")); @@ -870,20 +712,11 @@ void VariableArray::printSensorData(Stream* stream) { stream->println(); } else { stream->print(arrayOfVars[i]->getParentSensorNameAndLocation()); - // stream->print(F(" with status 0b")); - // stream->print(getSensorStatusBit(i, 7)); - // stream->print(getSensorStatusBit(i, 6)); - // stream->print(getSensorStatusBit(i, 5)); - // stream->print(getSensorStatusBit(i, 4)); - // stream->print(getSensorStatusBit(i, 3)); - // stream->print(getSensorStatusBit(i, 2)); - // stream->print(getSensorStatusBit(i, 1)); - // stream->print(getSensorStatusBit(i, 0)); stream->print(F(" reports ")); stream->print(arrayOfVars[i]->getVarName()); stream->print(F(" (")); stream->print(arrayOfVars[i]->getVarCode()); - stream->print(F(") ")); + stream->print(F(")")); stream->print(F(" is ")); stream->print(arrayOfVars[i]->getValueString()); stream->print(F(" ")); @@ -901,12 +734,10 @@ bool VariableArray::isLastVarFromSensor(int arrayIndex) { if (arrayOfVars[arrayIndex]->isCalculated) { return false; } else { - String sensNameLoc = - arrayOfVars[arrayIndex]->getParentSensorNameAndLocation(); - bool unique = true; + Sensor* parSens = arrayOfVars[arrayIndex]->parentSensor; + bool unique = true; for (int j = arrayIndex + 1; j < _variableCount; j++) { - if (sensNameLoc == - arrayOfVars[j]->getParentSensorNameAndLocation()) { + if (parSens == arrayOfVars[j]->parentSensor) { unique = false; break; } @@ -922,23 +753,9 @@ bool VariableArray::getSensorStatusBit(int arrayIndex, return arrayOfVars[arrayIndex]->parentSensor->getStatusBit(bitToGet); } -// Count the maximum number of measurements needed from a single sensor for the -// requested averaging -uint8_t VariableArray::countMaxToAverage(void) { - uint8_t numReps = 0; - for (uint8_t i = 0; i < _variableCount; i++) { - if (isLastVarFromSensor(i)) { // Skip non-unique sensors - numReps = max( - numReps, - arrayOfVars[i]->parentSensor->getNumberMeasurementsToAverage()); - } - } - return numReps; -} - -// Check that all variable have valid UUID's, if they are assigned -bool VariableArray::checkVariableUUIDs(void) { +// Check that all variable have valid UUIDs, if they are assigned +bool VariableArray::checkVariableUUIDs() { bool success = true; for (uint8_t i = 0; i < _variableCount; i++) { if (!arrayOfVars[i]->checkUUIDFormat()) { @@ -966,9 +783,10 @@ bool VariableArray::checkVariableUUIDs(void) { } } } - if (success) - PRINTOUT(F("All variable UUID's appear to be correctly formed.\n")); - // Print out all UUID's to check + if (success) { + PRINTOUT(F("All variable UUIDs appear to be correctly formed.\n")); + } + // Print out all UUIDs to check for (uint8_t i = 0; i < _variableCount; i++) { if (arrayOfVars[i]->getVarUUID() != nullptr && strlen(arrayOfVars[i]->getVarUUID()) > 0) { diff --git a/src/VariableArray.h b/src/VariableArray.h index 777916723..62ac17f8f 100644 --- a/src/VariableArray.h +++ b/src/VariableArray.h @@ -77,33 +77,36 @@ class VariableArray { public: // Constructors - /** - * @brief Construct a new Variable Array object - */ - VariableArray(); /** * @brief Construct a new Variable Array object * * @param variableCount The number of variables in the array * @param variableList An array of pointers to variable objects. The * pointers may be to calculated or measured variable objects. + * @param uuids An array of UUIDs. These are linked 1-to-1 with the + * variables by array position. */ - VariableArray(uint8_t variableCount, Variable* variableList[]); + VariableArray(uint8_t variableCount, Variable* variableList[], + const char* uuids[]); /** * @brief Construct a new Variable Array object * * @param variableCount The number of variables in the array * @param variableList An array of pointers to variable objects. The * pointers may be to calculated or measured variable objects. - * @param uuids An array of UUID's. These are linked 1-to-1 with the - * variables by array position. */ - VariableArray(uint8_t variableCount, Variable* variableList[], - const char* uuids[]); + VariableArray(uint8_t variableCount, Variable* variableList[]); + /** + * @brief Construct a new Variable Array object without initialization. + * + * Use this constructor when the variable list is not yet available. + * Call begin() to initialize the array before use. + */ + VariableArray(); /** * @brief Destroy the Variable Array object - no action taken. */ - ~VariableArray(); + ~VariableArray() = default; // "Begins" the VariableArray - attaches the number and array of variables // Not doing this in the constructor because we expect the VariableArray to @@ -132,13 +135,13 @@ class VariableArray { * @param variableList An array of pointers to variable objects. The * pointers may be to calculated or measured variable objects. Supersedes * any value given in the constructor. - * @param uuids An array of UUID's. These are linked 1-to-1 with the + * @param uuids An array of UUIDs. These are linked 1-to-1 with the * variables by array position. */ void begin(uint8_t variableCount, Variable* variableList[], const char* uuids[]); /** - * @brief Begins the VariableArray. Checks the validity of all UUID and + * @brief Begins the VariableArray. Checks the validity of all UUIDs and * outputs the results. */ void begin(); @@ -155,19 +158,18 @@ class VariableArray { * * @return The number of variables */ - uint8_t getVariableCount(void) { + uint8_t getVariableCount() { return _variableCount; } /** * @brief Get the number of calculated variables * - * @return The number of calculated (ie, not measured by a + * @return The number of calculated (i.e., not measured by a * sensor) variables */ - uint8_t getCalculatedVariableCount(void); + uint8_t getCalculatedVariableCount(); - // This counts and returns the number of sensors /** * @brief Get the number of sensors associated with the variables in the * array. @@ -177,15 +179,15 @@ class VariableArray { * * @return The number of sensors */ - uint8_t getSensorCount(void); + uint8_t getSensorCount(); /** - * @brief Match UUID's from the given variables in the variable array. + * @brief Match UUIDs from the given variables in the variable array. * - * This over-writes all UUID's previously assigned to every variable. The + * This over-writes all UUIDs previously assigned to every variable. The * match is 1-to-1 based on array position. * - * @param uuids An array of UUID's + * @param uuids An array of UUIDs */ void matchUUIDs(const char* uuids[]); @@ -202,14 +204,14 @@ class VariableArray { * @return True indicates all sensors have been set up * successfully. */ - bool setupSensors(void); + bool setupSensors(); /** * @brief Power up each sensor. * * Runs the powerUp sensor function for each unique sensor. */ - void sensorsPowerUp(void); + void sensorsPowerUp(); /** * @brief Wake up each sensor. @@ -219,7 +221,7 @@ class VariableArray { * * @return True if all wake functions were run successfully. */ - bool sensorsWake(void); + bool sensorsWake(); /** * @brief Put all sensors to sleep @@ -228,24 +230,25 @@ class VariableArray { * * @return True if all sleep functions were run successfully. */ - bool sensorsSleep(void); + bool sensorsSleep(); /** * @brief Cut power to all sensors. * Runs the powerDown sensor function for each unique sensor. */ - void sensorsPowerDown(void); + void sensorsPowerDown(); /** * @brief Update the values for all connected sensors. * - * Does not power or wake/sleep sensors. Returns a boolean indication the - * overall success. Does NOT return any values. Repeatedly checks each - * sensor's readiness state to optimize timing. + * @m_deprecated_since{0,38,0} + * + * Use completeUpdate() instead and set the powerUp, wake, sleep and + * powerDown parameters as needed. * * @return True if all steps of the update succeeded. */ - bool updateAllSensors(void); + bool updateAllSensors(); // This function powers, wakes, updates values, sleeps and powers down. @@ -257,19 +260,38 @@ class VariableArray { * values. Repeatedly checks each sensor's readiness state to optimize * timing. * + * @param powerUp If true, powers up all sensors before updating. + * @param wake If true, wakes all sensors before updating. + * @param sleep If true, puts all sensors to sleep after updating. + * @param powerDown If true, cuts power to all sensors after updating. + * * @return True if all steps of the update succeeded. */ - bool completeUpdate(void); + bool completeUpdate(bool powerUp = true, bool wake = true, + bool sleep = true, bool powerDown = true); /** - * @brief Print out the results for all connected sensors to a stream + * @brief Print out the results for all variables in the variable array to a + * stream + * + * This prints current variable values - both those from sensors and + * calculated variables - along with meta-data to a stream (either hardware + * or software serial). This does not include all possible variables from + * each sensor, only those variables that are in the variable list. + * + * By default, it will print to the first Serial port. Note that the input + * is a pointer to a stream instance - to use a hardware serial instance you + * must use an ampersand before the serial name (i.e., &Serial1). * - * This prints current sensor values along with meta-data to a stream - * (either hardware or software serial). By default, it will print to the - * first Serial port. Note that the input is a pointer to a stream instance - * - to use a hardware serial instance you must use an ampersand before the - * serial name (ie, &Serial1). + * @param stream An Arduino Stream instance + */ + void printVariableData(Stream* stream = &Serial); + + /** + * @brief Print out the results for all variables in the variable array to a + * stream * + * @m_deprecated_since{0,38,0} Use printVariableData() instead. * @param stream An Arduino Stream instance */ void printSensorData(Stream* stream = &Serial); @@ -283,39 +305,99 @@ class VariableArray { * @brief The count of unique sensors tied to variables in the array */ uint8_t _sensorCount; - /** - * @brief The maximum number of samples to average of an single sensor. - */ - uint8_t _maxSamplesToAverage; private: /** * @brief Check if the current variable is the last variable that the sensor * will return. * - * This is used for formating output where the format is slightly different - * for the last value. (ie, no comma after the last value) + * This is used for formatting output where the format is slightly different + * for the last value. (i.e., no comma after the last value) * * @param arrayIndex The index of the variable in the sensor variable array * @return True if the variable is the last in the array. */ bool isLastVarFromSensor(int arrayIndex); /** - * @brief Count the maximum number of measurements needed from a single - * sensor for the requested averaging + * @brief Check that all variables have valid UUIDs, if they are assigned * - * @return The number of measurements needed. + * @return True if all assigned UUIDs are valid; also returns true if no + * UUIDs are assigned. + * + * @warning This does not check that the UUIDs are the true UUIDs for the + * variables, just that the text is a validly formed UUID. */ - uint8_t countMaxToAverage(void); + bool checkVariableUUIDs(); + /** - * @brief Check that all variable have valid UUID's, if they are assigned + * @brief Check if a sensor has failed to wake up or is not ready for use. * - * @return True if all variables have valid UUID's. + * @param sensorIndex Index of the sensor in _sensorList to check + * @param wake Whether we are currently in wake mode + * @return True if sensor wake failed or is not ready + */ + bool isSensorWakeFailure(uint8_t sensorIndex, bool wake); + + /** + * @brief Check if a sensor should be woken up now. * - * @warning This does not check that the UUID's are the true UUID's for the - * variables, just that the text is a validly formed UUID. + * @param sensorIndex Index of the sensor in _sensorList to check + * @param wake Whether we are currently in wake mode + * @param deepDebugTiming Whether to use deep debug timing + * @return True if sensor should be woken up + */ + bool shouldWakeSensor(uint8_t sensorIndex, bool wake, bool deepDebugTiming); + + /** + * @brief Check if a sensor is ready to start measurements. + * + * @param sensorIndex Index of the sensor in _sensorList to check + * @return True if sensor is awake and ready for measurements + */ + bool isSensorReadyToMeasure(uint8_t sensorIndex); + + /** + * @brief Check if measurements have been attempted on a sensor. + * + * @param sensorIndex Index of the sensor in _sensorList to check + * @return True if measurement attempt has been made + */ + bool isMeasurementAttempted(uint8_t sensorIndex); + + /** + * @brief Check if all required measurements are complete for a sensor. + * + * @param sensorIndex Index of the sensor in _sensorList to check + * @return True if all measurements are complete */ - bool checkVariableUUIDs(void); + bool areMeasurementsComplete(uint8_t sensorIndex); + + /** + * @brief Check if two sensors share any power pins + * + * This helper function checks if two sensors share either primary or + * secondary power pins by comparing all combinations of power pin + * assignments between the sensors. + * + * @param a First sensor to compare + * @param b Second sensor to compare + * @return True if sensors share any power pins + */ + bool sharesPowerPin(Sensor* a, Sensor* b); + + /** + * @brief Check if sensor can be powered down safely + * + * This helper function checks if a sensor can be powered down by verifying + * that no other sensors sharing the same power pins still need + * measurements. Provides comprehensive debug output about the power + * management decision. + * + * @param sensorIndex Index of the sensor in _sensorList to check + * @return True if sensor can be safely powered down, false if it must stay + * powered + */ + bool canPowerDownSensor(uint8_t sensorIndex); /** * @brief Get a specific status bit from the sensor tied to a variable in @@ -328,6 +410,21 @@ class VariableArray { bool getSensorStatusBit(int arrayIndex, Sensor::sensor_status_bits bitToGet); + /** + * @brief Populate the internal sensor list from the variable array + * + * This extracts unique sensors from the variable array and stores them + * in the internal sensor list for efficient access during operations. + * + * @return True if the sensor list was populated successfully + */ + bool populateSensorList(); + + /** + * @brief Array of pointers to unique sensors derived from variables + */ + Sensor* _sensorList[MAX_NUMBER_SENSORS] = {}; + #ifdef MS_VARIABLEARRAY_DEBUG_DEEP /** * @brief Prints out the contents of an array with even spaces and commas diff --git a/src/VariableBase.cpp b/src/VariableBase.cpp index 12fdbdc08..efbf0ff21 100644 --- a/src/VariableBase.cpp +++ b/src/VariableBase.cpp @@ -15,97 +15,41 @@ // The class and functions for interfacing with a specific variable. // ============================================================================ -// The constructor for a measured variable - that is, one whose values are -// updated by a sensor. +// Primary constructors +// The constructor for a measured variable with UUID - that is, one whose values +// are updated by a sensor. Variable::Variable(Sensor* parentSense, uint8_t sensorVarNum, uint8_t decimalResolution, const char* varName, const char* varUnit, const char* varCode, const char* uuid) - : _sensorVarNum(sensorVarNum) { - setVarUUID(uuid); - setVarCode(varCode); - setVarUnit(varUnit); - setVarName(varName); - setResolution(decimalResolution); - - attachSensor(parentSense); -} -Variable::Variable(uint8_t sensorVarNum, uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode) - : _sensorVarNum(sensorVarNum) { - setVarCode(varCode); - setVarUnit(varUnit); - setVarName(varName); + : parentSensor(nullptr), + isCalculated(false), + _currentValue(MS_INVALID_VALUE), + _calcFxn(nullptr), + _sensorVarNum(sensorVarNum) { + if (uuid) setVarUUID(uuid); + if (varCode) setVarCode(varCode); + if (varUnit) setVarUnit(varUnit); + if (varName) setVarName(varName); setResolution(decimalResolution); + if (parentSense) attachSensor(parentSense); } - -// The constructor for a calculated variable - that is, one whose value is -// calculated by the calcFxn which returns a float. +// The constructor for a calculated variable with UUID - that is, one whose +// value is calculated by the calcFxn which returns a float. Variable::Variable(float (*calcFxn)(), uint8_t decimalResolution, const char* varName, const char* varUnit, const char* varCode, const char* uuid) - : isCalculated(true) { - setVarUUID(uuid); - setVarCode(varCode); - setVarUnit(varUnit); - setVarName(varName); - setResolution(decimalResolution); - - setCalculation(calcFxn); -} -Variable::Variable(float (*calcFxn)(), uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode) - : isCalculated(true) { - setVarCode(varCode); - setVarUnit(varUnit); - setVarName(varName); - setResolution(decimalResolution); - - setCalculation(calcFxn); -} - -// constructor with no arguments -Variable::Variable() : isCalculated(true) {} -// Destructor -Variable::~Variable() {} - - -// This does all of the setup that can't happen in the constructors -// That is, anything that depends on another object having been created -// first or anything that requires the actual processor/MCU to do something. -Variable* Variable::begin(Sensor* parentSense, const char* uuid, - const char* customVarCode) { - setVarCode(customVarCode); - return begin(parentSense, uuid); -} -Variable* Variable::begin(Sensor* parentSense, const char* uuid) { - setVarUUID(uuid); - return begin(parentSense); -} -Variable* Variable::begin(Sensor* parentSense) { - attachSensor(parentSense); - return this; -} - - -// Begin functions for calculated variables -Variable* Variable::begin(float (*calcFxn)(), uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode, const char* uuid) { - setVarUUID(uuid); - return begin(calcFxn, decimalResolution, varName, varUnit, varCode); -} -Variable* Variable::begin(float (*calcFxn)(), uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode) { - setVarCode(varCode); - setVarUnit(varUnit); - setVarName(varName); + : parentSensor(nullptr), + isCalculated(false), + _currentValue(MS_INVALID_VALUE), + _calcFxn(nullptr), + _sensorVarNum(0) { + if (uuid) setVarUUID(uuid); + if (varCode) setVarCode(varCode); + if (varUnit) setVarUnit(varUnit); + if (varName) setVarName(varName); setResolution(decimalResolution); - setCalculation(calcFxn); - return this; + if (calcFxn) setCalculation(calcFxn); } @@ -131,7 +75,7 @@ void Variable::onSensorUpdate(Sensor* parentSense) { // This is a helper - it returns the name of the parent sensor, if applicable // This is needed for dealing with variables in arrays -String Variable::getParentSensorName(void) { +String Variable::getParentSensorName() { if (isCalculated) { return "Calculated"; } else if (parentSensor == nullptr) { @@ -145,7 +89,7 @@ String Variable::getParentSensorName(void) { // This is a helper - it returns the name and location of the parent sensor, if // applicable This is needed for dealing with variables in arrays -String Variable::getParentSensorNameAndLocation(void) { +String Variable::getParentSensorNameAndLocation() { if (isCalculated) { return "Calculated"; } else if (parentSensor == nullptr) { @@ -159,12 +103,13 @@ String Variable::getParentSensorNameAndLocation(void) { // This ties a calculated variable to its calculation function void Variable::setCalculation(float (*calcFxn)()) { - if (isCalculated) { _calcFxn = calcFxn; } + _calcFxn = calcFxn; + isCalculated = (calcFxn != nullptr); } // This gets/sets the variable's resolution for value strings -uint8_t Variable::getResolution(void) { +uint8_t Variable::getResolution() { return _decimalResolution; } void Variable::setResolution(uint8_t decimalResolution) { @@ -173,7 +118,7 @@ void Variable::setResolution(uint8_t decimalResolution) { // This gets/sets the variable's name using // http://vocabulary.odm2.org/variablename/ -String Variable::getVarName(void) { +String Variable::getVarName() { return _varName; } void Variable::setVarName(const char* varName) { @@ -181,7 +126,7 @@ void Variable::setVarName(const char* varName) { } // This gets/sets the variable's unit using http://vocabulary.odm2.org/units/ -String Variable::getVarUnit(void) { +String Variable::getVarUnit() { return _varUnit; } void Variable::setVarUnit(const char* varUnit) { @@ -189,7 +134,7 @@ void Variable::setVarUnit(const char* varUnit) { } // This returns a customized code for the variable -String Variable::getVarCode(void) { +String Variable::getVarCode() { return _varCode; } // This sets the variable code to a new custom value @@ -198,12 +143,12 @@ void Variable::setVarCode(const char* varCode) { } // This returns the variable UUID as a String, if one has been assigned -String Variable::getVarUUIDString(void) { +String Variable::getVarUUIDString() { return String(_uuid); } // This returns the variable UUID as a pointer to a const char array, if one has // been assigned -const char* Variable::getVarUUID(void) { +const char* Variable::getVarUUID() { return _uuid; } // This sets the UUID @@ -211,7 +156,7 @@ void Variable::setVarUUID(const char* uuid) { _uuid = uuid; } // This checks that the UUID is properly formatted -bool Variable::checkUUIDFormat(void) { +bool Variable::checkUUIDFormat() { // If no UUID, move on if (_uuid == nullptr || strlen(_uuid) == 0) { return true; } @@ -252,13 +197,27 @@ float Variable::getValue(bool updateValue) { // NOTE: Only run the calculation function if update value is called, // otherwise return the last value. If we run the calculation function // every time we call getValue() or getValueString(), we will get - // different values each time - ie, the data on the CSV and each + // different values each time - i.e., the data on the CSV and each // publisher will report a different value. That is **NOT** the desired // behavior. Thus, we stash the value. - if (updateValue) _currentValue = _calcFxn(); + if (updateValue && _calcFxn != nullptr) { + _currentValue = _calcFxn(); + } else if (updateValue && _calcFxn == nullptr) { + // If no calculation function is set, return error value + _currentValue = MS_INVALID_VALUE; + MS_DBG(F("ERROR! Calculated variable"), getVarCode(), + F("has no calculation function!")); + } return _currentValue; } else { - if (updateValue) parentSensor->update(); + if (updateValue && parentSensor != nullptr) { + parentSensor->update(); + } else if (updateValue && parentSensor == nullptr) { + // If no parent sensor is set, return error value + _currentValue = MS_INVALID_VALUE; + MS_DBG(F("ERROR! Variable"), getVarCode(), + F("has no parent sensor!")); + } return _currentValue; } } diff --git a/src/VariableBase.h b/src/VariableBase.h index ce3a0cf06..0cf3e77f2 100644 --- a/src/VariableBase.h +++ b/src/VariableBase.h @@ -20,6 +20,9 @@ // Include the debugging config #include "ModSensorDebugConfig.h" +// Include math library for log10f function +#include + // Define the print label[s] for the debugger #ifdef MS_VARIABLEBASE_DEBUG #define MS_DEBUGGING_STD "VariableBase" @@ -83,27 +86,7 @@ class Variable { Variable(Sensor* parentSense, uint8_t sensorVarNum, uint8_t decimalResolution, const char* varName, const char* varUnit, const char* varCode, const char* uuid); - /** - * @brief Construct a new Variable object for a measured variable - that is, - * one whose values are updated by a sensor - but do not tie it to a - * specific sensor. - * - * @note This constructor is NOT intended to be used outside of this - * libraries. It is intended to be used internally with sensors defined in - * this library. - * - * @param sensorVarNum The position in the sensor's value array of this - * variable's value. - * @param decimalResolution The resolution (in decimal places) of the value. - * @param varName The name of the variable per the [ODM2 variable name - * controlled vocabulary](http://vocabulary.odm2.org/variablename/) - * @param varUnit The unit of the variable per the [ODM2 unit controlled - * vocabulary](http://vocabulary.odm2.org/units/) - * @param varCode A custom code for the variable. This can be any short - * text helping to identify the variable in files. - */ - Variable(uint8_t sensorVarNum, uint8_t decimalResolution, - const char* varName, const char* varUnit, const char* varCode); + /** * @brief Construct a new Variable object for a calculated variable - that @@ -120,121 +103,18 @@ class Variable { * @param uuid A universally unique identifier for the variable. * * @warning The `calcFxn` absolutely must return a float value. If it - * returns a value of any other type (ie, some type of integer), your + * returns a value of any other type (i.e., some type of integer), your * program will compile but immediately hang. */ Variable(float (*calcFxn)(), uint8_t decimalResolution, const char* varName, - const char* varUnit, const char* varCode, const char* uuid); - /** - * @brief Construct a new Variable object for a calculated variable - that - * is, one whose value is calculated by the calcFxn which returns a float. - * - * @param calcFxn Any function returning a float value - * @param decimalResolution The resolution (in decimal places) of the value. - * @param varName The name of the variable per the [ODM2 variable name - * controlled vocabulary](http://vocabulary.odm2.org/variablename/) - * @param varUnit The unit of the variable per the [ODM2 unit controlled - * vocabulary](http://vocabulary.odm2.org/units/) - * @param varCode A custom code for the variable. This can be any short - * text helping to identify the variable in files. - * - * @warning The `calcFxn` absolutely must return a float value. If it - * returns a value of any other type (ie, some type of integer), your - * program will compile but immediately hang. - */ - Variable(float (*calcFxn)(), uint8_t decimalResolution, const char* varName, - const char* varUnit, const char* varCode); - /** - * @brief Construct a new Variable object - */ - Variable(); + const char* varUnit, const char* varCode, + const char* uuid = nullptr); /** * @brief Destroy the Variable object - no action taken. */ - ~Variable(); - - /** - * @brief Begin for the Variable object - * - * @param parentSense The Sensor object supplying values. Supersedes any - * Sensor supplied in the constructor. - * @param uuid A universally unique identifier for the variable. - * Supersedes any value supplied in the constructor. - * @param customVarCode A custom code for the variable. Supersedes - * any value supplied in the constructor. - * @return A pointer to the variable object - */ - Variable* begin(Sensor* parentSense, const char* uuid, - const char* customVarCode); - /** - * @brief Begin for the Variable object - * - * @param parentSense The Sensor object supplying values. Supersedes any - * Sensor supplied in the constructor. - * @param uuid A universally unique identifier for the variable. - * Supersedes any value supplied in the constructor. - * @return A pointer to the variable object - */ - Variable* begin(Sensor* parentSense, const char* uuid); - /** - * @brief Begin for the Variable object - * - * @param parentSense The Sensor object supplying values. Supersedes any - * Sensor supplied in the constructor. - * @return A pointer to the variable object - */ - Variable* begin(Sensor* parentSense); + virtual ~Variable() = default; - /** - * @brief Begin for the Variable object - * - * @param calcFxn Any function returning a float value. Supersedes any - * function supplied in the constructor. - * @param decimalResolution The resolution (in decimal places) of the value. - * Supersedes any value supplied in the constructor. - * @param varName The name of the variable per the ODM2 variable name - * controlled vocabulary. Supersedes any value supplied in the constructor. - * @param varUnit The unit of the variable per the ODM2 unit controlled - * vocabulary. Supersedes any value supplied in the constructor. - * @param varCode A custom code for the variable. Supersedes any value - * supplied in the constructor. - * @param uuid A universally unique identifier for the variable. - * Supersedes any value supplied in the constructor. - * @return A pointer to the variable object - * - * @warning The `calcFxn` absolutely must return a float value. If it - * returns a value of any other type (ie, some type of integer), your - * program will compile but immediately hang. - */ - Variable* begin(float (*calcFxn)(), uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode, const char* uuid); - /** - * @brief Begin for the Variable object - * - * @param calcFxn Any function returning a float value. Supersedes any - * function supplied in the constructor. - * @param decimalResolution The resolution (in decimal places) of the value. - * Supersedes any value supplied in the constructor. - * @param varName The name of the variable per the ODM2 variable name - * controlled vocabulary. Supersedes any value supplied in the constructor. - * @param varUnit The unit of the variable per the ODM2 unit controlled - * vocabulary. Supersedes any value supplied in the constructor. - * @param varCode A custom code for the variable. Supersedes any value - * supplied in the constructor. - * @return A pointer to the variable object - * - * @warning The `calcFxn` absolutely must return a float value. If it - * returns a value of any other type (ie, some type of integer), your - * program will compile but immediately hang. - */ - Variable* begin(float (*calcFxn)(), uint8_t decimalResolution, - const char* varName, const char* varUnit, - const char* varCode); - - // This sets up the variable (generally attaching it to its parent) - // bool setup(void); /** * @brief Notify the parent sensor that it has an observing variable. @@ -262,7 +142,7 @@ class Variable { * * @return The parent sensor name */ - String getParentSensorName(void); + String getParentSensorName(); /** * @brief Get the parent sensor name and location, if applicable. * @@ -270,7 +150,7 @@ class Variable { * * @return The parent sensor's concatenated name and location. */ - String getParentSensorNameAndLocation(void); + String getParentSensorNameAndLocation(); /** * @brief Set the calculation function for a calculated variable @@ -285,7 +165,7 @@ class Variable { * * @return the variable resolution */ - uint8_t getResolution(void); + uint8_t getResolution(); /** * @brief Set the variable's resolution * @@ -297,7 +177,7 @@ class Variable { * * @return The variable name */ - String getVarName(void); + String getVarName(); /** * @brief Set the variable name. * @@ -313,7 +193,7 @@ class Variable { * * @return The variable unit */ - String getVarUnit(void); + String getVarUnit(); /** * @brief Set the variable unit. * @@ -329,7 +209,7 @@ class Variable { * * @return The customized code for the variable */ - String getVarCode(void); + String getVarCode(); /** * @brief Set a customized code for the variable * @@ -339,18 +219,18 @@ class Variable { void setVarCode(const char* varCode); // This gets/sets the variable UUID, if one has been assigned /** - * @brief Get the customized code for the variable + * @brief Get the variable's UUID as a String * - * @return The customized code for the variable + * @return The variable's UUID as a String */ - String getVarUUIDString(void); + String getVarUUIDString(); // This gets/sets the variable UUID, if one has been assigned /** - * @brief Get the customized code for the variable + * @brief Get the variable's UUID as a C-style string * - * @return The customized code for the variable + * @return The variable's UUID as a const char* (or nullptr if not assigned) */ - const char* getVarUUID(void); + const char* getVarUUID(); /** * @brief Set a customized code for the variable * @@ -358,14 +238,14 @@ class Variable { */ void setVarUUID(const char* uuid); /** - * @brief Verify the the UUID is correctly formatted + * @brief Verify the UUID is correctly formatted * * @return True if the UUID is correctly formatted. * * @note This only checks the _format_ of the UUID. It does not in any way * indicate that the value of the UUID is correct. */ - bool checkUUIDFormat(void); + bool checkUUIDFormat(); /** * @brief Get current value of the variable as a float @@ -404,14 +284,44 @@ class Variable { */ bool isCalculated = false; + /** + * @brief Convert measurement resolution to appropriate decimal places + * + * This static utility function converts any measurement resolution value to + * the appropriate number of decimal places for variable resolution + * settings. Works with any float resolution value (voltage, temperature, + * pressure, etc.). + * + * @param resolution The measurement resolution (e.g., volts per LSB, + * degrees per count, etc.) + * @return The number of decimal places needed to represent the resolution + */ + static inline uint8_t floatResolutionToDecimalPlaces(float resolution) { + if (resolution <= 0.0f || isnan(resolution) || isinf(resolution)) { + return 4; // Default to 4 decimal places for invalid input + } + + // Calculate the number of decimal places needed to represent the + // resolution. We want at least one significant digit beyond the + // resolution + float log10Resolution = log10f(resolution); + int decimalPlaces = static_cast(ceilf(-log10Resolution)) + 1; + + // Clamp to reasonable bounds (0-6 decimal places) + if (decimalPlaces < 0) { decimalPlaces = 0; } + if (decimalPlaces > 6) { decimalPlaces = 6; } + + return static_cast(decimalPlaces); + } + protected: /** * @brief The current data value * * When we create the variable, we also want to initialize it with a current - * value of -9999 (ie, a bad result). + * value of #MS_INVALID_VALUE (i.e., a bad result). */ - float _currentValue = -9999; + float _currentValue = MS_INVALID_VALUE; private: @@ -419,7 +329,7 @@ class Variable { * @brief Private reference to function used to calculate the variables * value. */ - float (*_calcFxn)(void) = nullptr; + float (*_calcFxn)() = nullptr; /** diff --git a/src/WatchDogs/WatchDogAVR.cpp b/src/WatchDogs/WatchDogAVR.cpp index 32d34d062..10a725c8a 100644 --- a/src/WatchDogs/WatchDogAVR.cpp +++ b/src/WatchDogs/WatchDogAVR.cpp @@ -63,7 +63,7 @@ void extendedWatchDogAVR::enableWatchDog() { // Bit 3: WDE (Watchdog System Reset Enable) - 0 (Clear?) // Bits 2:0 Watchdog timer prescaler [WDP2:0] - see delay interval patterns - // maxium delay interval: + // maximum delay interval: // 0bxx1xx001 = 1048576 clock cycles = ~8 seconds @ 8MHz sei(); // re-enable interrupts @@ -98,12 +98,16 @@ void extendedWatchDogAVR::clearWDTInterrupt() { /** * @brief ISR for watchdog early warning + * + * This makes multi cycle WDT possible. */ ISR(WDT_vect) { MS_DEEP_DBG(F("\nWatchdog interrupt!")); - extendedWatchDogAVR::_barksUntilReset--; // Increment down the counter, - // makes multi cycle WDT possible - if (extendedWatchDogAVR::_barksUntilReset <= 0) { + // Decrement the counter, but don't allow to roll-over + if (extendedWatchDogAVR::_barksUntilReset > 0) { + extendedWatchDogAVR::_barksUntilReset--; + } + if (extendedWatchDogAVR::_barksUntilReset == 0) { MS_DEEP_DBG(F("The dog has barked enough; resetting the board.")); MCUSR = 0; // reset flags @@ -125,3 +129,5 @@ ISR(WDT_vect) { } #endif + +// cSpell:words MCUSR WDTCSR WDCE WDRF WDIF WDIE WDT_vect diff --git a/src/WatchDogs/WatchDogSAMD.cpp b/src/WatchDogs/WatchDogSAMD.cpp index af6fb1361..53381a41e 100644 --- a/src/WatchDogs/WatchDogSAMD.cpp +++ b/src/WatchDogs/WatchDogSAMD.cpp @@ -26,7 +26,7 @@ void extendedWatchDogSAMD::setupWatchDog(uint32_t resetTime_s) { extendedWatchDogSAMD::_barksUntilReset = _resetTime_s / MAXIMUM_WATCHDOG_PERIOD; MS_DEEP_DBG(F("WDT Reset time:"), _resetTime_s, - F(" Maximim time between barks"), MAXIMUM_WATCHDOG_PERIOD, + F(" Maximum time between barks"), MAXIMUM_WATCHDOG_PERIOD, F(" Barks until reset:"), _barksUntilReset); // Steps: @@ -38,7 +38,7 @@ void extendedWatchDogSAMD::setupWatchDog(uint32_t resetTime_s) { // - The WDT **cannot** be cleared until after the end of the // closed-window period. Attempting a clear during the closed window // causes a reset. - // - 0xB - 16384 clockcycles @ 1024hz = 16 seconds + // - 0xB - 16384 clock cycles @ 1024hz = 16 seconds // - Set the watchdog time-out period to the maximum value // - In windowed mode, the time-out period (or open window) starts at // the end of the closed window period @@ -48,9 +48,9 @@ void extendedWatchDogSAMD::setupWatchDog(uint32_t resetTime_s) { // is, because we're going to clear the watchdog or issue a reset as // soon as the early warning interrupt fires to tell us that the open // window has started. - // - 0xB - 16384 clockcycles @ 1024hz = 16 seconds + // - 0xB - 16384 clock cycles @ 1024hz = 16 seconds // - Set the watchdog early warning offset value to the minimum value. - // - 0x0 - 8 clockcycles @ 1024hz ~= 7.8ms + // - 0x0 - 8 clock cycles @ 1024hz ~= 7.8ms // - The early warning offset isn't relevant in windowed mode. // - Enable windowed mode @@ -110,20 +110,20 @@ void extendedWatchDogSAMD::enableWatchDog() { WDT->CONFIG.bit.PER = 0xB; // Period/Open window period // ^^ Use the maximum period for both windowed and normal mode - // 0xB = 16384 clockcycles @ 1024hz = 16 seconds + // 0xB = 16384 clock cycles @ 1024hz = 16 seconds WDT->CONFIG.bit.WINDOW = 0xB; // Closed window period // ^^ Use the maximum closed window period for windowed mode // This is irrelevant for normal mode - // 0xB = 16384 clockcycles @ 1024hz = 16 seconds + // 0xB = 16384 clock cycles @ 1024hz = 16 seconds WDT->EWCTRL.bit.EWOFFSET = 0xA; // Early Warning Offset // ^^ The Early Warning Offset bits define the number of GCLK_WDT clocks // before the interrupt is generated, relative to the start of the watchdog // time-out period. This must be less than the size of the watchdog period // or the interrupt will not be generated. - // Use the maximum offset for the longest time before the interrut in normal - // mode. + // Use the maximum offset for the longest time before the interrupt in + // normal mode. // This is irrelevant in windowed mode. - // 0xA = 8192 clockcycles @ 1024hz = 8 seconds + // 0xA = 8192 clock cycles @ 1024hz = 8 seconds // WDT->INTFLAG.bit.EW = 1; // Clear any pending interrupt flags WDT->INTENSET.bit.EW = 1; // Enable early warning interrupt @@ -224,7 +224,7 @@ void extendedWatchDogSAMD::configureClockGenerator() { // better. // NOTE: The generic clock generator must be enabled by performing a single // 32-bit write to the Generic Clock Generator Control register (GENCTRL) - - // ie, do this all in one step. + // i.e., do this all in one step. // NOTE: Per the manual 15.8.4, the run in standby setting // (GCLK_GENCTRL_RUNSTDBY) for the generic clock generator control only // applies if the generic clock generator has been configured to be output @@ -237,7 +237,7 @@ void extendedWatchDogSAMD::configureClockGenerator() { GENERIC_CLOCK_GENERATOR_MS); GCLK->GENCTRL.reg = static_cast( GCLK_GENCTRL_ID(GENERIC_CLOCK_GENERATOR_MS) | // Select GCLK - GCLK_GENCTRL_GENEN | // Enable the generic clock clontrol + GCLK_GENCTRL_GENEN | // Enable the generic clock controller GCLK_GENCTRL_SRC_OSCULP32K | // Select the built-in ultra-low power // internal oscillator GCLK_GENCTRL_IDC | // improve duty cycle @@ -257,7 +257,7 @@ void extendedWatchDogSAMD::configureWDTClock() { //^^ SAMD21 // Per datasheet 16.6.3.3 the generic clock must be disabled before being // re-enabled with a new clock source setting. - MS_DEEP_DBG(F("Disabling WDT peripeheral clock for configuration")); + MS_DEEP_DBG(F("Disabling WDT peripheral clock for configuration")); // this will set all bits but the ID to 0, disabling everything // See https://github.com/arduino-libraries/ArduinoLowPower/issues/30 GCLK->CLKCTRL.reg = static_cast(GCLK_CLKCTRL_ID(GCM_WDT)); @@ -270,7 +270,7 @@ void extendedWatchDogSAMD::configureWDTClock() { GCLK->CLKCTRL.reg = static_cast( GCLK_CLKCTRL_GEN(GENERIC_CLOCK_GENERATOR_MS) | // Select generic clock // generator - GCLK_CLKCTRL_CLKEN | // Enable the generic clock clontrol + GCLK_CLKCTRL_CLKEN | // Enable the generic clock controller GCLK_CLKCTRL_ID(GCM_WDT)); // Feed the GCLK to the WDT waitForGCLKBitSync(); #endif @@ -327,7 +327,7 @@ void extendedWatchDogSAMD::configureEICClock() { // Per datasheet 16.6.3.3 the generic clock must be disabled before being // re-enabled with a new clock source setting. - MS_DEEP_DBG(F("Disabling EIC peripeheral clock for configuration")); + MS_DEEP_DBG(F("Disabling EIC peripheral clock for configuration")); // this will set all bits but the ID to 0, disabling everything // See https://github.com/arduino-libraries/ArduinoLowPower/issues/30 GCLK->CLKCTRL.reg = static_cast(GCLK_CLKCTRL_ID(GCM_EIC)); @@ -340,7 +340,7 @@ void extendedWatchDogSAMD::configureEICClock() { GCLK->CLKCTRL.reg = static_cast( GCLK_CLKCTRL_GEN(GENERIC_CLOCK_GENERATOR_MS) | // Select generic clock // generator - GCLK_CLKCTRL_CLKEN | // Enable the generic clock clontrol + GCLK_CLKCTRL_CLKEN | // Enable the generic clock controller GCLK_CLKCTRL_ID(GCM_EIC)); // Feed the GCLK to the EIC waitForGCLKBitSync(); @@ -427,5 +427,7 @@ void WDT_Handler(void) { } } - +// cspell:words EWCTRL EWOFFSET INTENSET ULP32KOSC GCLK_GENCTRL_GENEN APBAMASK #endif + +// cSpell:words GCLK_CLKCTRL_CLKEN irqn CKSEL diff --git a/src/WatchDogs/WatchDogSAMD.h b/src/WatchDogs/WatchDogSAMD.h index d6d99c6fe..3480ebc16 100644 --- a/src/WatchDogs/WatchDogSAMD.h +++ b/src/WatchDogs/WatchDogSAMD.h @@ -42,7 +42,7 @@ * * For a SAMD board, we can get the longest possible time between interrupts by * using the maximum closed window period in "windowed" mode and setting the - * early warning interrupt that opens the window to occur at the mimimum + * early warning interrupt that opens the window to occur at the minimum * possible time before a reset fires. The maximum number of clock cycles for * the closed window period is 16384 cycles on both a SAMD21 and SAM(D/E)51. * @@ -50,7 +50,7 @@ * CLK_WDT_OSC clock sourced from the ULP32KOSC. * * On a SAMD21 the WDT can be clocked from any clock source with the maximum - * dividor depending on the selected clock generator. To save power, we force + * divider depending on the selected clock generator. To save power, we force * the SAMD21 to use the ULP32KOSC for the WDT and EIC. For simplicity of code, * we use a 32x divisor on the ULP32KOSC to match the SAM(D/E)51 1.024kHz * CLK_WDT_OSC. @@ -71,7 +71,7 @@ /** * @brief ISR handler for watchdog timer early warning (WDT EW) interrupt */ -void WDT_Handler(void); +void WDT_Handler(); /** * @brief The extendedWatchDogSAMD class uses the early warning interrupt to of @@ -136,7 +136,7 @@ class extendedWatchDogSAMD { static void configureWDTClock(); /** * @brief Configure the peripheral clock for the external interrupt - * congtroller (EIC) - sourced from the generic clock generator + * controller (EIC) - sourced from the generic clock generator */ static void configureEICClock(); @@ -146,11 +146,11 @@ class extendedWatchDogSAMD { static void clearWDTInterrupt(); /** - * @brief Wait for the WDT config bit sync to finish.+ + * @brief Wait for the WDT config bit sync to finish. */ static void inline waitForWDTBitSync(); /** - * @brief Wait for the GCLK config bit sync to finish.+ + * @brief Wait for the GCLK config bit sync to finish. */ static void inline waitForGCLKBitSync(); @@ -168,3 +168,5 @@ class extendedWatchDogSAMD { }; #endif // SRC_WATCHDOGS_WATCHDOGSAMD_H_ + +// cSpell:words ULP32KOSC diff --git a/src/dataPublisherBase.cpp b/src/dataPublisherBase.cpp index b58d8cc3a..bc6fd603b 100644 --- a/src/dataPublisherBase.cpp +++ b/src/dataPublisherBase.cpp @@ -22,26 +22,29 @@ const char* dataPublisher::putHeader = "PUT "; const char* dataPublisher::HTTPtag = " HTTP/1.1"; const char* dataPublisher::hostHeader = "\r\nHost: "; -// Constructors -dataPublisher::dataPublisher() {} - -dataPublisher::dataPublisher(Logger& baseLogger, int sendEveryX) - : _baseLogger(&baseLogger), - _inClient(nullptr), - _sendEveryX(sendEveryX) { - _baseModem = - _baseLogger->registerDataPublisher(this); // register self with logger -} +// Primary constructor dataPublisher::dataPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX) + int sendEveryX, uint8_t startupTransmissions) : _baseLogger(&baseLogger), _inClient(inClient), - _sendEveryX(sendEveryX) { + _sendEveryX(sendEveryX), + _startupTransmissions(startupTransmissions < 1 ? 1 + : startupTransmissions) { _baseModem = _baseLogger->registerDataPublisher(this); // register self with logger } -// Destructor -dataPublisher::~dataPublisher() {} +// Constructors +dataPublisher::dataPublisher(Logger& baseLogger, int sendEveryX, + uint8_t startupTransmissions) + : dataPublisher(baseLogger, nullptr, sendEveryX, startupTransmissions) {} +// Default constructor with explicit initialization to ensure all members are +// initialized +dataPublisher::dataPublisher() + : _baseLogger(nullptr), + _baseModem(nullptr), + _inClient(nullptr), + _sendEveryX(1), + _startupTransmissions(DEFAULT_STARTUP_TRANSMISSIONS) {} // Sets the client @@ -70,6 +73,25 @@ void dataPublisher::setSendInterval(int sendEveryX) { } +// Get the number of startup transmissions +uint8_t dataPublisher::getStartupTransmissions() const { + return _startupTransmissions; +} + + +// Set the number of startup transmissions to send immediately after logging +void dataPublisher::setStartupTransmissions(uint8_t count) { + // Ensure minimum of 1 transmission + if (count < 1) { + MS_DBG(F("Startup transmissions count too low, setting to 1")); + _startupTransmissions = 1; + } else { + _startupTransmissions = count; + MS_DBG(F("Startup transmissions set to:"), _startupTransmissions); + } +} + + // "Begins" the publisher - attaches client and logger void dataPublisher::begin(Logger& baseLogger, Client* inClient) { setClient(inClient); @@ -180,7 +202,7 @@ void dataPublisher::txBufferFlush(bool debug_flush) { } } -bool dataPublisher::connectionNeeded(void) { +bool dataPublisher::connectionNeeded() { // connection is always needed unless publisher has special logic return true; } diff --git a/src/dataPublisherBase.h b/src/dataPublisherBase.h index 73c4e2ec9..4cf377389 100644 --- a/src/dataPublisherBase.h +++ b/src/dataPublisherBase.h @@ -45,6 +45,20 @@ #include "LoggerBase.h" #include "Client.h" +// HTTP response parsing constants +/** + * @brief Length of the HTTP version prefix "HTTP/1.1 " used when parsing HTTP + * response codes + */ +#define HTTP_VERSION_PREFIX_LEN 9 + +// Data publisher defaults +/** + * @brief Default number of startup transmissions to send immediately after each + * data point + */ +#define DEFAULT_STARTUP_TRANSMISSIONS 5 + /** * @brief The dataPublisher class is a virtual class used by other publishers to * distribute data online. @@ -80,8 +94,15 @@ class dataPublisher { * @param baseLogger The logger supplying the data to be published * @param sendEveryX Interval (in units of the logging interval) between * attempted data transmissions. Not respected by all publishers. + * @param startupTransmissions Number of transmissions to send immediately + * after each data point is logged, before beginning to cache data and only + * transmit every sendEveryX times the logger records data (default: 5). + * This allows faster in-field validation of startup data. Not respected by + * all publishers. */ - explicit dataPublisher(Logger& baseLogger, int sendEveryX = 1); + explicit dataPublisher( + Logger& baseLogger, int sendEveryX = 1, + uint8_t startupTransmissions = DEFAULT_STARTUP_TRANSMISSIONS); /** * @brief Construct a new data publisher object. * @@ -95,12 +116,18 @@ class dataPublisher { * single TinyGSM modem instance * @param sendEveryX Interval (in units of the logging interval) between * attempted data transmissions. Not respected by all publishers. + * @param startupTransmissions Number of transmissions to send immediately + * after each data point is logged, before beginning to cache data and only + * transmit every sendEveryX times the logger records data (default: 5). + * This allows faster in-field validation of startup data. Not respected by + * all publishers. */ - dataPublisher(Logger& baseLogger, Client* inClient, int sendEveryX = 1); + dataPublisher(Logger& baseLogger, Client* inClient, int sendEveryX = 1, + uint8_t startupTransmissions = DEFAULT_STARTUP_TRANSMISSIONS); /** * @brief Destroy the data publisher object - no action is taken. */ - virtual ~dataPublisher(); + virtual ~dataPublisher() = default; /** * @brief Set the Client object. @@ -123,6 +150,28 @@ class dataPublisher { */ void setSendInterval(int sendEveryX); + /** + * @brief Get the number of startup transmissions + * + * @return The number of transmissions that will be sent at one minute + * intervals for faster in-field validation + */ + uint8_t getStartupTransmissions() const; + + /** + * @brief Set the number of startup transmissions to send immediately after + * logging + * + * This controls how many of the first data points are transmitted + * immediately after each is logged, before beginning to cache data and only + * transmit every sendEveryX times the logger records data. This allows + * faster in-field validation of initial data. + * + * @param count Number of startup transmissions (must be 1-255, will be + * clamped to this range) + */ + void setStartupTransmissions(uint8_t count); + /** * @brief Attach the publisher to a logger. * @@ -176,7 +225,7 @@ class dataPublisher { * * @return The URL or HOST to receive published data */ - virtual String getEndpoint(void) = 0; + virtual String getEndpoint() = 0; /** @@ -185,7 +234,7 @@ class dataPublisher { * * @return True if an internet connection is needed for the next publish. */ - virtual bool connectionNeeded(void); + virtual bool connectionNeeded(); /** * @brief Opens a socket to the correct receiver and sends out the formatted @@ -201,9 +250,8 @@ class dataPublisher { * @return The result of publishing data. May be an http response code or a * result code from PubSubClient. */ - virtual int16_t - publishData(Client* outClient, - bool forceFlush = MS_ALWAYS_FLUSH_PUBLISHERS) = 0; + virtual int16_t publishData( + Client* outClient, bool forceFlush = MS_ALWAYS_FLUSH_PUBLISHERS) = 0; /** * @brief Open a socket to the correct receiver and send out the formatted * data. @@ -369,6 +417,10 @@ class dataPublisher { * memory leak because we cannot delete from the pointer because the * destructor for a client in the Arduino core isn't virtual. * + * @note The client must be deleted by the same type of modem that created + * it. This is unlikely to be an issue unless you're trying to use two + * modems on the same logger. + * * @param client The client to delete */ virtual void deleteClient(Client* client); @@ -379,6 +431,18 @@ class dataPublisher { */ int _sendEveryX = 1; + /** + * @brief The number of startup transmissions to send immediately after + * logging + * + * We send each of the first several data points immediately after they are + * logged, before beginning to cache data and only transmit every sendEveryX + * times the logger records data. This value is user-settable via + * constructor parameter or setStartupTransmissions() and allows faster + * in-field validation. + */ + uint8_t _startupTransmissions = DEFAULT_STARTUP_TRANSMISSIONS; + // Basic chunks of HTTP /** * @brief the text "GET " diff --git a/src/modems/DigiXBee.cpp b/src/modems/DigiXBee.cpp index 371a3fdc7..79d7bf5f9 100644 --- a/src/modems/DigiXBee.cpp +++ b/src/modems/DigiXBee.cpp @@ -21,36 +21,28 @@ DigiXBee::DigiXBee(int8_t powerPin, int8_t statusPin, bool useCTSStatus, XBEE_DISCONNECT_TIME_MS, XBEE_WAKE_DELAY_MS, XBEE_AT_RESPONSE_TIME_MS) {} -// Destructor -DigiXBee::~DigiXBee() {} - // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean // After enabling pin sleep, the sleep request pin is held `LOW` to keep the // XBee on. Enable pin sleep in the setup function or using XCTU prior to // connecting the XBee -bool DigiXBee::modemWakeFxn(void) { +bool DigiXBee::modemWakeFxn() { if (_modemSleepRqPin >= 0) { - // Don't go to sleep if there's not a wake pin! MS_DBG(F("Setting pin"), _modemSleepRqPin, _wakeLevel ? F("HIGH") : F("LOW"), F("to wake"), _modemName); digitalWrite(_modemSleepRqPin, _wakeLevel); - return true; - } else { - return true; } + return true; } -bool DigiXBee::modemSleepFxn(void) { +bool DigiXBee::modemSleepFxn() { if (_modemSleepRqPin >= 0) { MS_DBG(F("Setting pin"), _modemSleepRqPin, !_wakeLevel ? F("HIGH") : F("LOW"), F("to put"), _modemName, F("to sleep")); digitalWrite(_modemSleepRqPin, !_wakeLevel); - return true; - } else { - return true; } + return true; } diff --git a/src/modems/DigiXBee.h b/src/modems/DigiXBee.h index a1a4f466b..8b8b13d7a 100644 --- a/src/modems/DigiXBee.h +++ b/src/modems/DigiXBee.h @@ -127,9 +127,9 @@ * indirectly with `CTS_N/DIO7`. The status level will depend on which is * being used: * - the `ON/SLEEP_N/DIO9` will be `HIGH` when the XBee is awake - * (ie, yes, I am not sleeping) + * (i.e., yes, I am not sleeping) * - but the `CTS_N/DIO7` will be `LOW` when the - * board is awake (ie, no it's not not clear to send). + * board is awake (i.e., no it's not not clear to send). * * To use the `CTS_N/DIO7` as the status indicator, set useCTSStatus to true in * the constructor. @@ -221,11 +221,11 @@ class DigiXBee : public loggerModem { /** * @brief Destroy the Digi XBee object - no action taken */ - virtual ~DigiXBee(); + ~DigiXBee() override = default; protected: - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; }; /**@}*/ #endif // SRC_MODEMS_DIGIXBEE_H_ diff --git a/src/modems/DigiXBee3GBypass.cpp b/src/modems/DigiXBee3GBypass.cpp index f338f6804..e1ed67f1d 100644 --- a/src/modems/DigiXBee3GBypass.cpp +++ b/src/modems/DigiXBee3GBypass.cpp @@ -28,9 +28,6 @@ DigiXBee3GBypass::DigiXBee3GBypass(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -DigiXBee3GBypass::~DigiXBee3GBypass() {} - MS_IS_MODEM_AWAKE(DigiXBee3GBypass); MS_MODEM_WAKE(DigiXBee3GBypass); @@ -38,12 +35,12 @@ MS_MODEM_CONNECT_INTERNET(DigiXBee3GBypass); MS_MODEM_DISCONNECT_INTERNET(DigiXBee3GBypass); MS_MODEM_IS_INTERNET_AVAILABLE(DigiXBee3GBypass); -MS_MODEM_CREATE_CLIENT(DigiXBee3GBypass); -MS_MODEM_DELETE_CLIENT(DigiXBee3GBypass); -MS_MODEM_CREATE_SECURE_CLIENT(DigiXBee3GBypass); -MS_MODEM_DELETE_SECURE_CLIENT(DigiXBee3GBypass); +MS_MODEM_CREATE_CLIENT(DigiXBee3GBypass, UBLOX); +MS_MODEM_DELETE_CLIENT(DigiXBee3GBypass, UBLOX); +MS_MODEM_CREATE_SECURE_CLIENT(DigiXBee3GBypass, UBLOX); +MS_MODEM_DELETE_SECURE_CLIENT(DigiXBee3GBypass, UBLOX); -MS_MODEM_GET_NIST_TIME(DigiXBee3GBypass); +MS_MODEM_GET_NIST_TIME(DigiXBee3GBypass, UBLOX); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(DigiXBee3GBypass); MS_MODEM_GET_MODEM_BATTERY_DATA(DigiXBee3GBypass); @@ -51,7 +48,7 @@ MS_MODEM_GET_MODEM_BATTERY_DATA(DigiXBee3GBypass); // mode MS_MODEM_GET_MODEM_TEMPERATURE_DATA(DigiXBee3GBypass); -bool DigiXBee3GBypass::extraModemSetup(void) { +bool DigiXBee3GBypass::extraModemSetup() { bool success = false; MS_DBG(F("Putting XBee into command mode...")); for (uint8_t i = 0; i < 5; i++) { @@ -107,7 +104,7 @@ bool DigiXBee3GBypass::extraModemSetup(void) { /** Disassociate from the network for the lowest power deep sleep. */ MS_DBG(F("Setting Other Options...")); /** Disable remote manager and enable 2G fallback. */ - gsmModem.sendAT(GF("DO"), 02); + gsmModem.sendAT(GF("DO"), 2); success &= gsmModem.waitResponse(GF("OK\r")) == 1; /** Make sure airplane mode is off - bypass and airplane mode are * incompatible. */ @@ -148,7 +145,7 @@ bool DigiXBee3GBypass::extraModemSetup(void) { return success; } -bool DigiXBee3GBypass::modemHardReset(void) { +bool DigiXBee3GBypass::modemHardReset() { bool success = false; // If the u-blox cellular component isn't responding but the Digi processor // is, use the Digi API to reset the cellular component @@ -162,7 +159,7 @@ bool DigiXBee3GBypass::modemHardReset(void) { } if (success) { MS_DBG(F("... and forcing a reset of the cellular component.")); - // Force a reset of the undelying cellular component + // Force a reset of the underlying cellular component gsmModem.sendAT(GF("!R")); success &= gsmModem.waitResponse(30000L, GF("OK\r")) == 1; // Exit command mode diff --git a/src/modems/DigiXBee3GBypass.h b/src/modems/DigiXBee3GBypass.h index d6d03d529..a534e43a9 100644 --- a/src/modems/DigiXBee3GBypass.h +++ b/src/modems/DigiXBee3GBypass.h @@ -73,7 +73,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientUBLOX.h" #undef TINY_GSM_MODEM_HAS_WIFI #include "DigiXBee.h" @@ -125,36 +125,37 @@ class DigiXBee3GBypass : public DigiXBee { /** * @brief Destroy the Digi XBee 3G Bypass object - no action needed */ - ~DigiXBee3GBypass(); + ~DigiXBee3GBypass() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool modemHardReset(void) override; + bool modemHardReset() override; #ifdef MS_DIGIXBEE3GBYPASS_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -163,10 +164,10 @@ class DigiXBee3GBypass : public DigiXBee { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmUBLOX gsmModem; protected: - bool isInternetAvailable(void) override; + bool isInternetAvailable() override; /** * @copybrief loggerModem::extraModemSetup() * @@ -176,8 +177,8 @@ class DigiXBee3GBypass : public DigiXBee { * * @return True if the extra setup succeeded. */ - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/DigiXBeeCellularTransparent.cpp b/src/modems/DigiXBeeCellularTransparent.cpp index bd878b65f..ffff5e7a5 100644 --- a/src/modems/DigiXBeeCellularTransparent.cpp +++ b/src/modems/DigiXBeeCellularTransparent.cpp @@ -31,9 +31,6 @@ DigiXBeeCellularTransparent::DigiXBeeCellularTransparent( _pwd(pwd) { } -// Destructor -DigiXBeeCellularTransparent::~DigiXBeeCellularTransparent() {} - MS_IS_MODEM_AWAKE(DigiXBeeCellularTransparent); MS_MODEM_WAKE(DigiXBeeCellularTransparent); @@ -41,17 +38,17 @@ MS_MODEM_CONNECT_INTERNET(DigiXBeeCellularTransparent); MS_MODEM_DISCONNECT_INTERNET(DigiXBeeCellularTransparent); MS_MODEM_IS_INTERNET_AVAILABLE(DigiXBeeCellularTransparent); -MS_MODEM_CREATE_CLIENT(DigiXBeeCellularTransparent); -MS_MODEM_DELETE_CLIENT(DigiXBeeCellularTransparent); -MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeCellularTransparent); -MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeCellularTransparent); +MS_MODEM_CREATE_CLIENT(DigiXBeeCellularTransparent, XBee); +MS_MODEM_DELETE_CLIENT(DigiXBeeCellularTransparent, XBee); +MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeCellularTransparent, XBee); +MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeCellularTransparent, XBee); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(DigiXBeeCellularTransparent); MS_MODEM_GET_MODEM_BATTERY_DATA(DigiXBeeCellularTransparent); MS_MODEM_GET_MODEM_TEMPERATURE_DATA(DigiXBeeCellularTransparent); // We turn off airplane mode in the wake. -bool DigiXBeeCellularTransparent::modemWakeFxn(void) { +bool DigiXBeeCellularTransparent::modemWakeFxn() { if (_modemSleepRqPin >= 0) { // Don't go to sleep if there's not a wake pin! MS_DBG(F("Setting pin"), _modemSleepRqPin, @@ -74,7 +71,7 @@ bool DigiXBeeCellularTransparent::modemWakeFxn(void) { // We turn on airplane mode in before sleep -bool DigiXBeeCellularTransparent::modemSleepFxn(void) { +bool DigiXBeeCellularTransparent::modemSleepFxn() { if (_modemSleepRqPin >= 0) { MS_DBG(F("Turning on airplane mode...")); if (gsmModem.commandMode()) { @@ -98,7 +95,7 @@ bool DigiXBeeCellularTransparent::modemSleepFxn(void) { } -bool DigiXBeeCellularTransparent::extraModemSetup(void) { +bool DigiXBeeCellularTransparent::extraModemSetup() { bool success = true; /** First run the TinyGSM init() function for the XBee. */ MS_DBG(F("Initializing the XBee...")); @@ -195,7 +192,7 @@ bool DigiXBeeCellularTransparent::extraModemSetup(void) { return success; } -uint32_t DigiXBeeCellularTransparent::getNISTTime(void) { +uint32_t DigiXBeeCellularTransparent::getNISTTime() { /* bail if not connected to the internet */ if (!isInternetAvailable()) { MS_DBG(F("No internet connection, cannot connect to NIST.")); @@ -218,8 +215,9 @@ uint32_t DigiXBeeCellularTransparent::getNISTTime(void) { /* This is the IP address of time-e-wwv.nist.gov */ /* XBee's address lookup falters on time.nist.gov */ - IPAddress ip(132, 163, 97, 6); - TinyGsmClient gsmClient(gsmModem); /*create client, default mux*/ + IPAddress ip(132, 163, 97, 6); + TinyGsmXBee::GsmClientXBee gsmClient( + gsmModem); /*create client, default mux*/ connectionMade = gsmClient.connect(ip, 37, 15); /* Wait again so NIST doesn't refuse us! */ delay(4000L); @@ -239,7 +237,12 @@ uint32_t DigiXBeeCellularTransparent::getNISTTime(void) { byte response[4] = {0}; gsmClient.read(response, 4); gsmClient.stop(); - return parseNISTBytes(response); + uint32_t nistParsed = parseNISTBytes(response); + if (nistParsed != 0) { + return nistParsed; + } else { + MS_DBG(F("NIST response was invalid!")); + } } else { MS_DBG(F("NIST Time server did not respond!")); gsmClient.stop(); @@ -252,19 +255,16 @@ uint32_t DigiXBeeCellularTransparent::getNISTTime(void) { } -bool DigiXBeeCellularTransparent::updateModemMetadata(void) { - bool success = true; - +bool DigiXBeeCellularTransparent::updateModemMetadata() { // Unset whatever we had previously - loggerModem::_priorRSSI = -9999; - loggerModem::_priorSignalPercent = -9999; - loggerModem::_priorBatteryState = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorModemTemp = -9999; - + loggerModem::_priorRSSI = MS_INVALID_VALUE; + loggerModem::_priorSignalPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryState = MS_INVALID_VALUE; + loggerModem::_priorBatteryPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryVoltage = MS_INVALID_VALUE; + loggerModem::_priorModemTemp = MS_INVALID_VALUE; // Initialize variable - int16_t signalQual = -9999; + int16_t signalQual = MS_INVALID_VALUE; MS_DBG(F("Modem polling settings:"), String(_pollModemMetaData, BIN)); @@ -293,10 +293,10 @@ bool DigiXBeeCellularTransparent::updateModemMetadata(void) { MS_DBG(F("Getting signal quality:")); signalQual = gsmModem.getSignalQuality(); MS_DBG(F("Raw signal quality:"), signalQual); - if (signalQual != 0 && signalQual != -9999) break; + if (signalQual != 0 && signalQual != MS_INVALID_VALUE) break; delay(250); - } while ((signalQual == 0 || signalQual == -9999) && - millis() - startMillis < 15000L && success); + } while ((signalQual == 0 || signalQual == MS_INVALID_VALUE) && + millis() - startMillis < 15000L); // Convert signal quality to RSSI loggerModem::_priorRSSI = signalQual; @@ -321,5 +321,5 @@ bool DigiXBeeCellularTransparent::updateModemMetadata(void) { MS_DBG(F("Leaving Command Mode after updating modem metadata:")); gsmModem.exitCommand(); - return success; + return true; } diff --git a/src/modems/DigiXBeeCellularTransparent.h b/src/modems/DigiXBeeCellularTransparent.h index d05b34a7e..e48253e39 100644 --- a/src/modems/DigiXBeeCellularTransparent.h +++ b/src/modems/DigiXBeeCellularTransparent.h @@ -96,7 +96,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientXBee.h" #undef TINY_GSM_MODEM_HAS_WIFI #include "DigiXBee.h" @@ -144,51 +144,52 @@ class DigiXBeeCellularTransparent : public DigiXBee { * reference. * @param apn The Access Point Name (APN) for the SIM card. * @param user The user name, if required, associated with the APN; - * optional, defaulting to NULL + * optional, defaulting to nullptr * @param pwd The password, if required, associated with the APN; optional, - * defaulting to NULL + * defaulting to nullptr * * @see DigiXBee::DigiXBee */ DigiXBeeCellularTransparent(Stream* modemStream, int8_t powerPin, int8_t statusPin, bool useCTSStatus, int8_t modemResetPin, int8_t modemSleepRqPin, - const char* apn, const char* user = NULL, - const char* pwd = NULL); + const char* apn, const char* user = nullptr, + const char* pwd = nullptr); /** * @brief Destroy the Digi XBee Cellular Transparent object - no action * needed */ - ~DigiXBeeCellularTransparent(); + ~DigiXBeeCellularTransparent() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool updateModemMetadata(void) override; + bool updateModemMetadata() override; #ifdef MS_DIGIXBEECELLULARTRANSPARENT_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -197,12 +198,12 @@ class DigiXBeeCellularTransparent : public DigiXBee { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmXBee gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemWakeFxn(void) override; - bool modemSleepFxn(void) override; + bool isInternetAvailable() override; + bool modemWakeFxn() override; + bool modemSleepFxn() override; /** * @copybrief loggerModem::extraModemSetup() * @@ -212,8 +213,8 @@ class DigiXBeeCellularTransparent : public DigiXBee { * * @return True if the extra setup succeeded. */ - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/DigiXBeeLTEBypass.cpp b/src/modems/DigiXBeeLTEBypass.cpp index b892e4fd1..af239631a 100644 --- a/src/modems/DigiXBeeLTEBypass.cpp +++ b/src/modems/DigiXBeeLTEBypass.cpp @@ -28,9 +28,6 @@ DigiXBeeLTEBypass::DigiXBeeLTEBypass(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -DigiXBeeLTEBypass::~DigiXBeeLTEBypass() {} - MS_IS_MODEM_AWAKE(DigiXBeeLTEBypass); MS_MODEM_WAKE(DigiXBeeLTEBypass); @@ -38,18 +35,18 @@ MS_MODEM_CONNECT_INTERNET(DigiXBeeLTEBypass); MS_MODEM_DISCONNECT_INTERNET(DigiXBeeLTEBypass); MS_MODEM_IS_INTERNET_AVAILABLE(DigiXBeeLTEBypass); -MS_MODEM_CREATE_CLIENT(DigiXBeeLTEBypass); -MS_MODEM_DELETE_CLIENT(DigiXBeeLTEBypass); -MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeLTEBypass); -MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeLTEBypass); +MS_MODEM_CREATE_CLIENT(DigiXBeeLTEBypass, SaraR4); +MS_MODEM_DELETE_CLIENT(DigiXBeeLTEBypass, SaraR4); +MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeLTEBypass, SaraR4); +MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeLTEBypass, SaraR4); -MS_MODEM_GET_NIST_TIME(DigiXBeeLTEBypass); +MS_MODEM_GET_NIST_TIME(DigiXBeeLTEBypass, SaraR4); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(DigiXBeeLTEBypass); MS_MODEM_GET_MODEM_BATTERY_DATA(DigiXBeeLTEBypass); MS_MODEM_GET_MODEM_TEMPERATURE_DATA(DigiXBeeLTEBypass); -bool DigiXBeeLTEBypass::extraModemSetup(void) { +bool DigiXBeeLTEBypass::extraModemSetup() { bool success = false; MS_DBG(F("Putting XBee into command mode...")); for (uint8_t i = 0; i < 5; i++) { @@ -138,8 +135,6 @@ bool DigiXBeeLTEBypass::extraModemSetup(void) { MS_DBG(F("Attempting to reconnect to the u-blox SARA R410M module...")); success &= gsmModem.init(); _modemName = gsmModem.getModemName(); - } else { - success = false; } if (success) { @@ -150,7 +145,7 @@ bool DigiXBeeLTEBypass::extraModemSetup(void) { return success; } -bool DigiXBeeLTEBypass::modemHardReset(void) { +bool DigiXBeeLTEBypass::modemHardReset() { bool success = false; // If the u-blox cellular component isn't responding but the Digi processor // is, use the Digi API to reset the cellular component @@ -164,7 +159,7 @@ bool DigiXBeeLTEBypass::modemHardReset(void) { } if (success) { MS_DBG(F("... and forcing a reset of the cellular component.")); - // Force a reset of the undelying cellular component + // Force a reset of the underlying cellular component gsmModem.sendAT(GF("!R")); success &= gsmModem.waitResponse(30000L, GF("OK\r")) == 1; // Exit command mode diff --git a/src/modems/DigiXBeeLTEBypass.h b/src/modems/DigiXBeeLTEBypass.h index 056bd15de..ea97d9a22 100644 --- a/src/modems/DigiXBeeLTEBypass.h +++ b/src/modems/DigiXBeeLTEBypass.h @@ -89,7 +89,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSaraR4.h" #undef TINY_GSM_MODEM_HAS_WIFI #include "DigiXBee.h" @@ -140,36 +140,37 @@ class DigiXBeeLTEBypass : public DigiXBee { /** * @brief Destroy the Digi XBee LTE Bypass object - no action needed */ - ~DigiXBeeLTEBypass(); + ~DigiXBeeLTEBypass() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool modemHardReset(void) override; + bool modemHardReset() override; #ifdef MS_DIGIXBEELTEBYPASS_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -178,10 +179,10 @@ class DigiXBeeLTEBypass : public DigiXBee { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSaraR4 gsmModem; protected: - bool isInternetAvailable(void) override; + bool isInternetAvailable() override; /** * @copybrief loggerModem::extraModemSetup() * @@ -191,8 +192,8 @@ class DigiXBeeLTEBypass : public DigiXBee { * * @return True if the extra setup succeeded. */ - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/DigiXBeeWifi.cpp b/src/modems/DigiXBeeWifi.cpp index 8a92a74e2..6b8a9a835 100644 --- a/src/modems/DigiXBeeWifi.cpp +++ b/src/modems/DigiXBeeWifi.cpp @@ -32,9 +32,6 @@ DigiXBeeWifi::DigiXBeeWifi(Stream* modemStream, int8_t powerPin, _maintainAssociation(maintainAssociation) { } -// Destructor -DigiXBeeWifi::~DigiXBeeWifi() {} - MS_IS_MODEM_AWAKE(DigiXBeeWifi); MS_MODEM_WAKE(DigiXBeeWifi); @@ -87,16 +84,16 @@ bool DigiXBeeWifi::connectInternet(uint32_t maxConnectionTime) { } MS_MODEM_IS_INTERNET_AVAILABLE(DigiXBeeWifi); -MS_MODEM_CREATE_CLIENT(DigiXBeeWifi); -MS_MODEM_DELETE_CLIENT(DigiXBeeWifi); -MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeWifi); -MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeWifi); +MS_MODEM_CREATE_CLIENT(DigiXBeeWifi, XBee); +MS_MODEM_DELETE_CLIENT(DigiXBeeWifi, XBee); +MS_MODEM_CREATE_SECURE_CLIENT(DigiXBeeWifi, XBee); +MS_MODEM_DELETE_SECURE_CLIENT(DigiXBeeWifi, XBee); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(DigiXBeeWifi); MS_MODEM_GET_MODEM_BATTERY_DATA(DigiXBeeWifi); MS_MODEM_GET_MODEM_TEMPERATURE_DATA(DigiXBeeWifi); -bool DigiXBeeWifi::extraModemSetup(void) { +bool DigiXBeeWifi::extraModemSetup() { bool success = true; /** First run the TinyGSM init() function for the XBee. */ MS_DBG(F("Initializing the XBee...")); @@ -218,7 +215,7 @@ bool DigiXBeeWifi::extraModemSetup(void) { * thus brighter LED) indicates better signal quality. NOTE: Only * the `DIO10/PWM0` pin (6 on the bee socket) can be used for this * function. */ - bool changedP0 = gsmModem.changeSettingIfNeeded(GF("D5"), 1); + bool changedP0 = gsmModem.changeSettingIfNeeded(GF("P0"), 1); changesMade |= changedP0; if (changedP0) { MS_DBG(F("DIO10/PWM0 changed to"), 1); @@ -349,12 +346,13 @@ bool DigiXBeeWifi::extraModemSetup(void) { } -void DigiXBeeWifi::disconnectInternet(void) { +void DigiXBeeWifi::disconnectInternet() { // Ensure Wifi XBee IP socket torn down by forcing connection to // localhost IP For A XBee S6B bug, then force restart. - TinyGsmClient gsmClient(gsmModem); // need to create again to force close - String oldRemoteIp = gsmClient.remoteIP(); - IPAddress newHostIp = IPAddress(127, 0, 0, 1); // localhost + TinyGsmXBee::GsmClientXBee gsmClient( + gsmModem); // need to create again to force close + String oldRemoteIp = gsmClient.remoteIP(); + IPAddress newHostIp = IPAddress(127, 0, 0, 1); // localhost gsmClient.connect(newHostIp, 80); MS_DBG(gsmModem.getBeeName(), oldRemoteIp, F("disconnectInternet set to"), gsmClient.remoteIP()); @@ -364,7 +362,7 @@ void DigiXBeeWifi::disconnectInternet(void) { // Get the time from NIST via TIME protocol (rfc868) -uint32_t DigiXBeeWifi::getNISTTime(void) { +uint32_t DigiXBeeWifi::getNISTTime() { // bail if not connected to the internet if (!isInternetAvailable()) { MS_DBG(F("No internet connection, cannot connect to NIST.")); @@ -401,7 +399,7 @@ uint32_t DigiXBeeWifi::getNISTTime(void) { // NOTE: This "connect" only sets up the connection parameters, the TCP // socket isn't actually opened until we first send data (the '!' below) - TinyGsmClient gsmClient(gsmModem); + TinyGsmXBee::GsmClientXBee gsmClient(gsmModem); connectionMade = gsmClient.connect(nistIPs[i], TIME_PROTOCOL_PORT); // Need to send something before connection is made gsmClient.println('!'); @@ -419,7 +417,12 @@ uint32_t DigiXBeeWifi::getNISTTime(void) { byte response[NIST_RESPONSE_BYTES] = {0}; gsmClient.read(response, NIST_RESPONSE_BYTES); gsmClient.stop(); - return parseNISTBytes(response); + uint32_t nistParsed = parseNISTBytes(response); + if (nistParsed != 0) { + return nistParsed; + } else { + MS_DBG(F("NIST response was invalid!")); + } } else { MS_DBG(F("NIST Time server did not respond!")); gsmClient.stop(); @@ -432,16 +435,16 @@ uint32_t DigiXBeeWifi::getNISTTime(void) { } -bool DigiXBeeWifi::updateModemMetadata(void) { +bool DigiXBeeWifi::updateModemMetadata() { bool success = true; // Unset whatever we had previously - loggerModem::_priorRSSI = -9999; - loggerModem::_priorSignalPercent = -9999; - loggerModem::_priorBatteryState = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorBatteryPercent = -9999; - loggerModem::_priorModemTemp = -9999; + loggerModem::_priorRSSI = MS_INVALID_VALUE; + loggerModem::_priorSignalPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryState = MS_INVALID_VALUE; + loggerModem::_priorBatteryPercent = MS_INVALID_VALUE; + loggerModem::_priorBatteryVoltage = MS_INVALID_VALUE; + loggerModem::_priorModemTemp = MS_INVALID_VALUE; MS_DBG(F("Modem polling settings:"), String(_pollModemMetaData, BIN)); @@ -468,14 +471,14 @@ bool DigiXBeeWifi::updateModemMetadata(void) { // Try up to 5 times to get a signal quality int8_t num_trys_remaining = 5; - int16_t rssi = -9999; + int16_t rssi = MS_INVALID_VALUE; do { rssi = gsmModem.getSignalQuality(); MS_DBG(F("Raw signal quality ("), num_trys_remaining, F("):"), rssi); - if (rssi != 0 && rssi != -9999) break; + if (rssi != 0 && rssi != MS_INVALID_VALUE) break; num_trys_remaining--; - } while ((rssi == 0 || rssi == -9999) && num_trys_remaining); + } while ((rssi == 0 || rssi == MS_INVALID_VALUE) && num_trys_remaining); // Convert signal quality to a percent @@ -486,7 +489,7 @@ bool DigiXBeeWifi::updateModemMetadata(void) { loggerModem::_priorRSSI = rssi; MS_DBG(F("CURRENT RSSI:"), rssi); - success &= ((rssi != -9999) && (rssi != 0)); + success &= ((rssi != MS_INVALID_VALUE) && (rssi != 0)); } else { MS_DBG(F("Polling for both RSSI and signal strength is disabled")); } @@ -500,9 +503,10 @@ bool DigiXBeeWifi::updateModemMetadata(void) { MS_DBG(F("CURRENT Modem battery (mV):"), volt_mV); if (volt_mV != 9999) { loggerModem::_priorBatteryVoltage = - static_cast(volt_mV / 1000); + static_cast(volt_mV / 1000.0f); } else { - loggerModem::_priorBatteryVoltage = static_cast(-9999); + loggerModem::_priorBatteryVoltage = + static_cast(MS_INVALID_VALUE); } success &= ((volt_mV != 9999) && (volt_mV != 0)); @@ -513,17 +517,14 @@ bool DigiXBeeWifi::updateModemMetadata(void) { if ((_pollModemMetaData & MODEM_TEMPERATURE_ENABLE_BITMASK) == MODEM_TEMPERATURE_ENABLE_BITMASK) { MS_DBG(F("Getting chip temperature:")); - float chip_temp = -9999; - chip_temp = getModemChipTemperature(); + float chip_temp = getModemChipTemperature(); + loggerModem::_priorModemTemp = chip_temp; MS_DBG(F("CURRENT Modem temperature(C):"), loggerModem::_priorModemTemp); - if (chip_temp != -9999.f) { - loggerModem::_priorModemTemp = chip_temp; - } else { - loggerModem::_priorModemTemp = static_cast(-9999); - } - success &= ((chip_temp != -9999.f) && (chip_temp != 0)); + // TinyGSM returns -9999 when it fails to get a temperature reading, so + // check for that as well as the invalid value + success &= ((chip_temp != MS_INVALID_VALUE) && (chip_temp != -9999)); } else { MS_DBG(F("Polling for modem chip temperature is disabled")); } diff --git a/src/modems/DigiXBeeWifi.h b/src/modems/DigiXBeeWifi.h index 1c2d63dcc..2f53056e5 100644 --- a/src/modems/DigiXBeeWifi.h +++ b/src/modems/DigiXBeeWifi.h @@ -69,7 +69,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientXBee.h" #undef TINY_GSM_MODEM_HAS_GPRS #include "DigiXBee.h" @@ -135,36 +135,37 @@ class DigiXBeeWifi : public DigiXBee { /** * @brief Destroy the Digi XBee Wifi object - no action taken */ - ~DigiXBeeWifi(); + ~DigiXBeeWifi() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool updateModemMetadata(void) override; + bool updateModemMetadata() override; #ifdef MS_DIGIXBEEWIFI_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -173,10 +174,10 @@ class DigiXBeeWifi : public DigiXBee { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmXBee gsmModem; protected: - bool isInternetAvailable(void) override; + bool isInternetAvailable() override; /** * @copybrief loggerModem::extraModemSetup() * @@ -186,8 +187,8 @@ class DigiXBeeWifi : public DigiXBee { * * @return True if the extra setup succeeded. */ - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool extraModemSetup() override; + bool isModemAwake() override; private: diff --git a/src/modems/Espressif.cpp b/src/modems/Espressif.cpp index 6633a2569..ec9db5b02 100644 --- a/src/modems/Espressif.cpp +++ b/src/modems/Espressif.cpp @@ -23,12 +23,9 @@ Espressif::Espressif(Stream* modemStream, int8_t powerPin, int8_t modemResetPin, _ssid(ssid), _pwd(pwd) {} -// Destructor -Espressif::~Espressif() {} - // A helper function to wait for the esp to boot and immediately change some // settings We'll use this in the wake function -bool Espressif::ESPwaitForBoot(void) { +bool Espressif::ESPwaitForBoot() { // Wait for boot - finished when characters start coming // NOTE: After every "hard" reset (either power off or via RST-B), the ESP // sends out a boot log from the ROM on UART1 at 74880 baud. We're not @@ -54,13 +51,12 @@ bool Espressif::ESPwaitForBoot(void) { // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool Espressif::modemWakeFxn(void) { +bool Espressif::modemWakeFxn() { bool success = true; if (_powerPin >= 0) { // Turns on when power is applied MS_DEEP_DBG( F("Power pin"), _powerPin, - F("takes priority over reset pin, modem wakes on power on")); - digitalWrite(_modemSleepRqPin, !_wakeLevel); + F("takes priority over reset pin, modem wakes on power on")); if (_modemSleepRqPin >= 0) { digitalWrite(_modemSleepRqPin, !_wakeLevel); } @@ -75,7 +71,9 @@ bool Espressif::modemWakeFxn(void) { digitalWrite(_modemResetPin, LOW); delay(_resetPulse_ms); digitalWrite(_modemResetPin, HIGH); - digitalWrite(_modemSleepRqPin, !_wakeLevel); + if (_modemSleepRqPin >= 0) { + digitalWrite(_modemSleepRqPin, !_wakeLevel); + } success &= ESPwaitForBoot(); if (_modemSleepRqPin >= 0) { digitalWrite(_modemSleepRqPin, _wakeLevel); diff --git a/src/modems/Espressif.h b/src/modems/Espressif.h index 92f24c2b6..1d23731fb 100644 --- a/src/modems/Espressif.h +++ b/src/modems/Espressif.h @@ -200,7 +200,7 @@ class Espressif : public loggerModem { /** * @brief Destroy the Espressif object - no action taken */ - virtual ~Espressif(); + ~Espressif() override = default; /** * @brief A pointer to the Arduino serial Stream used for communication @@ -211,7 +211,7 @@ class Espressif : public loggerModem { Stream* _modemStream; protected: - bool modemWakeFxn(void) override; + bool modemWakeFxn() override; protected: /** @@ -222,7 +222,7 @@ class Espressif : public loggerModem { * @return True if text (assumed to be the start message) was received; * false if text was received after boot. */ - bool ESPwaitForBoot(void); + bool ESPwaitForBoot(); const char* _ssid; ///< Internal reference to the WiFi SSID const char* _pwd; ///< Internal reference to the WiFi password }; diff --git a/src/modems/EspressifESP32.cpp b/src/modems/EspressifESP32.cpp index 8e5319ef5..3a2471f44 100644 --- a/src/modems/EspressifESP32.cpp +++ b/src/modems/EspressifESP32.cpp @@ -26,9 +26,6 @@ EspressifESP32::EspressifESP32(Stream* modemStream, int8_t powerPin, { } -// Destructor -EspressifESP32::~EspressifESP32() {} - MS_IS_MODEM_AWAKE(EspressifESP32); MS_MODEM_WAKE(EspressifESP32); @@ -36,18 +33,18 @@ MS_MODEM_CONNECT_INTERNET(EspressifESP32, ESPRESSIF_RECONNECT_TIME_MS); MS_MODEM_DISCONNECT_INTERNET(EspressifESP32); MS_MODEM_IS_INTERNET_AVAILABLE(EspressifESP32); -MS_MODEM_CREATE_CLIENT(EspressifESP32); -MS_MODEM_DELETE_CLIENT(EspressifESP32); -MS_MODEM_CREATE_SECURE_CLIENT(EspressifESP32); -MS_MODEM_DELETE_SECURE_CLIENT(EspressifESP32); +MS_MODEM_CREATE_CLIENT(EspressifESP32, ESP32); +MS_MODEM_DELETE_CLIENT(EspressifESP32, ESP32); +MS_MODEM_CREATE_SECURE_CLIENT(EspressifESP32, ESP32); +MS_MODEM_DELETE_SECURE_CLIENT(EspressifESP32, ESP32); -MS_MODEM_GET_NIST_TIME(EspressifESP32); +MS_MODEM_GET_NIST_TIME(EspressifESP32, ESP32); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(EspressifESP32); MS_MODEM_GET_MODEM_BATTERY_DATA(EspressifESP32); MS_MODEM_GET_MODEM_TEMPERATURE_DATA(EspressifESP32); -bool EspressifESP32::modemSleepFxn(void) { +bool EspressifESP32::modemSleepFxn() { // Use this if you have an MCU pin connected to the ESP's reset pin to wake // from deep sleep. We'll also put it in deep sleep before yanking power. if (_modemResetPin >= 0 || _powerPin >= 0) { @@ -67,25 +64,31 @@ bool EspressifESP32::modemSleepFxn(void) { } // Set up the light-sleep status pin, if applicable -bool EspressifESP32::extraModemSetup(void) { +bool EspressifESP32::extraModemSetup() { if (_modemSleepRqPin >= 0) { digitalWrite(_modemSleepRqPin, !_wakeLevel); } - gsmModem.init(); + bool success = gsmModem.init(); + // Attempt to get the modem name even without a successful init + // The full make and model won't be returned, but it will at least be + // something that identifies the modem as an ESP32, which is helpful for + // debugging. _modemName = gsmModem.getModemName(); - // AT+CWCOUNTRY=,,, - // : - // 0: will change the county code to be the same as the AP that the - // ESP32 is connected to. - // 1: the country code will not change, always be the one set by - // command. - // : country code. Maximum length: 3 characters. Refer to ISO - // 3166-1 alpha-2 for country codes. - // : the channel number to start. Range: [1,14]. - // : total number of channels. - gsmModem.sendAT( - GF("+CWCOUNTRY=0,\"US\",1,13")); // Set country code to default to US, - // but allow to change if the AP is - gsmModem.waitResponse(); - return true; + if (success) { + // AT+CWCOUNTRY=,,, + // : + // 0: will change the county code to be the same as the AP that the + // ESP32 is connected to. + // 1: the country code will not change, always be the one set by + // command. + // : country code. Maximum length: 3 characters. Refer to + // ISO 3166-1 alpha-2 for country codes. + // : the channel number to start. Range: [1,14]. + // : total number of channels. + // We set the country code to default to US, but allow it to change if + // the AP is in a different country. + gsmModem.sendAT(GF("+CWCOUNTRY=0,\"US\",1,13")); + success &= (gsmModem.waitResponse() == 1); + } + return success; } // cSpell:ignore CWCOUNTRY diff --git a/src/modems/EspressifESP32.h b/src/modems/EspressifESP32.h index 77b8edad4..12790dedb 100644 --- a/src/modems/EspressifESP32.h +++ b/src/modems/EspressifESP32.h @@ -80,7 +80,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientESP32.h" #include "Espressif.h" #ifdef MS_ESPRESSIFESP32_DEBUG_DEEP @@ -123,34 +123,35 @@ class EspressifESP32 : public Espressif { /** * @brief Destroy the Espressif ESP32 object - no action taken */ - ~EspressifESP32(); + ~EspressifESP32() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_ESPRESSIFESP32_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -159,13 +160,13 @@ class EspressifESP32 : public Espressif { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmESP32 gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; }; /**@}*/ #endif // SRC_MODEMS_ESPRESSIFESP32_H_ diff --git a/src/modems/EspressifESP8266.cpp b/src/modems/EspressifESP8266.cpp index 40a9bc1be..d6bbcdf6f 100644 --- a/src/modems/EspressifESP8266.cpp +++ b/src/modems/EspressifESP8266.cpp @@ -26,9 +26,6 @@ EspressifESP8266::EspressifESP8266(Stream* modemStream, int8_t powerPin, { } -// Destructor -EspressifESP8266::~EspressifESP8266() {} - MS_IS_MODEM_AWAKE(EspressifESP8266); MS_MODEM_WAKE(EspressifESP8266); @@ -36,18 +33,18 @@ MS_MODEM_CONNECT_INTERNET(EspressifESP8266, ESPRESSIF_RECONNECT_TIME_MS); MS_MODEM_DISCONNECT_INTERNET(EspressifESP8266); MS_MODEM_IS_INTERNET_AVAILABLE(EspressifESP8266); -MS_MODEM_CREATE_CLIENT(EspressifESP8266); -MS_MODEM_DELETE_CLIENT(EspressifESP8266); -MS_MODEM_CREATE_SECURE_CLIENT(EspressifESP8266); -MS_MODEM_DELETE_SECURE_CLIENT(EspressifESP8266); +MS_MODEM_CREATE_CLIENT(EspressifESP8266, ESP8266); +MS_MODEM_DELETE_CLIENT(EspressifESP8266, ESP8266); +MS_MODEM_CREATE_SECURE_CLIENT(EspressifESP8266, ESP8266); +MS_MODEM_DELETE_SECURE_CLIENT(EspressifESP8266, ESP8266); -MS_MODEM_GET_NIST_TIME(EspressifESP8266); +MS_MODEM_GET_NIST_TIME(EspressifESP8266, ESP8266); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(EspressifESP8266); MS_MODEM_GET_MODEM_BATTERY_DATA(EspressifESP8266); MS_MODEM_GET_MODEM_TEMPERATURE_DATA(EspressifESP8266); -bool EspressifESP8266::modemSleepFxn(void) { +bool EspressifESP8266::modemSleepFxn() { // Use this if you have an MCU pin connected to the ESP's reset pin to wake // from deep sleep. We'll also put it in deep sleep before yanking power. if (_modemResetPin >= 0 || _powerPin >= 0) { @@ -67,9 +64,13 @@ bool EspressifESP8266::modemSleepFxn(void) { } // Set up the light-sleep status pin, if applicable -bool EspressifESP8266::extraModemSetup(void) { +bool EspressifESP8266::extraModemSetup() { if (_modemSleepRqPin >= 0) { digitalWrite(_modemSleepRqPin, !_wakeLevel); } - gsmModem.init(); + bool success = gsmModem.init(); + // Attempt to get the modem name even without a successful init + // The full make and model won't be returned, but it will at least be + // something that identifies the modem as an ESP8266, which is helpful for + // debugging. _modemName = gsmModem.getModemName(); - return true; + return success; } diff --git a/src/modems/EspressifESP8266.h b/src/modems/EspressifESP8266.h index 631e602b3..df12f6fc7 100644 --- a/src/modems/EspressifESP8266.h +++ b/src/modems/EspressifESP8266.h @@ -79,7 +79,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientESP8266.h" #include "Espressif.h" #ifdef MS_ESPRESSIFESP8266_DEBUG_DEEP @@ -122,34 +122,35 @@ class EspressifESP8266 : public Espressif { /** * @brief Destroy the Espressif ESP8266 object - no action taken */ - ~EspressifESP8266(); + ~EspressifESP8266() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_ESPRESSIFESP8266_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -158,13 +159,13 @@ class EspressifESP8266 : public Espressif { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmESP8266 gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; }; /**@}*/ #endif // SRC_MODEMS_ESPRESSIFESP8266_H_ diff --git a/src/modems/LoggerModemMacros.h b/src/modems/LoggerModemMacros.h index b732302f5..7e2d53d9c 100644 --- a/src/modems/LoggerModemMacros.h +++ b/src/modems/LoggerModemMacros.h @@ -15,6 +15,10 @@ #ifndef SRC_MODEMS_LOGGERMODEMMACROS_H_ #define SRC_MODEMS_LOGGERMODEMMACROS_H_ +#if defined(TINY_GSM_MODEM_HAS_NTP) && defined(TINY_GSM_MODEM_HAS_TIME) +#include "ClockSupport.h" +#endif + /** * @def MS_MODEM_NTP_SYNC @@ -42,7 +46,7 @@ * subclass. */ #define MS_MODEM_EXTRA_SETUP(specificModem) \ - bool specificModem::extraModemSetup(void) { \ + bool specificModem::extraModemSetup() { \ bool success = gsmModem.init(); \ _modemName = gsmModem.getModemName(); \ MS_MODEM_NTP_SYNC \ @@ -59,7 +63,7 @@ * subclass. */ #define MS_IS_MODEM_AWAKE(specificModem) \ - bool specificModem::isModemAwake(void) { \ + bool specificModem::isModemAwake() { \ if (_wakePulse_ms == 0 && _modemSleepRqPin >= 0) { \ /** If the wake up is one where a pin is held (0 wake time) and \ * that pin is defined, then we're going to check the level of the \ @@ -117,7 +121,7 @@ * subclass. */ #define MS_MODEM_WAKE(specificModem) \ - bool specificModem::modemWake(void) { \ + bool specificModem::modemWake() { \ /** Set-up pin modes. \ Because the modem calls wake BEFORE the first setup, we must set \ the pin modes in the wake function. */ \ @@ -130,7 +134,7 @@ MS_DBG(F("Wait"), _wakeDelayTime_ms - (millis() - _millisPowerOn), \ F("ms longer for warm-up")); \ while (millis() - _millisPowerOn < _wakeDelayTime_ms) { \ - /* wait*/ \ + yield(); /* wait */ \ } \ } \ \ @@ -253,7 +257,7 @@ #if defined(TINY_GSM_MODEM_HAS_GPRS) #define MS_MODEM_IS_INTERNET_AVAILABLE(specificModem) \ - bool specificModem::isInternetAvailable(void) { \ + bool specificModem::isInternetAvailable() { \ return gsmModem.isGprsConnected(); \ } @@ -327,7 +331,7 @@ } #define MS_MODEM_DISCONNECT_INTERNET(specificModem) \ - void specificModem::disconnectInternet(void) { \ + void specificModem::disconnectInternet() { \ MS_START_DEBUG_TIMER; \ gsmModem.gprsDisconnect(); \ MS_DBG(F("Disconnected from cellular network after"), \ @@ -335,10 +339,10 @@ } #else -//^^ from #if defined(TINY_GSM_MODEM_HAS_GPRS) (ie, this is wifi) +//^^ from #if defined(TINY_GSM_MODEM_HAS_GPRS) (i.e., this is wifi) #define MS_MODEM_IS_INTERNET_AVAILABLE(specificModem) \ - bool specificModem::isInternetAvailable(void) { \ + bool specificModem::isInternetAvailable() { \ return gsmModem.isNetworkConnected(); \ } @@ -421,7 +425,7 @@ } #define MS_MODEM_DISCONNECT_INTERNET(specificModem) \ - void specificModem::disconnectInternet(void) { \ + void specificModem::disconnectInternet() { \ MS_START_DEBUG_TIMER; \ gsmModem.networkDisconnect(); \ MS_DBG(F("Disconnected from WiFi network after"), \ @@ -433,15 +437,17 @@ * @brief Creates createClient functions for a specific modem subclass. * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of createClient functions specific to a single * modem subclass. */ -#define MS_MODEM_CREATE_CLIENT(specificModem) \ +#define MS_MODEM_CREATE_CLIENT(specificModem, TinyGSMType) \ Client* specificModem::createClient() { \ /* Use the new keyword to create a new client on the **heap** */ \ /* NOTE: Be sure to delete this object when you're done with it! */ \ - Client* newClient = new TinyGsmClient(gsmModem); \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClient##TinyGSMType(gsmModem); \ return newClient; \ } @@ -450,11 +456,12 @@ * clients. * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of createSecureClient functions specific to a single * modem subclass. */ -#define MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem) \ +#define MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem, TinyGSMType) \ Client* specificModem::createSecureClient( \ SSLAuthMode, SSLVersion, const char*, const char*, const char*) { \ return nullptr; \ @@ -473,91 +480,140 @@ * For modems that don't support SSL, this returns a nullptr. * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of createClient functions specific to a single * modem subclass. */ #if defined(TINY_GSM_MODEM_HAS_SSL) && defined(TINY_GSM_MODEM_CAN_SPECIFY_CERTS) -#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem) \ +#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem, TinyGSMType) \ Client* specificModem::createSecureClient() { \ /* Use the new keyword to create a new client on the **heap** */ \ /* NOTE: Be sure to delete this object when you're done with it! */ \ - Client* newClient = new TinyGsmClientSecure(gsmModem); \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType(gsmModem); \ return newClient; \ } \ Client* specificModem::createSecureClient( \ SSLAuthMode sslAuthMode, SSLVersion sslVersion, \ const char* CAcertName, const char* clientCertName, \ const char* clientKeyName) { \ - Client* newClient = new TinyGsmClientSecure( \ - gsmModem, sslAuthMode, sslVersion, CAcertName, clientCertName, \ - clientKeyName); \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType( \ + gsmModem, sslAuthMode, sslVersion, CAcertName, clientCertName, \ + clientKeyName); \ return newClient; \ } \ Client* specificModem::createSecureClient( \ const char* pskIdent, const char* psKey, SSLVersion sslVersion) { \ - Client* newClient = new TinyGsmClientSecure(gsmModem, pskIdent, psKey, \ - sslVersion); \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType( \ + gsmModem, pskIdent, psKey, sslVersion); \ return newClient; \ } \ Client* specificModem::createSecureClient(const char* pskTableName, \ SSLVersion sslVersion) { \ - Client* newClient = new TinyGsmClientSecure(gsmModem, pskTableName, \ - sslVersion); \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType( \ + gsmModem, pskTableName, sslVersion); \ return newClient; \ } #elif defined(TINY_GSM_MODEM_HAS_SSL) -#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem) \ - Client* specificModem::createSecureClient() { \ - /* Use the new keyword to create a new client on the **heap** */ \ - /* NOTE: Be sure to delete this object when you're done with it! */ \ - Client* newClient = new TinyGsmClientSecure(gsmModem); \ - return newClient; \ - } \ - MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem) +#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem, TinyGSMType) \ + Client* specificModem::createSecureClient() { \ + /* Use the new keyword to create a new client on the **heap** */ \ + /* NOTE: Be sure to delete this object when you're done with it! */ \ + Client* newClient = \ + new TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType(gsmModem); \ + return newClient; \ + } \ + MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem, TinyGSMType) #else -#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem) \ - Client* specificModem::createSecureClient() { \ - return nullptr; \ - } \ - MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem) +#define MS_MODEM_CREATE_SECURE_CLIENT(specificModem, TinyGSMType) \ + Client* specificModem::createSecureClient() { \ + return nullptr; \ + } \ + MS_MODEM_CREATE_NULL_SECURE_CLIENTS(specificModem, TinyGSMType) #endif /** + * @def MS_MODEM_DELETE_CLIENT * @brief Creates a deleteClient function for a specific modem subclass. * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of deleteClient function specific to a single modem * subclass. + * + * @warning CRITICAL: This function MUST only be called with Client* pointers + * that were created by the corresponding createClient() function. Passing a + * Client* created by createSecureClient() will cause undefined behavior. + * Always match create/delete pairs: + * - createClient() -> deleteClient() + * - createSecureClient() -> deleteSecureClient() + * + * @note Since RTTI is not available, runtime type checking cannot be performed. + * The caller is responsible for ensuring the correct delete function is used. */ -#define MS_MODEM_DELETE_CLIENT(specificModem) \ - void specificModem::deleteClient(Client* client) { \ - if (client != nullptr) { \ - TinyGsmClient* cast_pointer = static_cast(client); \ - delete cast_pointer; \ - } \ +#define MS_MODEM_DELETE_CLIENT(specificModem, TinyGSMType) \ + void specificModem::deleteClient(Client* client) { \ + if (client != nullptr) { \ + MS_DBG(F("deleteClient: Deleting client of type "), \ + F("GsmClient" #TinyGSMType)); \ + /* WARNING: This static_cast is safe ONLY if the client was */ \ + /* created by createClient(). Mismatched create/delete calls */ \ + /* will cause undefined behavior. */ \ + TinyGsm##TinyGSMType::GsmClient##TinyGSMType* cast_pointer = \ + static_cast( \ + client); \ + delete cast_pointer; \ + } else { \ + MS_DBG(F("deleteClient: Attempted to delete nullptr client")); \ + } \ } /** * @def MS_MODEM_DELETE_SECURE_CLIENT - * @brief Creates a deleteClient function for a specific modem subclass. + * @brief Creates a deleteSecureClient function for a specific modem subclass. * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of deleteSecureClient function specific to a single modem * subclass. + * + * @warning CRITICAL: This function MUST only be called with Client* pointers + * that were created by the corresponding createSecureClient() function. Passing + * a Client* created by createClient() will cause undefined behavior. Always + * match create/delete pairs: + * - createClient() -> deleteClient() + * - createSecureClient() -> deleteSecureClient() + * + * @note Since RTTI is not available, runtime type checking cannot be performed. + * The caller is responsible for ensuring the correct delete function is used. */ #if defined(TINY_GSM_MODEM_HAS_SSL) -#define MS_MODEM_DELETE_SECURE_CLIENT(specificModem) \ - void specificModem::deleteSecureClient(Client* client) { \ - if (client != nullptr) { \ - TinyGsmClientSecure* cast_pointer = \ - static_cast(client); \ - delete cast_pointer; \ - } \ +#define MS_MODEM_DELETE_SECURE_CLIENT(specificModem, TinyGSMType) \ + void specificModem::deleteSecureClient(Client* client) { \ + if (client != nullptr) { \ + MS_DBG(F("deleteSecureClient: Deleting client of type "), \ + F("GsmClientSecure" #TinyGSMType)); \ + /* WARNING: This static_cast is safe ONLY if the client was */ \ + /* created by createSecureClient(). Mismatched create/delete */ \ + /* calls will cause undefined behavior. */ \ + TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType* cast_pointer = \ + static_cast< \ + TinyGsm##TinyGSMType::GsmClientSecure##TinyGSMType*>( \ + client); \ + delete cast_pointer; \ + } else { \ + MS_DBG(F("deleteSecureClient: Attempted to delete nullptr")); \ + } \ } #else -#define MS_MODEM_DELETE_SECURE_CLIENT(specificModem) \ - void specificModem::deleteSecureClient(Client*) {} +#define MS_MODEM_DELETE_SECURE_CLIENT(specificModem, TinyGSMType) \ + void specificModem::deleteSecureClient(Client*) { \ + MS_DBG(F("deleteSecureClient: SSL not supported, no-op")); \ + } #endif /** @@ -590,13 +646,70 @@ * https://tf.nist.gov/tf-cgi/servers.cgi * * @param specificModem The modem subclass + * @param TinyGSMType The type used for the TinyGSM modem * * @return The text of a getNISTTime() function specific to a single modem * subclass. * */ -#define MS_MODEM_GET_NIST_TIME(specificModem) \ - uint32_t specificModem::getNISTTime(void) { \ +#if defined(TINY_GSM_MODEM_ESP8266) || defined(TINY_GSM_MODEM_ESP32) +#define MS_MODEM_GET_NIST_TIME(specificModem, TinyGSMType) \ + uint32_t specificModem::getNISTTime() { \ + /** Check for and bail if not connected to the internet. */ \ + if (!isInternetAvailable()) { \ + MS_DBG(F("No internet connection, cannot get network time.")); \ + return 0; \ + } \ + \ + MS_DBG(F("Asking modem to sync with NTP")); \ + gsmModem.NTPServerSync("pool.ntp.org", 0); /*UTC!*/ \ + gsmModem.waitForTimeSync(); \ + return gsmModem.getNetworkEpoch(TinyGSM_EpochStart::UNIX); \ + } +#elif defined(TINY_GSM_MODEM_HAS_NTP) && defined(TINY_GSM_MODEM_HAS_TIME) +#define MS_MODEM_GET_NIST_TIME(specificModem, TinyGSMType) \ + uint32_t specificModem::getNISTTime() { \ + /** Check for and bail if not connected to the internet. */ \ + if (!isInternetAvailable()) { \ + MS_DBG(F("No internet connection, cannot get network time.")); \ + return 0; \ + } \ + \ + MS_DBG(F("Asking modem to sync with NTP")); \ + gsmModem.NTPServerSync("pool.ntp.org", 0); /*UTC!*/ \ + gsmModem.waitForTimeSync(); \ + \ + /* Create ints to hold time parts */ \ + int seconds = 0; \ + int minutes = 0; \ + int hours = 0; \ + int day = 0; \ + int month = 0; \ + int year = 0; \ + /* Fetch the time as parts */ \ + bool success = gsmModem.getNetworkTime(&year, &month, &day, &hours, \ + &minutes, &seconds, 0); \ + if (!success) { return 0; } \ + tm timeParts = {}; \ + timeParts.tm_sec = seconds; \ + timeParts.tm_min = minutes; \ + timeParts.tm_hour = hours; \ + timeParts.tm_mday = day; \ + timeParts.tm_mon = month - 1; /* tm_mon is 0-11 */ \ + timeParts.tm_year = year - 1900; /* tm_year is since 1900 */ \ + timeParts.tm_wday = 0; /* day of week, will be calculated */ \ + timeParts.tm_yday = 0; /* day of year, will be calculated */ \ + timeParts.tm_isdst = 0; /* daylight saving time flag */ \ + time_t timeTimeT = mktime(&timeParts); \ + /* The mktime function uses 'local' time in making the timestamp. */ \ + /* We subtract whatever the processor thinks is 'local' */ \ + /* to get back to UTC.*/ \ + return static_cast(timeTimeT) - \ + loggerClock::getCoreTimeZone(); \ + } +#else +#define MS_MODEM_GET_NIST_TIME(specificModem, TinyGSMType) \ + uint32_t specificModem::getNISTTime() { \ /** Check for and bail if not connected to the internet. */ \ if (!isInternetAvailable()) { \ MS_DBG(F("No internet connection, cannot connect to NIST.")); \ @@ -605,11 +718,11 @@ \ /** Try up to 12 times to get a timestamp from NIST. */ \ for (uint8_t i = 0; i < NIST_SERVER_RETRYS; i++) { \ - while (millis() < _lastNISTrequest + 4000) { /* wait */ \ - } \ + while (millis() - _lastNISTrequest < 4000) { yield(); } \ \ /** Make TCP connection. */ \ - TinyGsmClient gsmClient(gsmModem); /*new client, default mux*/ \ + TinyGsm##TinyGSMType::GsmClient##TinyGSMType gsmClient( \ + gsmModem); /*new client, default mux*/ \ MS_DBG(F("\nConnecting to NIST daytime Server")); \ bool connectionMade = gsmClient.connect("time.nist.gov", \ TIME_PROTOCOL_PORT, 15); \ @@ -619,7 +732,9 @@ uint32_t start = millis(); \ while (gsmClient && \ gsmClient.available() < NIST_RESPONSE_BYTES && \ - millis() - start < 5000L) {} \ + millis() - start < 5000L) { \ + yield(); \ + } \ \ if (gsmClient.available() >= NIST_RESPONSE_BYTES) { \ MS_DBG(F("NIST responded after"), millis() - start, \ @@ -644,6 +759,7 @@ } \ return 0; \ } +#endif /** * @def MS_MODEM_CALC_SIGNAL_QUALITY @@ -716,8 +832,12 @@ * chargeState, int8_t& percent, int16_t& milliVolts) for modems where such * data is available. * - * This populates the entered references with -9999s for modems where such data - * is not available. + * This populates the entered references with invalid values for modems where + * such data is not available. + * + * @warning This function does **not** use #MS_INVALID_VALUE for the invalid + * values! This is because of the size of the int variables and the standards + * within TinyGSM. * * @param specificModem The modem subclass * @@ -753,7 +873,7 @@ * This is a passthrough to the specific modem's getTemperature() for modems * where such data is available * - * This returns -9999 for modems that don't return such data. + * This returns #MS_INVALID_VALUE for modems that don't return such data. * * @param specificModem The modem subclass * @@ -763,7 +883,7 @@ */ #ifdef TINY_GSM_MODEM_HAS_TEMPERATURE #define MS_MODEM_GET_MODEM_TEMPERATURE_DATA(specificModem) \ - float specificModem::getModemChipTemperature(void) { \ + float specificModem::getModemChipTemperature() { \ MS_DBG(F("Getting temperature:")); \ float temp = gsmModem.getTemperature(); \ MS_DBG(F("Temperature:"), temp); \ @@ -772,9 +892,9 @@ } #else #define MS_MODEM_GET_MODEM_TEMPERATURE_DATA(specificModem) \ - float specificModem::getModemChipTemperature(void) { \ + float specificModem::getModemChipTemperature() { \ MS_DBG(F("This modem doesn't return temperature!")); \ - return static_cast(-9999); \ + return static_cast(MS_INVALID_VALUE); \ } #endif diff --git a/src/modems/QuectelBG96.cpp b/src/modems/QuectelBG96.cpp index 7e3bdbebc..3d60ff178 100644 --- a/src/modems/QuectelBG96.cpp +++ b/src/modems/QuectelBG96.cpp @@ -30,9 +30,6 @@ QuectelBG96::QuectelBG96(Stream* modemStream, int8_t powerPin, int8_t statusPin, _apn(apn) { } -// Destructor -QuectelBG96::~QuectelBG96() {} - MS_MODEM_EXTRA_SETUP(QuectelBG96); MS_IS_MODEM_AWAKE(QuectelBG96); MS_MODEM_WAKE(QuectelBG96); @@ -41,12 +38,12 @@ MS_MODEM_CONNECT_INTERNET(QuectelBG96); MS_MODEM_DISCONNECT_INTERNET(QuectelBG96); MS_MODEM_IS_INTERNET_AVAILABLE(QuectelBG96); -MS_MODEM_CREATE_CLIENT(QuectelBG96); -MS_MODEM_DELETE_CLIENT(QuectelBG96); -MS_MODEM_CREATE_SECURE_CLIENT(QuectelBG96); -MS_MODEM_DELETE_SECURE_CLIENT(QuectelBG96); +MS_MODEM_CREATE_CLIENT(QuectelBG96, BG96); +MS_MODEM_DELETE_CLIENT(QuectelBG96, BG96); +MS_MODEM_CREATE_SECURE_CLIENT(QuectelBG96, BG96); +MS_MODEM_DELETE_SECURE_CLIENT(QuectelBG96, BG96); -MS_MODEM_GET_NIST_TIME(QuectelBG96); +MS_MODEM_GET_NIST_TIME(QuectelBG96, BG96); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(QuectelBG96); MS_MODEM_GET_MODEM_BATTERY_DATA(QuectelBG96); @@ -54,7 +51,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(QuectelBG96); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool QuectelBG96::modemWakeFxn(void) { +bool QuectelBG96::modemWakeFxn() { // Must power on and then pulse on if (_modemSleepRqPin >= 0) { MS_DBG(F("Sending a"), _wakePulse_ms, F("ms"), @@ -69,7 +66,7 @@ bool QuectelBG96::modemWakeFxn(void) { } -bool QuectelBG96::modemSleepFxn(void) { +bool QuectelBG96::modemSleepFxn() { if (_modemSleepRqPin >= 0) { // BG96 must have access to `PWRKEY` pin to sleep // Easiest to just go to sleep with the AT command rather than using @@ -82,7 +79,7 @@ bool QuectelBG96::modemSleepFxn(void) { return true; // DON'T go to sleep if we can't wake up! } -bool QuectelBG96::modemHardReset(void) { +bool QuectelBG96::modemHardReset() { digitalWrite(_modemSleepRqPin, !_wakeLevel); // set the wake pin high bool success = loggerModem::modemHardReset(); if (success) { return gsmModem.waitResponse(10000L, GF("RDY")) == 1; } diff --git a/src/modems/QuectelBG96.h b/src/modems/QuectelBG96.h index 902b94c7d..f5c64f197 100644 --- a/src/modems/QuectelBG96.h +++ b/src/modems/QuectelBG96.h @@ -88,7 +88,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientBG96.h" #include "LoggerModem.h" #ifdef MS_QUECTELBG96_DEBUG_DEEP @@ -200,36 +200,37 @@ class QuectelBG96 : public loggerModem { /** * @brief Destroy the Quectel BG96 object - no action taken */ - ~QuectelBG96(); + ~QuectelBG96() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; + void disconnectInternet() override; - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; - uint32_t getNISTTime(void) override; + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool modemHardReset(void) override; + bool modemHardReset() override; #ifdef MS_QUECTELBG96_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -238,14 +239,14 @@ class QuectelBG96 : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmBG96 gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/SIMComSIM7000.cpp b/src/modems/SIMComSIM7000.cpp index c7fd33414..3539cc154 100644 --- a/src/modems/SIMComSIM7000.cpp +++ b/src/modems/SIMComSIM7000.cpp @@ -30,9 +30,6 @@ SIMComSIM7000::SIMComSIM7000(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -SIMComSIM7000::~SIMComSIM7000() {} - MS_MODEM_EXTRA_SETUP(SIMComSIM7000); MS_IS_MODEM_AWAKE(SIMComSIM7000); MS_MODEM_WAKE(SIMComSIM7000); @@ -41,12 +38,12 @@ MS_MODEM_CONNECT_INTERNET(SIMComSIM7000); MS_MODEM_DISCONNECT_INTERNET(SIMComSIM7000); MS_MODEM_IS_INTERNET_AVAILABLE(SIMComSIM7000); -MS_MODEM_CREATE_CLIENT(SIMComSIM7000); -MS_MODEM_DELETE_CLIENT(SIMComSIM7000); -MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM7000); -MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM7000); +MS_MODEM_CREATE_CLIENT(SIMComSIM7000, Sim7000SSL); +MS_MODEM_DELETE_CLIENT(SIMComSIM7000, Sim7000SSL); +MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM7000, Sim7000SSL); +MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM7000, Sim7000SSL); -MS_MODEM_GET_NIST_TIME(SIMComSIM7000); +MS_MODEM_GET_NIST_TIME(SIMComSIM7000, Sim7000SSL); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SIMComSIM7000); MS_MODEM_GET_MODEM_BATTERY_DATA(SIMComSIM7000); @@ -54,7 +51,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SIMComSIM7000); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SIMComSIM7000::modemWakeFxn(void) { +bool SIMComSIM7000::modemWakeFxn() { // Must power on and then pulse on if (_modemSleepRqPin >= 0) { MS_DBG(F("Sending a"), _wakePulse_ms, F("ms"), @@ -68,9 +65,10 @@ bool SIMComSIM7000::modemWakeFxn(void) { } -bool SIMComSIM7000::modemSleepFxn(void) { +bool SIMComSIM7000::modemSleepFxn() { if (_modemSleepRqPin >= 0) { - // Must have access to `PWRKEY` pin to sleep + // Must have access to `PWRKEY` pin to wake up; don't go to sleep if we + // can't wake up! // Easiest to just go to sleep with the AT command rather than using // pins MS_DBG(F("Asking SIM7000 to power down")); diff --git a/src/modems/SIMComSIM7000.h b/src/modems/SIMComSIM7000.h index e1533b661..9a7aae1fa 100644 --- a/src/modems/SIMComSIM7000.h +++ b/src/modems/SIMComSIM7000.h @@ -74,7 +74,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSIM7000SSL.h" #include "LoggerModem.h" #ifdef MS_SIMCOMSIM7000_DEBUG_DEEP @@ -188,34 +188,35 @@ class SIMComSIM7000 : public loggerModem { /** * @brief Destroy the SIMComSIM7000 object - no action needed */ - ~SIMComSIM7000(); + ~SIMComSIM7000() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_SIMCOMSIM7000_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -224,14 +225,14 @@ class SIMComSIM7000 : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSim7000SSL gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/SIMComSIM7080.cpp b/src/modems/SIMComSIM7080.cpp index 1cd681f0b..617e08d00 100644 --- a/src/modems/SIMComSIM7080.cpp +++ b/src/modems/SIMComSIM7080.cpp @@ -30,10 +30,8 @@ SIMComSIM7080::SIMComSIM7080(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -SIMComSIM7080::~SIMComSIM7080() {} -bool SIMComSIM7080::extraModemSetup(void) { +bool SIMComSIM7080::extraModemSetup() { bool success = gsmModem.init(); _modemName = gsmModem.getModemName(); @@ -52,8 +50,8 @@ bool SIMComSIM7080::extraModemSetup(void) { gsmModem.waitResponse(); // Enable netlight indication of GPRS status // Enable, the netlight will be forced to enter into 64ms on/300ms off - // blinking state in GPRS data transmission service.Otherwise, the netlight - // state is not restricted. + // blinking state in GPRS data transmission service. Otherwise, the + // netlight state is not restricted. gsmModem.sendAT(F("+CNETLIGHT=1")); gsmModem.waitResponse(); @@ -71,12 +69,12 @@ MS_MODEM_CONNECT_INTERNET(SIMComSIM7080); MS_MODEM_DISCONNECT_INTERNET(SIMComSIM7080); MS_MODEM_IS_INTERNET_AVAILABLE(SIMComSIM7080); -MS_MODEM_CREATE_CLIENT(SIMComSIM7080); -MS_MODEM_DELETE_CLIENT(SIMComSIM7080); -MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM7080); -MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM7080); +MS_MODEM_CREATE_CLIENT(SIMComSIM7080, Sim7080); +MS_MODEM_DELETE_CLIENT(SIMComSIM7080, Sim7080); +MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM7080, Sim7080); +MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM7080, Sim7080); -MS_MODEM_GET_NIST_TIME(SIMComSIM7080); +MS_MODEM_GET_NIST_TIME(SIMComSIM7080, Sim7080); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SIMComSIM7080); MS_MODEM_GET_MODEM_BATTERY_DATA(SIMComSIM7080); @@ -84,7 +82,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SIMComSIM7080); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SIMComSIM7080::modemWakeFxn(void) { +bool SIMComSIM7080::modemWakeFxn() { // Must power on and then pulse on if (_modemSleepRqPin >= 0) { MS_DBG(F("Sending a"), _wakePulse_ms, F("ms"), @@ -110,7 +108,7 @@ bool SIMComSIM7080::modemWakeFxn(void) { } -bool SIMComSIM7080::modemSleepFxn(void) { +bool SIMComSIM7080::modemSleepFxn() { if (_modemSleepRqPin >= 0) { // Must have access to `PWRKEY` pin to sleep // Easiest to just go to sleep with the AT command rather than using diff --git a/src/modems/SIMComSIM7080.h b/src/modems/SIMComSIM7080.h index e6f9935ad..44b846872 100644 --- a/src/modems/SIMComSIM7080.h +++ b/src/modems/SIMComSIM7080.h @@ -66,7 +66,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSIM7080.h" #include "LoggerModem.h" #ifdef MS_SIMCOMSIM7080_DEBUG_DEEP @@ -183,34 +183,35 @@ class SIMComSIM7080 : public loggerModem { /** * @brief Destroy the SIMComSIM7080 object - no action needed */ - ~SIMComSIM7080(); + ~SIMComSIM7080() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_SIMCOMSIM7080_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -219,14 +220,14 @@ class SIMComSIM7080 : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSim7080 gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/SIMComSIM800.cpp b/src/modems/SIMComSIM800.cpp index 1f812baa3..20a3fe4dc 100644 --- a/src/modems/SIMComSIM800.cpp +++ b/src/modems/SIMComSIM800.cpp @@ -30,9 +30,6 @@ SIMComSIM800::SIMComSIM800(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -SIMComSIM800::~SIMComSIM800() {} - MS_MODEM_EXTRA_SETUP(SIMComSIM800); MS_IS_MODEM_AWAKE(SIMComSIM800); MS_MODEM_WAKE(SIMComSIM800); @@ -41,12 +38,12 @@ MS_MODEM_CONNECT_INTERNET(SIMComSIM800); MS_MODEM_DISCONNECT_INTERNET(SIMComSIM800); MS_MODEM_IS_INTERNET_AVAILABLE(SIMComSIM800); -MS_MODEM_CREATE_CLIENT(SIMComSIM800); -MS_MODEM_DELETE_CLIENT(SIMComSIM800); -MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM800); -MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM800); +MS_MODEM_CREATE_CLIENT(SIMComSIM800, Sim800); +MS_MODEM_DELETE_CLIENT(SIMComSIM800, Sim800); +MS_MODEM_CREATE_SECURE_CLIENT(SIMComSIM800, Sim800); +MS_MODEM_DELETE_SECURE_CLIENT(SIMComSIM800, Sim800); -MS_MODEM_GET_NIST_TIME(SIMComSIM800); +MS_MODEM_GET_NIST_TIME(SIMComSIM800, Sim800); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SIMComSIM800); MS_MODEM_GET_MODEM_BATTERY_DATA(SIMComSIM800); @@ -54,7 +51,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SIMComSIM800); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SIMComSIM800::modemWakeFxn(void) { +bool SIMComSIM800::modemWakeFxn() { // Must power on and then pulse on if (_modemSleepRqPin >= 0) { MS_DBG(F("Sending a"), _wakePulse_ms, F("ms"), @@ -68,9 +65,10 @@ bool SIMComSIM800::modemWakeFxn(void) { } -bool SIMComSIM800::modemSleepFxn(void) { +bool SIMComSIM800::modemSleepFxn() { if (_modemSleepRqPin >= 0) { - // Must have access to `PWRKEY` pin to sleep + // Must have access to `PWRKEY` (_modemSleepRqPin) pin to wake up; don't + // sleep without it. // Easiest to just go to sleep with the AT command rather than using // pins MS_DBG(F("Asking SIM800 to power down")); @@ -79,6 +77,6 @@ bool SIMComSIM800::modemSleepFxn(void) { return res; } else { // DON'T go to sleep if we can't wake up! gsmModem.stream.flush(); - return true; + return true; // nothing's wrong with sleeping, we just won't do it! } } diff --git a/src/modems/SIMComSIM800.h b/src/modems/SIMComSIM800.h index dfd722f0b..c950e5d99 100644 --- a/src/modems/SIMComSIM800.h +++ b/src/modems/SIMComSIM800.h @@ -28,7 +28,7 @@ * SIM800. * The one exception is the Sodaq GPRSBee **R6 and higher**, which has its own * [constructor](@ref modem_gprsbee). - * The earlier Sodaq GPRSBee's (ie, R4) do use this version. + * The earlier Sodaq GPRSBees (i.e., R4) do use this version. * * The SIM800 consumes up to 2A of power while connecting to the network. * That is 4x what a typical USB or Arduino board can supply, so expect to give @@ -78,7 +78,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSIM800.h" #include "LoggerModem.h" #ifdef MS_SIMCOMSIM800_DEBUG_DEEP @@ -190,34 +190,35 @@ class SIMComSIM800 : public loggerModem { /** * @brief Destroy the SIMComSIM800 object - no action taken */ - ~SIMComSIM800(); + ~SIMComSIM800() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_SIMCOMSIM800_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -226,14 +227,14 @@ class SIMComSIM800 : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSim800 gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/SequansMonarch.cpp b/src/modems/SequansMonarch.cpp index 29b55972a..0646059b9 100644 --- a/src/modems/SequansMonarch.cpp +++ b/src/modems/SequansMonarch.cpp @@ -30,8 +30,6 @@ SequansMonarch::SequansMonarch(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -SequansMonarch::~SequansMonarch() {} MS_IS_MODEM_AWAKE(SequansMonarch); MS_MODEM_WAKE(SequansMonarch); @@ -40,12 +38,12 @@ MS_MODEM_CONNECT_INTERNET(SequansMonarch); MS_MODEM_DISCONNECT_INTERNET(SequansMonarch); MS_MODEM_IS_INTERNET_AVAILABLE(SequansMonarch); -MS_MODEM_CREATE_CLIENT(SequansMonarch); -MS_MODEM_DELETE_CLIENT(SequansMonarch); -MS_MODEM_CREATE_SECURE_CLIENT(SequansMonarch); -MS_MODEM_DELETE_SECURE_CLIENT(SequansMonarch); +MS_MODEM_CREATE_CLIENT(SequansMonarch, SequansMonarch); +MS_MODEM_DELETE_CLIENT(SequansMonarch, SequansMonarch); +MS_MODEM_CREATE_SECURE_CLIENT(SequansMonarch, SequansMonarch); +MS_MODEM_DELETE_SECURE_CLIENT(SequansMonarch, SequansMonarch); -MS_MODEM_GET_NIST_TIME(SequansMonarch); +MS_MODEM_GET_NIST_TIME(SequansMonarch, SequansMonarch); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SequansMonarch); MS_MODEM_GET_MODEM_BATTERY_DATA(SequansMonarch); @@ -53,7 +51,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SequansMonarch); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SequansMonarch::modemWakeFxn(void) { +bool SequansMonarch::modemWakeFxn() { // Module turns on when power is applied // No pulsing required in this case if (_powerPin >= 0) { @@ -90,7 +88,7 @@ bool SequansMonarch::modemWakeFxn(void) { } -bool SequansMonarch::modemSleepFxn(void) { +bool SequansMonarch::modemSleepFxn() { if (_powerPin >= 0 || _modemResetPin >= 0) { // Module will go on with power on // Easiest to just go to sleep with the AT command rather than using @@ -119,14 +117,14 @@ bool SequansMonarch::modemSleepFxn(void) { } -bool SequansMonarch::extraModemSetup(void) { +bool SequansMonarch::extraModemSetup() { bool success = gsmModem.init(); _modemName = gsmModem.getModemName(); // Turn on the LED gsmModem.sendAT(GF("+SQNLED=1")); success &= static_cast(gsmModem.waitResponse()); // Enable power save mode if we're not going to cut power or use reset - if (!(_powerPin >= 0) && !(_modemResetPin >= 0) && _modemSleepRqPin >= 0) { + if (_powerPin < 0 && _modemResetPin < 0 && _modemSleepRqPin >= 0) { MS_DBG( "Enabling power save mode tracking area update [PSM TAU] timers"); // Requested Periodic TAU (Time in between Tracking Area Updates) = 101 diff --git a/src/modems/SequansMonarch.h b/src/modems/SequansMonarch.h index cf8df5d26..326b907ce 100644 --- a/src/modems/SequansMonarch.h +++ b/src/modems/SequansMonarch.h @@ -79,7 +79,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSequansMonarch.h" #include "LoggerModem.h" #ifdef MS_SEQUANSMONARCH_DEBUG_DEEP @@ -221,34 +221,35 @@ class SequansMonarch : public loggerModem { /** * @brief Destroy the Sequans Monarch object - no action taken */ - ~SequansMonarch(); + ~SequansMonarch() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; + void disconnectInternet() override; - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; - uint32_t getNISTTime(void) override; + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_SEQUANSMONARCH_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -257,14 +258,14 @@ class SequansMonarch : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSequansMonarch gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/modems/Sodaq2GBeeR6.cpp b/src/modems/Sodaq2GBeeR6.cpp index 614901582..a09c6f0b9 100644 --- a/src/modems/Sodaq2GBeeR6.cpp +++ b/src/modems/Sodaq2GBeeR6.cpp @@ -27,12 +27,10 @@ Sodaq2GBeeR6::Sodaq2GBeeR6(Stream* modemStream, int8_t vRefPin, setVRefPin(vRefPin); } -// Destructor -Sodaq2GBeeR6::~Sodaq2GBeeR6() {} // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool Sodaq2GBeeR6::modemWakeFxn(void) { +bool Sodaq2GBeeR6::modemWakeFxn() { if (_vRefPin >= 0) { MS_DBG(F("Enabling voltage reference for GPRSBeeR6 on pin"), _vRefPin); pinMode(_vRefPin, OUTPUT); @@ -42,22 +40,22 @@ bool Sodaq2GBeeR6::modemWakeFxn(void) { } -bool Sodaq2GBeeR6::modemSleepFxn(void) { +bool Sodaq2GBeeR6::modemSleepFxn() { // Ask the SIM800 to shut down nicely MS_DBG(F("Asking SIM800 on GPRSBeeR6 to power down")); bool success = gsmModem.poweroff(); if (_vRefPin >= 0) { MS_DBG(F("Disabling voltage reference for GPRSBeeR6 on pin"), _vRefPin); + pinMode(_vRefPin, OUTPUT); digitalWrite(_vRefPin, LOW); } gsmModem.stream.flush(); return success; } -bool Sodaq2GBeeR6::extraModemSetup(void) { +bool Sodaq2GBeeR6::extraModemSetup() { bool success = gsmModem.init(); _modemName = gsmModem.getModemName(); - if (_vRefPin >= 0) { pinMode(_vRefPin, OUTPUT); } return success; } diff --git a/src/modems/Sodaq2GBeeR6.h b/src/modems/Sodaq2GBeeR6.h index 97dd50e45..9a1f1c043 100644 --- a/src/modems/Sodaq2GBeeR6.h +++ b/src/modems/Sodaq2GBeeR6.h @@ -106,7 +106,7 @@ * @brief The loggerModem subclass for the [Sodaq 2GBee](@ref modem_gprsbee) * revisions 6 and higher based on the SIMCOM SIM800H. * - * @note The Sodaq GPRSBee doesn't expose the SIM800's reset pin.. + * @note The Sodaq GPRSBee doesn't expose the SIM800's reset pin. * * @note The power pin of the SIM800 is wired to the XBee's `DTR` pin, the * `PWR_KEY` itself is not exposed - it is tied inversely to the power in to the @@ -162,7 +162,7 @@ class Sodaq2GBeeR6 : public SIMComSIM800 { /** * @brief Destroy the Sodaq 2GBee R6 object - no action taken */ - ~Sodaq2GBeeR6(); + ~Sodaq2GBeeR6() override = default; /** * @brief Sets the pin to use to control voltage reference on the GPRSBee. @@ -173,9 +173,9 @@ class Sodaq2GBeeR6 : public SIMComSIM800 { void setVRefPin(int8_t vRefPin); protected: - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; private: /** diff --git a/src/modems/SodaqUBeeR410M.cpp b/src/modems/SodaqUBeeR410M.cpp index c538c93bc..a409abb44 100644 --- a/src/modems/SodaqUBeeR410M.cpp +++ b/src/modems/SodaqUBeeR410M.cpp @@ -51,9 +51,6 @@ SodaqUBeeR410M::SodaqUBeeR410M(Stream* modemStream, int8_t powerPin, } #endif -// Destructor -SodaqUBeeR410M::~SodaqUBeeR410M() {} - MS_IS_MODEM_AWAKE(SodaqUBeeR410M); MS_MODEM_WAKE(SodaqUBeeR410M); @@ -61,12 +58,12 @@ MS_MODEM_CONNECT_INTERNET(SodaqUBeeR410M); MS_MODEM_DISCONNECT_INTERNET(SodaqUBeeR410M); MS_MODEM_IS_INTERNET_AVAILABLE(SodaqUBeeR410M); -MS_MODEM_CREATE_CLIENT(SodaqUBeeR410M); -MS_MODEM_DELETE_CLIENT(SodaqUBeeR410M); -MS_MODEM_CREATE_SECURE_CLIENT(SodaqUBeeR410M); -MS_MODEM_DELETE_SECURE_CLIENT(SodaqUBeeR410M); +MS_MODEM_CREATE_CLIENT(SodaqUBeeR410M, SaraR4); +MS_MODEM_DELETE_CLIENT(SodaqUBeeR410M, SaraR4); +MS_MODEM_CREATE_SECURE_CLIENT(SodaqUBeeR410M, SaraR4); +MS_MODEM_DELETE_SECURE_CLIENT(SodaqUBeeR410M, SaraR4); -MS_MODEM_GET_NIST_TIME(SodaqUBeeR410M); +MS_MODEM_GET_NIST_TIME(SodaqUBeeR410M, SaraR4); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SodaqUBeeR410M); MS_MODEM_GET_MODEM_BATTERY_DATA(SodaqUBeeR410M); @@ -74,7 +71,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SodaqUBeeR410M); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SodaqUBeeR410M::modemWakeFxn(void) { +bool SodaqUBeeR410M::modemWakeFxn() { // SARA R4/N4 series must power on and then pulse on if (_modemSleepRqPin >= 0) { MS_DBG(F("Sending a"), _wakePulse_ms, F("ms"), @@ -86,7 +83,7 @@ bool SodaqUBeeR410M::modemWakeFxn(void) { // before ending pulse if (_statusPin >= 0) { uint32_t startTimer = millis(); - // 0.15-3.2s pulse for wake on SARA R4/N4 (ie, max is 3.2s) + // 0.15-3.2s pulse for wake on SARA R4/N4 (i.e., max is 3.2s) // Wait no more than 3.2s while (digitalRead(_statusPin) != static_cast(_statusLevel) && millis() - startTimer < 3200L) {} @@ -116,18 +113,7 @@ bool SodaqUBeeR410M::modemWakeFxn(void) { // The baud rate setting is NOT saved to non-volatile memory, so it must // be changed every time after loosing power. #if F_CPU == 8000000L - if (_powerPin >= 0) { - MS_DBG(F("Waiting for UART to become active and requesting a " - "slower baud rate.")); - delay(_max_at_response_time_ms + - 250); // Must wait for UART port to become active - _modemSerial->begin(115200); - gsmModem.setBaud(9600); - _modemSerial->end(); - _modemSerial->begin(9600); - gsmModem.sendAT(GF("E0")); - gsmModem.waitResponse(); - } + if (_powerPin >= 0) { configureLowBaudRate(); } #endif return true; } else { @@ -136,23 +122,22 @@ bool SodaqUBeeR410M::modemWakeFxn(void) { } -bool SodaqUBeeR410M::modemSleepFxn(void) { +bool SodaqUBeeR410M::modemSleepFxn() { + bool res = true; + // Only go to sleep if we can wake up! if (_modemSleepRqPin >= 0) { // R410 must have access to `PWR_ON` pin to sleep // Easiest to just go to sleep with the AT command rather than using // pins MS_DBG(F("Asking u-blox R410M to power down")); - bool res = gsmModem.poweroff(); - gsmModem.stream.flush(); - return res; - } else { // DON'T go to sleep if we can't wake up! - gsmModem.stream.flush(); - return true; + res = gsmModem.poweroff(); } + gsmModem.stream.flush(); + return res; } -bool SodaqUBeeR410M::modemHardReset(void) { +bool SodaqUBeeR410M::modemHardReset() { if (_modemResetPin >= 0) { MS_DBG(F("Doing a hard reset on the modem by setting pin"), _modemResetPin, _resetLevel ? F("HIGH") : F("LOW"), F("for"), @@ -162,16 +147,7 @@ bool SodaqUBeeR410M::modemHardReset(void) { delay(_resetPulse_ms); digitalWrite(_modemResetPin, !_resetLevel); #if F_CPU == 8000000L - MS_DBG(F("Waiting for UART to become active and requesting a slower " - "baud rate.")); - delay(_max_at_response_time_ms + - 250); // Must wait for UART port to become active - _modemSerial->begin(115200); - gsmModem.setBaud(9600); - _modemSerial->end(); - _modemSerial->begin(9600); - gsmModem.sendAT(GF("E0")); - gsmModem.waitResponse(); + configureLowBaudRate(); #endif return gsmModem.init(); } else { @@ -180,14 +156,30 @@ bool SodaqUBeeR410M::modemHardReset(void) { } } -bool SodaqUBeeR410M::extraModemSetup(void) { +bool SodaqUBeeR410M::extraModemSetup() { bool success = gsmModem.init(); _modemName = gsmModem.getModemName(); // Turn on network indicator light // Pin 16 = GPIO1, function 2 = network status indication gsmModem.sendAT(GF("+UGPIOC=16,2")); - gsmModem.waitResponse(); + success &= gsmModem.waitResponse() == 1; return success; } + +#if F_CPU == 8000000L +void SodaqUBeeR410M::configureLowBaudRate() { + MS_DBG(F("Waiting for UART to become active and requesting a slower " + "baud rate.")); + delay(_max_at_response_time_ms + + 250); // Must wait for UART port to become active + _modemSerial->begin(115200); + gsmModem.setBaud(9600); + _modemSerial->end(); + _modemSerial->begin(9600); + gsmModem.sendAT(GF("E0")); + gsmModem.waitResponse(); // allowed to fail +} +#endif + // cSpell:ignore UGPIOC diff --git a/src/modems/SodaqUBeeR410M.h b/src/modems/SodaqUBeeR410M.h index 10636c50f..69d672c59 100644 --- a/src/modems/SodaqUBeeR410M.h +++ b/src/modems/SodaqUBeeR410M.h @@ -135,7 +135,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientSaraR4.h" #include "LoggerModem.h" #ifdef MS_SODAQUBEER410M_DEBUG_DEEP @@ -219,12 +219,10 @@ * @brief The loggerModem subclass for the * [LTE-M](@ref modem_ubee_ltem) [Sodaq UBee](@ref modem_ublox) based on the * u-blox SARA R410M LTE-M cellular module. This can be also used for any other - * breakout of the the u-blox R4 or N4 series modules. + * breakout of the u-blox R4 or N4 series modules. */ class SodaqUBeeR410M : public loggerModem { public: - // Constructor/Destructor - #if F_CPU == 8000000L /** * @brief Construct a new Sodaq UBee R410M object @@ -299,36 +297,37 @@ class SodaqUBeeR410M : public loggerModem { /** * @brief Destroy the Sodaq UBee R410M object - no action needed */ - ~SodaqUBeeR410M(); + ~SodaqUBeeR410M() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; + void disconnectInternet() override; - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; - uint32_t getNISTTime(void) override; + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; - bool modemHardReset(void) override; + bool modemHardReset() override; #ifdef MS_SODAQUBEER410M_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -337,7 +336,7 @@ class SodaqUBeeR410M : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmSaraR4 gsmModem; #if F_CPU == 8000000L /** @@ -348,15 +347,29 @@ class SodaqUBeeR410M : public loggerModem { #endif protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN + +#if F_CPU == 8000000L || defined(DOXYGEN) + /** + * @brief Configure modem to use a lower baud rate (9600) for slow + * processors. + * + * This helper method encapsulates the baud rate switching logic needed for + * F_CPU == 8000000L to slow down the R4/N4's default 115200 baud rate. The + * baud rate setting is NOT saved to non-volatile memory, so it must be + * changed every time after losing power. + */ + void configureLowBaudRate(); +#endif }; + /**@}*/ #endif // SRC_MODEMS_SODAQUBEER410M_H_ diff --git a/src/modems/SodaqUBeeU201.cpp b/src/modems/SodaqUBeeU201.cpp index 5e49c1243..bf07a127f 100644 --- a/src/modems/SodaqUBeeU201.cpp +++ b/src/modems/SodaqUBeeU201.cpp @@ -30,9 +30,6 @@ SodaqUBeeU201::SodaqUBeeU201(Stream* modemStream, int8_t powerPin, _apn(apn) { } -// Destructor -SodaqUBeeU201::~SodaqUBeeU201() {} - MS_IS_MODEM_AWAKE(SodaqUBeeU201); MS_MODEM_WAKE(SodaqUBeeU201); @@ -40,12 +37,12 @@ MS_MODEM_CONNECT_INTERNET(SodaqUBeeU201); MS_MODEM_DISCONNECT_INTERNET(SodaqUBeeU201); MS_MODEM_IS_INTERNET_AVAILABLE(SodaqUBeeU201); -MS_MODEM_CREATE_CLIENT(SodaqUBeeU201); -MS_MODEM_DELETE_CLIENT(SodaqUBeeU201); -MS_MODEM_CREATE_SECURE_CLIENT(SodaqUBeeU201); -MS_MODEM_DELETE_SECURE_CLIENT(SodaqUBeeU201); +MS_MODEM_CREATE_CLIENT(SodaqUBeeU201, UBLOX); +MS_MODEM_DELETE_CLIENT(SodaqUBeeU201, UBLOX); +MS_MODEM_CREATE_SECURE_CLIENT(SodaqUBeeU201, UBLOX); +MS_MODEM_DELETE_SECURE_CLIENT(SodaqUBeeU201, UBLOX); -MS_MODEM_GET_NIST_TIME(SodaqUBeeU201); +MS_MODEM_GET_NIST_TIME(SodaqUBeeU201, UBLOX); MS_MODEM_GET_MODEM_SIGNAL_QUALITY(SodaqUBeeU201); MS_MODEM_GET_MODEM_BATTERY_DATA(SodaqUBeeU201); @@ -53,7 +50,7 @@ MS_MODEM_GET_MODEM_TEMPERATURE_DATA(SodaqUBeeU201); // Create the wake and sleep methods for the modem // These can be functions of any type and must return a boolean -bool SodaqUBeeU201::modemWakeFxn(void) { +bool SodaqUBeeU201::modemWakeFxn() { // SARA/LISA U2/G2 and SARA G3 series turn on when power is applied // No pulsing required in this case if (_powerPin >= 0) { return true; } @@ -72,7 +69,7 @@ bool SodaqUBeeU201::modemWakeFxn(void) { } -bool SodaqUBeeU201::modemSleepFxn(void) { +bool SodaqUBeeU201::modemSleepFxn() { if (_powerPin >= 0 || _modemSleepRqPin >= 0) { // will go on with power on // Easiest to just go to sleep with the AT command rather than using @@ -87,13 +84,16 @@ bool SodaqUBeeU201::modemSleepFxn(void) { } } -bool SodaqUBeeU201::extraModemSetup(void) { +bool SodaqUBeeU201::extraModemSetup() { bool success = gsmModem.init(); _modemName = gsmModem.getModemName(); // Turn on network indicator light // Pin 16 = GPIO1, function 2 = network status indication gsmModem.sendAT(GF("+UGPIOC=16,2")); - gsmModem.waitResponse(); + if (gsmModem.waitResponse() != 1) { + // NOTE: We don't consider setup a failure without the light + MS_DBG(F("Failed to configure network indicator LED")); + } return success; } diff --git a/src/modems/SodaqUBeeU201.h b/src/modems/SodaqUBeeU201.h index e1daebbf9..97ce6b434 100644 --- a/src/modems/SodaqUBeeU201.h +++ b/src/modems/SodaqUBeeU201.h @@ -75,7 +75,7 @@ #undef MS_DEBUGGING_DEEP // Include other in-library and external dependencies -#include "TinyGsmClient.h" +#include "TinyGsmClientUBLOX.h" #include "LoggerModem.h" #ifdef MS_SODAQUBEEU201_DEBUG_DEEP @@ -196,34 +196,35 @@ class SodaqUBeeU201 : public loggerModem { /** * @brief Destroy the Sodaq UBee U201 object - no action taken */ - ~SodaqUBeeU201(); + ~SodaqUBeeU201() override = default; - bool modemWake(void) override; + bool modemWake() override; bool connectInternet(uint32_t maxConnectionTime = 50000L) override; - void disconnectInternet(void) override; - - virtual Client* createClient() override; - virtual void deleteClient(Client* client); - virtual Client* createSecureClient() override; - virtual void deleteSecureClient(Client* client); - virtual Client* createSecureClient( - SSLAuthMode sslAuthMode, SSLVersion sslVersion = SSLVersion::TLS1_2, - const char* CAcertName = nullptr, const char* clientCertName = nullptr, - const char* clientKeyName = nullptr) override; - virtual Client* - createSecureClient(const char* pskIdent, const char* psKey, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - virtual Client* - createSecureClient(const char* pskTableName, - SSLVersion sslVersion = SSLVersion::TLS1_2) override; - - uint32_t getNISTTime(void) override; + void disconnectInternet() override; + + Client* createClient() override; + void deleteClient(Client* client) override; + Client* createSecureClient() override; + void deleteSecureClient(Client* client) override; + Client* createSecureClient(SSLAuthMode sslAuthMode, + SSLVersion sslVersion = SSLVersion::TLS1_2, + const char* CAcertName = nullptr, + const char* clientCertName = nullptr, + const char* clientKeyName = nullptr) override; + Client* createSecureClient( + const char* pskIdent, const char* psKey, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + Client* createSecureClient( + const char* pskTableName, + SSLVersion sslVersion = SSLVersion::TLS1_2) override; + + uint32_t getNISTTime() override; bool getModemSignalQuality(int16_t& rssi, int16_t& percent) override; bool getModemBatteryStats(int8_t& chargeState, int8_t& percent, int16_t& milliVolts) override; - float getModemChipTemperature(void) override; + float getModemChipTemperature() override; #ifdef MS_SODAQUBEEU201_DEBUG_DEEP StreamDebugger _modemATDebugger; @@ -232,14 +233,14 @@ class SodaqUBeeU201 : public loggerModem { /** * @brief Public reference to the TinyGSM modem. */ - TinyGsm gsmModem; + TinyGsmUBLOX gsmModem; protected: - bool isInternetAvailable(void) override; - bool modemSleepFxn(void) override; - bool modemWakeFxn(void) override; - bool extraModemSetup(void) override; - bool isModemAwake(void) override; + bool isInternetAvailable() override; + bool modemSleepFxn() override; + bool modemWakeFxn() override; + bool extraModemSetup() override; + bool isModemAwake() override; private: const char* _apn; ///< Internal reference to the cellular APN diff --git a/src/publishers/AWS_IoT_Publisher.cpp b/src/publishers/AWS_IoT_Publisher.cpp index b1ba88acb..2c0f83f1c 100644 --- a/src/publishers/AWS_IoT_Publisher.cpp +++ b/src/publishers/AWS_IoT_Publisher.cpp @@ -25,51 +25,57 @@ const char* AWS_IoT_Publisher::timestampTag = "\"timestamp\":\""; // Constructors -AWS_IoT_Publisher::AWS_IoT_Publisher() : dataPublisher() { - init(); -} -AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - init(); -} +// Primary constructor - handles full initialization with all parameters AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, Client* inClient, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - init(); -} -AWS_IoT_Publisher::AWS_IoT_Publisher( - Logger& baseLogger, const char* awsIoTEndpoint, const char* caCertName, - const char* clientCertName, const char* clientKeyName, - const char* samplingFeatureUUID, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setEndpoint(awsIoTEndpoint); - setCACertName(caCertName); - setClientCertName(clientCertName); - setClientKeyName(clientKeyName); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); + const char* awsIoTEndpoint, + const char* caCertName, + const char* clientCertName, + const char* clientKeyName, + const char* samplingFeatureUUID) + : dataPublisher(baseLogger, inClient) { + if (awsIoTEndpoint) setEndpoint(awsIoTEndpoint); + if (caCertName) setCACertName(caCertName); + if (clientCertName) setClientCertName(clientCertName); + if (clientKeyName) setClientKeyName(clientKeyName); + if (samplingFeatureUUID && _baseLogger) { + _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); + } init(); } + + +// Delegating constructors AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, const char* awsIoTEndpoint, const char* caCertName, const char* clientCertName, - const char* clientKeyName, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setEndpoint(awsIoTEndpoint); - setCACertName(caCertName); - setClientCertName(clientCertName); - setClientKeyName(clientKeyName); - init(); -} + const char* clientKeyName, + const char* samplingFeatureUUID) + : AWS_IoT_Publisher(baseLogger, nullptr, awsIoTEndpoint, caCertName, + clientCertName, clientKeyName, samplingFeatureUUID) {} +AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, + const char* awsIoTEndpoint, + const char* caCertName, + const char* clientCertName, + const char* clientKeyName) + : AWS_IoT_Publisher(baseLogger, nullptr, awsIoTEndpoint, caCertName, + clientCertName, clientKeyName, nullptr) {} AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, Client* inClient, const char* awsIoTEndpoint, - const char* samplingFeatureUUID, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setEndpoint(awsIoTEndpoint); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); + const char* samplingFeatureUUID) + : AWS_IoT_Publisher(baseLogger, inClient, awsIoTEndpoint, nullptr, nullptr, + nullptr, samplingFeatureUUID) {} +AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger, Client* inClient) + : AWS_IoT_Publisher(baseLogger, inClient, nullptr, nullptr, nullptr, + nullptr, nullptr) {} +AWS_IoT_Publisher::AWS_IoT_Publisher(Logger& baseLogger) + : AWS_IoT_Publisher(baseLogger, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr) {} +AWS_IoT_Publisher::AWS_IoT_Publisher() : dataPublisher() { init(); } + + void AWS_IoT_Publisher::init() { // Initialize the sub topics as null pointers for (uint8_t i = 0; i < MS_AWS_IOT_PUBLISHER_SUB_COUNT; i++) { @@ -84,9 +90,6 @@ void AWS_IoT_Publisher::init() { contentGetrFxns[i] = nullptr; } } -// Destructor -AWS_IoT_Publisher::~AWS_IoT_Publisher() {} - void AWS_IoT_Publisher::setEndpoint(const char* awsIoTEndpoint) { _awsIoTEndpoint = awsIoTEndpoint; @@ -144,7 +147,7 @@ void AWS_IoT_Publisher::removeSubTopic(const char* topic) { } void AWS_IoT_Publisher::addPublishRequest(const char* topic, - String (*contentGetrFxn)(void)) { + String (*contentGetrFxn)()) { for (uint8_t i = 0; i < MS_AWS_IOT_PUBLISHER_PUB_COUNT; i++) { if (pub_topics[i] == nullptr) { pub_topics[i] = topic; diff --git a/src/publishers/AWS_IoT_Publisher.h b/src/publishers/AWS_IoT_Publisher.h index 4f4a71b5e..8553663c6 100644 --- a/src/publishers/AWS_IoT_Publisher.h +++ b/src/publishers/AWS_IoT_Publisher.h @@ -87,23 +87,6 @@ class AWS_IoT_Publisher : public dataPublisher { public: // Constructors - /** - * @brief Construct a new AWS IoT Core Publisher object with no members - * initialized. - */ - AWS_IoT_Publisher(); - /** - * @brief Construct a new AWS IoT Core Publisher object - * - * @note If a client is never specified, the publisher will attempt to - * create and use a client on a LoggerModem instance tied to the attached - * logger. - * - * @param baseLogger The logger supplying the data to be published - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! - */ - explicit AWS_IoT_Publisher(Logger& baseLogger, int sendEveryX = 1); /** * @brief Construct a new AWS IoT Core Publisher object * @@ -111,10 +94,21 @@ class AWS_IoT_Publisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param awsIoTEndpoint The endpoint for your AWS IoT instance + * @param caCertName The name of your certificate authority certificate + * file + * @param clientCertName The name of your client certificate file + * @param clientKeyName The name of your client private key file + * @param samplingFeatureUUID The sampling feature UUID + * + * @note The inputs to this are the **NAMES** of the certificate **files** + * as they are stored on your modem module, not the content of the + * certificates. */ - AWS_IoT_Publisher(Logger& baseLogger, Client* inClient, int sendEveryX = 1); + AWS_IoT_Publisher(Logger& baseLogger, Client* inClient, + const char* awsIoTEndpoint, const char* caCertName, + const char* clientCertName, const char* clientKeyName, + const char* samplingFeatureUUID); /** * @brief Construct a new AWS IoT Core Publisher object * @@ -124,10 +118,7 @@ class AWS_IoT_Publisher : public dataPublisher { * file * @param clientCertName The name of your client certificate file * @param clientKeyName The name of your client private key file - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param samplingFeatureUUID The sampling feature UUID * * @note The inputs to this are the **NAMES** of the certificate **files** * as they are stored on you modem module, not the content of the @@ -136,7 +127,7 @@ class AWS_IoT_Publisher : public dataPublisher { AWS_IoT_Publisher(Logger& baseLogger, const char* awsIoTEndpoint, const char* caCertName, const char* clientCertName, const char* clientKeyName, - const char* samplingFeatureUUID, int sendEveryX = 1); + const char* samplingFeatureUUID); /** * @brief Construct a new AWS IoT Core Publisher object * @@ -146,8 +137,6 @@ class AWS_IoT_Publisher : public dataPublisher { * file * @param clientCertName The name of your client certificate file * @param clientKeyName The name of your client private key file - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! * * @note The inputs to this are the **NAMES** of the certificate **files** * as they are stored on you modem module, not the content of the @@ -155,7 +144,7 @@ class AWS_IoT_Publisher : public dataPublisher { */ AWS_IoT_Publisher(Logger& baseLogger, const char* awsIoTEndpoint, const char* caCertName, const char* clientCertName, - const char* clientKeyName, int sendEveryX = 1); + const char* clientKeyName); /** * @brief Construct a new AWS IoT Core Publisher object * @@ -164,22 +153,43 @@ class AWS_IoT_Publisher : public dataPublisher { * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance * @param awsIoTEndpoint The endpoint for your AWS IoT instance - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param samplingFeatureUUID The sampling feature UUID */ AWS_IoT_Publisher(Logger& baseLogger, Client* inClient, const char* awsIoTEndpoint, - const char* samplingFeatureUUID, int sendEveryX = 1); + const char* samplingFeatureUUID); + /** + * @brief Construct a new AWS IoT Core Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + */ + AWS_IoT_Publisher(Logger& baseLogger, Client* inClient); + /** + * @brief Construct a new AWS IoT Core Publisher object + * + * @note If a client is never specified, the publisher will attempt to + * create and use a client on a LoggerModem instance tied to the attached + * logger. + * + * @param baseLogger The logger supplying the data to be published + */ + explicit AWS_IoT_Publisher(Logger& baseLogger); + /** + * @brief Construct a new AWS IoT Core Publisher object with all members set + * to defaults or nulls. This cannot be used without setting up the AWS IoT + * connection parameters and client before use. + */ + AWS_IoT_Publisher(); /** * @brief Destroy the AWS IoT Core Publisher object */ - virtual ~AWS_IoT_Publisher(); + ~AWS_IoT_Publisher() override = default; - // Returns the data destination - String getEndpoint(void) override { - return String(_awsIoTEndpoint); + String getEndpoint() override { + return _awsIoTEndpoint ? String(_awsIoTEndpoint) : String(); } /** @@ -205,7 +215,7 @@ class AWS_IoT_Publisher : public dataPublisher { * Certificate Authority - G2). * * This is exactly the same CA certificate as you would use to upload to S3 - * (ie, the S3 Presigned Publisher). For supported modules you can use the + * (i.e., the S3 Presigned Publisher). For supported modules you can use the * AWS_IOT_SetCertificates sketch in the extras folder to upload your * certificate. * @@ -272,7 +282,7 @@ class AWS_IoT_Publisher : public dataPublisher { * * If not specified, the topic "{LoggerID}/metadata" will be used for the * main logger metadata. For each variable, the variable number will be - * appended to the topic (ie, "{LoggerID}/metadata/variable01"). + * appended to the topic (i.e., "{LoggerID}/metadata/variable01"). * * Make sure you have IAM policies set up to allow your device to publish to * the specified topics! @@ -345,7 +355,7 @@ class AWS_IoT_Publisher : public dataPublisher { * @param contentGetrFxn A function to call to get the content to publish. * The function should return a pointer to a char array. */ - void addPublishRequest(const char* topic, String (*contentGetrFxn)(void)); + void addPublishRequest(const char* topic, String (*contentGetrFxn)()); /** * @brief Removes a topic from the publish list. * @@ -389,8 +399,7 @@ class AWS_IoT_Publisher : public dataPublisher { /** * @copydoc dataPublisher::begin(Logger& baseLogger, Client* inClient) * @param awsIoTEndpoint The endpoint for your AWS IoT instance - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. + * @param samplingFeatureUUID The sampling feature UUID */ void begin(Logger& baseLogger, Client* inClient, const char* awsIoTEndpoint, const char* samplingFeatureUUID); @@ -407,8 +416,7 @@ class AWS_IoT_Publisher : public dataPublisher { * file * @param clientCertName The name of your client certificate file * @param clientKeyName The name of your client private key file - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. + * @param samplingFeatureUUID The sampling feature UUID */ void begin(Logger& baseLogger, const char* awsIoTEndpoint, const char* caCertName, const char* clientCertName, @@ -450,8 +458,8 @@ class AWS_IoT_Publisher : public dataPublisher { static const int mqttPort; static const char* samplingFeatureTag; ///< The JSON feature UUID tag static const char* timestampTag; ///< The JSON feature timestamp tag - virtual Client* createClient() override; - virtual void deleteClient(Client* client) override; + Client* createClient() override; + void deleteClient(Client* client) override; private: // Keys for AWS IoT Core @@ -493,7 +501,7 @@ class AWS_IoT_Publisher : public dataPublisher { /** * @brief An array of functions to call to get publish content */ - String (*contentGetrFxns[MS_AWS_IOT_PUBLISHER_PUB_COUNT])(void); + String (*contentGetrFxns[MS_AWS_IOT_PUBLISHER_PUB_COUNT])(); /// constructor helper void init(); }; diff --git a/src/publishers/DreamHostPublisher.cpp b/src/publishers/DreamHostPublisher.cpp index 9fe526fca..915d2aa6b 100644 --- a/src/publishers/DreamHostPublisher.cpp +++ b/src/publishers/DreamHostPublisher.cpp @@ -22,27 +22,22 @@ const char* DreamHostPublisher::loggerTag = "?LoggerID="; const char* DreamHostPublisher::timestampTagDH = "&Loggertime="; // Constructors -DreamHostPublisher::DreamHostPublisher() : dataPublisher() {} - -DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) {} - -DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) {} - -DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, const char* dhUrl, - int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setDreamHostPortalRX(dhUrl); -} +// Primary constructor with all parameters DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, Client* inClient, - const char* dhUrl, int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setDreamHostPortalRX(dhUrl); + const char* dhUrl) + : dataPublisher(baseLogger, inClient) { + if (dhUrl) setDreamHostPortalRX(dhUrl); } -// Destructor -DreamHostPublisher::~DreamHostPublisher() {} + +// Delegating constructors +DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, Client* inClient) + : DreamHostPublisher(baseLogger, inClient, nullptr) {} +DreamHostPublisher::DreamHostPublisher(Logger& baseLogger, const char* dhUrl) + : DreamHostPublisher(baseLogger, static_cast(nullptr), dhUrl) {} +DreamHostPublisher::DreamHostPublisher(Logger& baseLogger) + : DreamHostPublisher(baseLogger, static_cast(nullptr), + static_cast(nullptr)) {} +DreamHostPublisher::DreamHostPublisher() : dataPublisher() {} // Functions for private SWRC server @@ -64,8 +59,14 @@ void DreamHostPublisher::begin(Logger& baseLogger, const char* dhUrl) { // Post the data to dream host. -// int16_t DreamHostPublisher::postDataDreamHost(void) int16_t DreamHostPublisher::publishData(Client* outClient, bool) { + // Validate required DreamHost URL is set before proceeding + if (_DreamHostPortalRX == nullptr) { + MS_DBG(F("ERROR: DreamHost Portal RX URL not set. Call begin() or " + "setDreamHostPortalRX() first.")); + return -1; + } + // Create a buffer for the portions of the request and response char tempBuffer[37] = ""; uint16_t did_respond = 0; @@ -126,9 +127,9 @@ int16_t DreamHostPublisher::publishData(Client* outClient, bool) { did_respond = outClient->readBytes(tempBuffer, 12); // Process the HTTP response code // The first 9 characters should be "HTTP/1.1 " - if (did_respond > 0) { + if (did_respond >= 12) { char responseCode_char[4]; - memcpy(responseCode_char, tempBuffer + 9, 3); + memcpy(responseCode_char, tempBuffer + HTTP_VERSION_PREFIX_LEN, 3); // Null terminate the string memset(responseCode_char + 3, '\0', 1); responseCode = atoi(responseCode_char); diff --git a/src/publishers/DreamHostPublisher.h b/src/publishers/DreamHostPublisher.h index d1d0e313e..decd58691 100644 --- a/src/publishers/DreamHostPublisher.h +++ b/src/publishers/DreamHostPublisher.h @@ -47,22 +47,16 @@ class DreamHostPublisher : public dataPublisher { public: // Constructors - /** - * @brief Construct a new DreamHost Publisher object with no members set. - */ - DreamHostPublisher(); /** * @brief Construct a new DreamHost Publisher object * - * @note If a client is never specified, the publisher will attempt to - * create and use a client on a LoggerModem instance tied to the attached - * logger. - * * @param baseLogger The logger supplying the data to be published - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + * @param dhUrl The URL for sending data to DreamHost */ - explicit DreamHostPublisher(Logger& baseLogger, int sendEveryX = 1); + DreamHostPublisher(Logger& baseLogger, Client* inClient, const char* dhUrl); /** * @brief Construct a new DreamHost Publisher object * @@ -70,41 +64,40 @@ class DreamHostPublisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ - DreamHostPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX = 1); + DreamHostPublisher(Logger& baseLogger, Client* inClient); /** * @brief Construct a new DreamHost Publisher object * * @param baseLogger The logger supplying the data to be published * @param dhUrl The URL for sending data to DreamHost - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ - DreamHostPublisher(Logger& baseLogger, const char* dhUrl, - int sendEveryX = 1); + DreamHostPublisher(Logger& baseLogger, const char* dhUrl); /** * @brief Construct a new DreamHost Publisher object * + * @note If a client is never specified, the publisher will attempt to + * create and use a client on a LoggerModem instance tied to the attached + * logger. + * * @param baseLogger The logger supplying the data to be published - * @param inClient An Arduino client instance to use to print data to. - * Allows the use of any type of client and multiple clients tied to a - * single TinyGSM modem instance - * @param dhUrl The URL for sending data to DreamHost - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ - DreamHostPublisher(Logger& baseLogger, Client* inClient, const char* dhUrl, - int sendEveryX = 1); + explicit DreamHostPublisher(Logger& baseLogger); + /** + * @brief Construct a new DreamHost Publisher object with all members set to + * default or null. + * + * @note You must call the begin() function to initialize the members before + * using the publisher. + */ + DreamHostPublisher(); /** * @brief Destroy the DreamHost Publisher object */ - virtual ~DreamHostPublisher(); + ~DreamHostPublisher() override = default; // Returns the data destination - String getEndpoint(void) override { + String getEndpoint() override { return String(dreamhostHost); } diff --git a/src/publishers/EnviroDIYPublisher.h b/src/publishers/EnviroDIYPublisher.h index 462e8a378..84d457418 100644 --- a/src/publishers/EnviroDIYPublisher.h +++ b/src/publishers/EnviroDIYPublisher.h @@ -4,309 +4,32 @@ * Part of the EnviroDIY ModularSensors library for Arduino. * This library is published under the BSD-3 license. * @author Sara Geleskie Damiano - * @author Thomas Watson * - * @brief Contains the EnviroDIYPublisher subclass of dataPublisher for - * publishing data to the Monitor My Watershed/EnviroDIY data portal at - * http://data.enviroDIY.org + * @brief Contains the EnviroDIYPublisher typedef, which provides backward + * compatibility by aliasing to MonitorMyWatershedPublisher (a subclass of + * dataPublisher). */ // Header Guards #ifndef SRC_PUBLISHERS_ENVIRODIYPUBLISHER_H_ #define SRC_PUBLISHERS_ENVIRODIYPUBLISHER_H_ -// Include the library config before anything else -#include "ModSensorConfig.h" - -// Include the debugging config -#include "ModSensorDebugConfig.h" - -// Define the print label[s] for the debugger -#ifdef MS_ENVIRODIYPUBLISHER_DEBUG -#define MS_DEBUGGING_STD "EnviroDIYPublisher" -#endif - -// Include the debugger -#include "ModSensorDebugger.h" -// Undefine the debugger label[s] -#undef MS_DEBUGGING_STD - -// Include other in-library and external dependencies -#include "dataPublisherBase.h" -#include "LogBuffer.h" +// Include the Monitor My Watershed Publisher since the EnviroDIYPublisher is +// only a typedef reference to it for backward compatibility +#include "MonitorMyWatershedPublisher.h" // ============================================================================ -// Functions for the EnviroDIY data portal receivers. +// Typedef for backward compatibility with EnviroDIY data portal receivers. // ============================================================================ /** - * @brief The EnviroDIYPublisher subclass of dataPublisher for publishing data - * to the Monitor My Watershed/EnviroDIY data portal at - * https://monitormywatershed.org/ (formerly at http://data.enviroDIY.org). + * @brief typedef for backwards compatibility; use the + * MonitorMyWatershedPublisher class in new code * * @ingroup the_publishers + * + * @m_deprecated_since{0,38,0} */ -class EnviroDIYPublisher : public dataPublisher { - public: - // Constructors - /** - * @brief Construct a new EnviroDIY Publisher object with no members set. - */ - EnviroDIYPublisher(); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @note If a client is never specified, the publisher will attempt to - * create and use a client on a LoggerModem instance tied to the attached - * logger. - * - * @param baseLogger The logger supplying the data to be published - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - explicit EnviroDIYPublisher(Logger& baseLogger, int sendEveryX = 1); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @param baseLogger The logger supplying the data to be published - * @param inClient An Arduino client instance to use to print data to. - * Allows the use of any type of client and multiple clients tied to a - * single TinyGSM modem instance - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX = 1); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @param baseLogger The logger supplying the data to be published - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - EnviroDIYPublisher(Logger& baseLogger, const char* registrationToken, - const char* samplingFeatureUUID, int sendEveryX = 1); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @param baseLogger The logger supplying the data to be published - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - EnviroDIYPublisher(Logger& baseLogger, const char* registrationToken, - int sendEveryX = 1); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @param baseLogger The logger supplying the data to be published - * @param inClient An Arduino client instance to use to print data to. - * Allows the use of any type of client and multiple clients tied to a - * single TinyGSM modem instance - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - const char* registrationToken, - const char* samplingFeatureUUID, int sendEveryX = 1); - /** - * @brief Construct a new EnviroDIY Publisher object - * - * @param baseLogger The logger supplying the data to be published - * @param inClient An Arduino client instance to use to print data to. - * Allows the use of any type of client and multiple clients tied to a - * single TinyGSM modem instance - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. - */ - EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - const char* registrationToken, int sendEveryX = 1); - /** - * @brief Destroy the EnviroDIY Publisher object - */ - virtual ~EnviroDIYPublisher(); - - // Returns the data destination - String getEndpoint(void) override { - return String(enviroDIYHost) + String(enviroDIYPath); - } - - /** - * @brief Get the EnviroDIY/Monitor My Watershed web host - * - * @return The EnviroDIY/Monitor My Watershed web host - */ - String getHost(void); - - /** - * @brief Set the EnviroDIY/Monitor My Watershed web host - * - * @param host The EnviroDIY/Monitor My Watershed web host - */ - void setHost(const char* host); - - /** - * @brief Get the EnviroDIY/Monitor My Watershed API path - * - * @return The EnviroDIY/Monitor My Watershed API path - */ - String getPath(void); - /** - * @brief Set the EnviroDIY/Monitor My Watershed API path - * - * @param endpoint The EnviroDIY/Monitor My Watershed API path - */ - void setPath(const char* endpoint); - - /** - * @brief Get the EnviroDIY/Monitor My Watershed API port - * - * @return The EnviroDIY/Monitor My Watershed API port - */ - int getPort(void); - /** - * @brief Set the EnviroDIY/Monitor My Watershed API port - * - * @param port The EnviroDIY/Monitor My Watershed API port - */ - void setPort(int port); - - // Adds the site registration token - /** - * @brief Set the site registration token - * - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - */ - void setToken(const char* registrationToken); - - /** - * @brief Calculates how long the outgoing JSON will be - * - * @return The number of characters in the JSON object. - */ - uint16_t calculateJsonSize(); - - - /** - * @copydoc dataPublisher::begin(Logger& baseLogger, Client* inClient) - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - */ - void begin(Logger& baseLogger, Client* inClient, - const char* registrationToken, const char* samplingFeatureUUID); - /** - * @copydoc dataPublisher::begin(Logger& baseLogger) - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - * @param samplingFeatureUUID The sampling feature UUID for the site on the - * Monitor My Watershed data portal. - */ - void begin(Logger& baseLogger, const char* registrationToken, - const char* samplingFeatureUUID); - /** - * @copydoc dataPublisher::begin(Logger& baseLogger) - * @param registrationToken The registration token for the site on the - * Monitor My Watershed data portal. - */ - void begin(Logger& baseLogger, const char* registrationToken); - - /** - * @brief Checks if the publisher needs an Internet connection for the next - * publishData call (as opposed to just buffering data internally). - * - * @return True if an internet connection is needed for the next publish. - */ - bool connectionNeeded(void) override; - - /** - * @brief Utilize an attached modem to open a a TCP connection to the - * EnviroDIY/ODM2DataSharingPortal and then stream out a post request over - * that connection. - * - * This depends on an internet connection already having been made and a - * client being available. - * - * @param outClient An Arduino client instance to use to print data to. - * Allows the use of any type of client and multiple clients tied to a - * single TinyGSM modem instance - * @param forceFlush Ask the publisher to flush buffered data immediately. - * @return The http status code of the response. - */ - int16_t publishData(Client* outClient, - bool forceFlush = MS_ALWAYS_FLUSH_PUBLISHERS) override; - - protected: - /** - * @anchor envirodiy_post_vars - * @name Portions of the POST request to EnviroDIY - * - * @{ - */ - const char* enviroDIYPath; ///< The api path - const char* enviroDIYHost; ///< The host name - int enviroDIYPort; ///< The host port - static const char* tokenHeader; ///< The token header text - static const char* contentLengthHeader; ///< The content length header text - static const char* contentTypeHeader; ///< The content type header text - /**@}*/ - - /** - * @anchor envirodiy_json_vars - * @name Portions of the JSON object for EnviroDIY - * - * @{ - */ - static const char* samplingFeatureTag; ///< The JSON feature UUID tag - static const char* timestampTag; ///< The JSON feature timestamp tag - - /**@}*/ - - - LogBuffer _logBuffer; ///< Internal reference to the logger buffer - - // actually transmit rather than just buffer data - /** - * @brief Transmit data from the data buffer to an external site - * - * @param outClient The client to publish the data over - * @return The HTTP response code from the publish attempt - * - * @note A 504 will be returned automatically if the server does not - * respond within 30 seconds. - */ - int16_t flushDataBuffer(Client* outClient); - - /** - * @brief The number of transmissions remaining at the single minute - * intervals - * - * We send every one of the first five data points at only one minute - * intervals for faster in-field validation. - * - * @todo This should be a user-settable parameter. - */ - uint8_t _initialTransmissionsRemaining = 5; - - private: - /** - * @brief Internal reference to the EnviroDIY/Monitor My Watershed - * registration token. - */ - const char* _registrationToken = nullptr; -}; +typedef MonitorMyWatershedPublisher EnviroDIYPublisher; #endif // SRC_PUBLISHERS_ENVIRODIYPUBLISHER_H_ diff --git a/src/publishers/EnviroDIYPublisher.cpp b/src/publishers/MonitorMyWatershedPublisher.cpp similarity index 58% rename from src/publishers/EnviroDIYPublisher.cpp rename to src/publishers/MonitorMyWatershedPublisher.cpp index a5895eae6..27770b7b4 100644 --- a/src/publishers/EnviroDIYPublisher.cpp +++ b/src/publishers/MonitorMyWatershedPublisher.cpp @@ -1,140 +1,127 @@ /** - * @file EnviroDIYPublisher.cpp + * @file MonitorMyWatershedPublisher.cpp * @copyright Stroud Water Research Center * Part of the EnviroDIY ModularSensors library for Arduino. * This library is published under the BSD-3 license. * @author Sara Geleskie Damiano * @author Thomas Watson * - * @brief Implements the EnviroDIYPublisher class. + * @brief Implements the MonitorMyWatershedPublisher class. */ -#include "EnviroDIYPublisher.h" +#include "MonitorMyWatershedPublisher.h" // ============================================================================ -// Functions for the EnviroDIY data portal receivers. +// Functions for Monitor My Watershed // ============================================================================ // Constant values for post requests // I want to refer to these more than once while ensuring there is only one copy // in memory -const char* EnviroDIYPublisher::tokenHeader = "\r\nTOKEN: "; -const char* EnviroDIYPublisher::contentLengthHeader = "\r\nContent-Length: "; -const char* EnviroDIYPublisher::contentTypeHeader = +const char* MonitorMyWatershedPublisher::tokenHeader = "\r\nTOKEN: "; +const char* MonitorMyWatershedPublisher::contentLengthHeader = + "\r\nContent-Length: "; +const char* MonitorMyWatershedPublisher::contentTypeHeader = "\r\nContent-Type: application/json\r\n\r\n"; -const char* EnviroDIYPublisher::samplingFeatureTag = "{\"sampling_feature\":\""; -const char* EnviroDIYPublisher::timestampTag = "\",\"timestamp\":"; +const char* MonitorMyWatershedPublisher::samplingFeatureTag = + "{\"sampling_feature\":\""; +const char* MonitorMyWatershedPublisher::timestampTag = "\",\"timestamp\":"; // Constructors -EnviroDIYPublisher::EnviroDIYPublisher() : dataPublisher() { - setHost("monitormywatershed.org"); - setPath("/api/data-stream/"); - setPort(80); -} -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { +// Primary constructor with all parameters +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, Client* inClient, const char* registrationToken, + const char* samplingFeatureUUID, int sendEveryX, + uint8_t startupTransmissions) + : dataPublisher(baseLogger, inClient, sendEveryX, startupTransmissions) { _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); setHost("monitormywatershed.org"); setPath("/api/data-stream/"); setPort(80); + if (registrationToken) setToken(registrationToken); + if (samplingFeatureUUID) + _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); } -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); - setHost("monitormywatershed.org"); - setPath("/api/data-stream/"); - setPort(80); -} -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, - const char* registrationToken, - const char* samplingFeatureUUID, - int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setToken(registrationToken); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); - setHost("monitormywatershed.org"); - setPath("/api/data-stream/"); - setPort(80); -} -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, - const char* registrationToken, - int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setToken(registrationToken); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); - setHost("monitormywatershed.org"); - setPath("/api/data-stream/"); - setPort(80); -} -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - const char* registrationToken, - const char* samplingFeatureUUID, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setToken(registrationToken); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); - setHost("monitormywatershed.org"); - setPath("/api/data-stream/"); - setPort(80); -} -EnviroDIYPublisher::EnviroDIYPublisher(Logger& baseLogger, Client* inClient, - const char* registrationToken, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setToken(registrationToken); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); + +// Delegating constructors +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, const char* registrationToken, + const char* samplingFeatureUUID, int sendEveryX, + uint8_t startupTransmissions) + : MonitorMyWatershedPublisher(baseLogger, static_cast(nullptr), + registrationToken, samplingFeatureUUID, + sendEveryX, startupTransmissions) {} +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, Client* inClient, const char* registrationToken, + int sendEveryX, uint8_t startupTransmissions) + : MonitorMyWatershedPublisher(baseLogger, inClient, registrationToken, + nullptr, sendEveryX, startupTransmissions) {} +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, Client* inClient, int sendEveryX, + uint8_t startupTransmissions) + : MonitorMyWatershedPublisher(baseLogger, inClient, nullptr, nullptr, + sendEveryX, startupTransmissions) {} +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, const char* registrationToken, int sendEveryX, + uint8_t startupTransmissions) + : MonitorMyWatershedPublisher(baseLogger, static_cast(nullptr), + registrationToken, nullptr, sendEveryX, + startupTransmissions) {} +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher( + Logger& baseLogger, int sendEveryX, uint8_t startupTransmissions) + : MonitorMyWatershedPublisher(baseLogger, static_cast(nullptr), + nullptr, nullptr, sendEveryX, + startupTransmissions) {} +MonitorMyWatershedPublisher::MonitorMyWatershedPublisher() : dataPublisher() { + // NOTE: _logBuffer is not initialized here because _baseLogger is null + // Must call begin(Logger&, ...) before use to properly initialize + // _logBuffer setHost("monitormywatershed.org"); setPath("/api/data-stream/"); setPort(80); } -// Destructor -EnviroDIYPublisher::~EnviroDIYPublisher() {} - // Returns the data destination -String EnviroDIYPublisher::getHost(void) { - return String(enviroDIYHost); +String MonitorMyWatershedPublisher::getHost() { + return String(monitorMWHost); } -// Returns the data destination -void EnviroDIYPublisher::setHost(const char* host) { - enviroDIYHost = host; +// Sets the data destination host +void MonitorMyWatershedPublisher::setHost(const char* host) { + monitorMWHost = host; } // Returns the data destination -String EnviroDIYPublisher::getPath(void) { - return String(enviroDIYPath); +String MonitorMyWatershedPublisher::getPath() { + return String(monitorMWPath); } -// Returns the data destination -void EnviroDIYPublisher::setPath(const char* endpoint) { - enviroDIYPath = endpoint; +// Sets the data destination path/endpoint +void MonitorMyWatershedPublisher::setPath(const char* endpoint) { + monitorMWPath = endpoint; } // Returns the data destination -int EnviroDIYPublisher::getPort(void) { - return enviroDIYPort; +int MonitorMyWatershedPublisher::getPort() { + return monitorMWPort; } -// Returns the data destination -void EnviroDIYPublisher::setPort(int port) { - enviroDIYPort = port; +// Sets the data destination port +void MonitorMyWatershedPublisher::setPort(int port) { + monitorMWPort = port; } -void EnviroDIYPublisher::setToken(const char* registrationToken) { +void MonitorMyWatershedPublisher::setToken(const char* registrationToken) { _registrationToken = registrationToken; } // Calculates how long the JSON will be -uint16_t EnviroDIYPublisher::calculateJsonSize() { +uint16_t MonitorMyWatershedPublisher::calculateJsonSize() { uint8_t variables = _logBuffer.getNumVariables(); int records = _logBuffer.getNumRecords(); MS_DBG(F("Number of records in log buffer:"), records); @@ -142,6 +129,12 @@ uint16_t EnviroDIYPublisher::calculateJsonSize() { MS_DBG(F("Number of variables in base logger:"), _baseLogger->getArrayVarCount()); + // Guard against underflow when records == 0 + if (records == 0) { + MS_DBG(F("No records to send, returning minimal JSON size")); + return 50; // Minimal size for empty JSON structure + } + uint16_t jsonLength = strlen(samplingFeatureTag); jsonLength += 36; // sampling feature UUID jsonLength += strlen(timestampTag); @@ -181,41 +174,40 @@ uint16_t EnviroDIYPublisher::calculateJsonSize() { // A way to set members in the begin to use with a bare constructor -void EnviroDIYPublisher::begin(Logger& baseLogger, Client* inClient, - const char* registrationToken, - const char* samplingFeatureUUID) { +void MonitorMyWatershedPublisher::begin(Logger& baseLogger, Client* inClient, + const char* registrationToken, + const char* samplingFeatureUUID) { setToken(registrationToken); dataPublisher::begin(baseLogger, inClient); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); + if (samplingFeatureUUID != nullptr) { + _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); + } _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); } -void EnviroDIYPublisher::begin(Logger& baseLogger, - const char* registrationToken, - const char* samplingFeatureUUID) { - setToken(registrationToken); - dataPublisher::begin(baseLogger); - _baseLogger->setSamplingFeatureUUID(samplingFeatureUUID); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); +void MonitorMyWatershedPublisher::begin(Logger& baseLogger, + const char* registrationToken, + const char* samplingFeatureUUID) { + begin(baseLogger, static_cast(nullptr), registrationToken, + samplingFeatureUUID); } -void EnviroDIYPublisher::begin(Logger& baseLogger, - const char* registrationToken) { - setToken(registrationToken); - dataPublisher::begin(baseLogger); - _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); +void MonitorMyWatershedPublisher::begin(Logger& baseLogger, + const char* registrationToken) { + begin(baseLogger, static_cast(nullptr), registrationToken, + nullptr); } -bool EnviroDIYPublisher::connectionNeeded(void) { +bool MonitorMyWatershedPublisher::connectionNeeded() { // compute the send interval, reducing it as the buffer gets more full so we // have less of a chance of losing data int interval = _sendEveryX; uint8_t percent = _logBuffer.getPercentFull(); MS_DBG(F("Buffer is"), percent, F("percent full")); - if (percent >= 50) { - interval /= 2; - } else if (percent >= 75) { - interval /= 4; - } else if (percent >= 90) { + if (percent >= 90) { interval = 1; + } else if (percent >= 75) { + interval = max(1, interval / 4); + } else if (percent >= 50) { + interval = max(1, interval / 2); } // the programmed interval is about to be reached by the next record, or it @@ -243,16 +235,16 @@ bool EnviroDIYPublisher::connectionNeeded(void) { // the initial log transmissions have not completed (we send every one of // the first five data points immediately for field validation) - bool initialTransmission = _initialTransmissionsRemaining > 0; + bool initialTransmission = _startupTransmissions > 0; return atSendInterval || initialTransmission; } -// This utilizes an attached modem to make a TCP connection to the -// EnviroDIY/ODM2DataSharingPortal and then streams out a post request over that -// connection. -// The return is the http status code of the response. -int16_t EnviroDIYPublisher::publishData(Client* outClient, bool forceFlush) { +// This utilizes an attached modem to make a TCP connection to Monitor My +// Watershed and then streams out a post request over that connection. The +// return is the http status code of the response. +int16_t MonitorMyWatershedPublisher::publishData(Client* outClient, + bool forceFlush) { // work around for strange construction order: make sure the number of // variables listed in the log buffer matches the number of variables in the // logger @@ -261,8 +253,11 @@ int16_t EnviroDIYPublisher::publishData(Client* outClient, bool forceFlush) { "variables in logger:"), _logBuffer.getNumVariables(), F("vs"), _baseLogger->getArrayVarCount()); - MS_DBG(F("Setting number of variables in log buffer to match number of " - "variables in logger. This will erase the buffer.")); + PRINTOUT( + F("Setting number of variables in log buffer to match number of " + "variables in logger.")); + PRINTOUT(F("THIS WILL ERASE THE BUFFER AND DELETE"), + _logBuffer.getNumRecords(), F("UNSENT RECORDS!")); _logBuffer.setNumVariables(_baseLogger->getArrayVarCount()); } @@ -286,8 +281,8 @@ int16_t EnviroDIYPublisher::publishData(Client* outClient, bool forceFlush) { } } - if (_initialTransmissionsRemaining > 0) { - _initialTransmissionsRemaining -= 1; + if (record >= 0 && _startupTransmissions > 0) { + _startupTransmissions -= 1; } // do the data buffer flushing if we previously planned to @@ -301,33 +296,51 @@ int16_t EnviroDIYPublisher::publishData(Client* outClient, bool forceFlush) { } } -int16_t EnviroDIYPublisher::flushDataBuffer(Client* outClient) { +int16_t MonitorMyWatershedPublisher::flushDataBuffer(Client* outClient) { // Create a buffer for the portions of the request and response - char tempBuffer[37] = ""; - uint16_t did_respond = 0; - int16_t responseCode = 0; + char tempBuffer[37] = ""; + int16_t did_respond = 0; + int16_t responseCode = 0; + + // Early return if no records to send + if (_logBuffer.getNumRecords() == 0) { + MS_DBG(F("No records to send, returning without action")); + return -1; + } if (_baseLogger->getSamplingFeatureUUID() == nullptr || strlen(_baseLogger->getSamplingFeatureUUID()) == 0) { PRINTOUT(F("A sampling feature UUID must be set before publishing data " "to Monitor My Watershed!.")); - return 0; + return -2; + } + if (_registrationToken == nullptr || strlen(_registrationToken) == 0) { + PRINTOUT(F("A registration token must be set before publishing data " + "to Monitor My Watershed!.")); + return -3; } - // Open a TCP/IP connection to the EnviroDIY Data Portal (WebSDL) + // Check for valid client before attempting connection + if (outClient == nullptr) { + PRINTOUT(F("No client available for publishing data to Monitor My " + "Watershed!")); + return -4; + } + + // Open a TCP/IP connection to Monitor My Watershed MS_DBG(F("Connecting client")); MS_START_DEBUG_TIMER; - if (outClient->connect(enviroDIYHost, enviroDIYPort)) { + if (outClient->connect(monitorMWHost, monitorMWPort)) { MS_DBG(F("Client connected after"), MS_PRINT_DEBUG_TIMER, F("ms")); txBufferInit(outClient); // copy the initial post header into the tx buffer txBufferAppend(postHeader); - txBufferAppend(enviroDIYPath); + txBufferAppend(monitorMWPath); txBufferAppend(HTTPtag); // add the rest of the HTTP POST headers to the outgoing buffer txBufferAppend(hostHeader); - txBufferAppend(enviroDIYHost); + txBufferAppend(monitorMWHost); txBufferAppend(tokenHeader); txBufferAppend(_registrationToken); @@ -385,8 +398,8 @@ int16_t EnviroDIYPublisher::flushDataBuffer(Client* outClient) { // Wait 30 seconds for a response from the server uint32_t start = millis(); - while ((millis() - start) < 30000L && outClient->connected() && - outClient->available() < 12) { + while ((millis() - start) < MMW_RESPONSE_TIMEOUT_MS && + outClient->connected() && outClient->available() < 12) { delay(10); } @@ -396,12 +409,12 @@ int16_t EnviroDIYPublisher::flushDataBuffer(Client* outClient) { did_respond = outClient->readBytes(tempBuffer, 12); // Process the HTTP response code // The first 9 characters should be "HTTP/1.1 " - if (did_respond > 0) { + if (did_respond >= 12) { char responseCode_char[4]; - memcpy(responseCode_char, tempBuffer + 9, 3); + memcpy(responseCode_char, tempBuffer + HTTP_VERSION_PREFIX_LEN, 3); // Null terminate the string - memset(responseCode_char + 3, '\0', 1); - responseCode = atoi(responseCode_char); + responseCode_char[3] = '\0'; + responseCode = atoi(responseCode_char); PRINTOUT(F("\n-- Response Code --")); PRINTOUT(responseCode); } else { @@ -426,10 +439,10 @@ int16_t EnviroDIYPublisher::flushDataBuffer(Client* outClient) { outClient->stop(); MS_DBG(F("Client stopped after"), MS_PRINT_DEBUG_TIMER, F("ms")); } else { - PRINTOUT(F("\n -- Unable to Establish Connection to EnviroDIY Data " - "Portal --")); + PRINTOUT(F( + "\n -- Unable to Establish Connection to Monitor My Watershed --")); + responseCode = -5; // Connection failure } - if (responseCode == 201) { // data was successfully transmitted, we can discard it from the buffer _logBuffer.clear(); diff --git a/src/publishers/MonitorMyWatershedPublisher.h b/src/publishers/MonitorMyWatershedPublisher.h new file mode 100644 index 000000000..b761803d9 --- /dev/null +++ b/src/publishers/MonitorMyWatershedPublisher.h @@ -0,0 +1,348 @@ +/** + * @file MonitorMyWatershedPublisher.h + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * @author Thomas Watson + * + * @brief Contains the MonitorMyWatershedPublisher subclass of dataPublisher for + * publishing data to Monitor My Watershed at https://monitormywatershed.org/ + */ + +// Header Guards +#ifndef SRC_PUBLISHERS_MONITORMYWATERSHEDPUBLISHER_H_ +#define SRC_PUBLISHERS_MONITORMYWATERSHEDPUBLISHER_H_ + +// Include the library config before anything else +#include "ModSensorConfig.h" + +// Include the debugging config +#include "ModSensorDebugConfig.h" + +// Define the print label[s] for the debugger +#ifdef MS_MONITORMYWATERSHEDPUBLISHER_DEBUG +#define MS_DEBUGGING_STD "MonitorMyWatershedPublisher" +#endif + +// Include the debugger +#include "ModSensorDebugger.h" +// Undefine the debugger label[s] +#undef MS_DEBUGGING_STD + +// Include other in-library and external dependencies +#include "dataPublisherBase.h" +#include "LogBuffer.h" + +/// Timeout for server response in milliseconds +#define MMW_RESPONSE_TIMEOUT_MS 30000L + + +// ============================================================================ +// Functions for Monitor My Watershed +// ============================================================================ +/** + * @brief The MonitorMyWatershedPublisher subclass of dataPublisher for + * publishing data to Monitor My Watershed at https://monitormywatershed.org/ + * (formerly at http://data.enviroDIY.org). + * + * @ingroup the_publishers + */ +class MonitorMyWatershedPublisher : public dataPublisher { + public: + // Constructors + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param samplingFeatureUUID The sampling feature UUID for the site on + * Monitor My Watershed. + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + MonitorMyWatershedPublisher(Logger& baseLogger, Client* inClient, + const char* registrationToken, + const char* samplingFeatureUUID, + int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param samplingFeatureUUID The sampling feature UUID for the site on + * Monitor My Watershed. + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + MonitorMyWatershedPublisher(Logger& baseLogger, + const char* registrationToken, + const char* samplingFeatureUUID, + int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + MonitorMyWatershedPublisher(Logger& baseLogger, Client* inClient, + const char* registrationToken, + int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + MonitorMyWatershedPublisher(Logger& baseLogger, Client* inClient, + int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @param baseLogger The logger supplying the data to be published + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + MonitorMyWatershedPublisher(Logger& baseLogger, + const char* registrationToken, + int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object + * + * @note If a client is never specified, the publisher will attempt to + * create and use a client on a LoggerModem instance tied to the attached + * logger. + * + * @param baseLogger The logger supplying the data to be published + * @param sendEveryX Interval (in units of the logging interval) between + * attempted data transmissions. + * @param startupTransmissions Number of transmissions to send + * immediately after each data point is logged, before beginning to cache + * data and only transmit every sendEveryX times the logger records data + * (default: 5). This allows faster in-field validation of startup data. + */ + explicit MonitorMyWatershedPublisher(Logger& baseLogger, int sendEveryX = 1, + uint8_t startupTransmissions = 5); + /** + * @brief Construct a new Monitor My Watershed Publisher object with only + * default values for all parameters. + */ + MonitorMyWatershedPublisher(); + /** + * @brief Destroy the Monitor My Watershed Publisher object + */ + ~MonitorMyWatershedPublisher() override = default; + + // Returns the data destination + String getEndpoint() override { + return String(monitorMWHost ? monitorMWHost : "") + + String(monitorMWPath ? monitorMWPath : ""); + } + + /** + * @brief Get the Monitor My Watershed web host + * + * @return The Monitor My Watershed web host + */ + String getHost(); + + /** + * @brief Set the Monitor My Watershed web host + * + * @warning The caller must guarantee that the passed C-string remains + * valid for the lifetime of this object or until another call to setHost(). + * Do not pass temporary strings or stack-allocated C-strings that may + * be destroyed before all publish calls are complete. + * + * @param host The Monitor My Watershed web host + */ + void setHost(const char* host); + + /** + * @brief Get the Monitor My Watershed API path + * + * @return The Monitor My Watershed API path + */ + String getPath(); + /** + * @brief Set the Monitor My Watershed API path + * + * @warning The caller must guarantee that the passed C-string remains + * valid for the lifetime of this object or until another call to setPath(). + * Do not pass temporary strings or stack-allocated C-strings that may + * be destroyed before all publish calls are complete. + * + * @param endpoint The Monitor My Watershed API path + */ + void setPath(const char* endpoint); + + /** + * @brief Get the Monitor My Watershed API port + * + * @return The Monitor My Watershed API port + */ + int getPort(); + /** + * @brief Set the Monitor My Watershed API port + * + * @param port The Monitor My Watershed API port + */ + void setPort(int port); + + // Adds the site registration token + /** + * @brief Set the site registration token + * + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + */ + void setToken(const char* registrationToken); + + /** + * @brief Calculates how long the outgoing JSON will be + * + * @return The number of characters in the JSON object. + */ + uint16_t calculateJsonSize(); + + + /** + * @copydoc dataPublisher::begin(Logger& baseLogger, Client* inClient) + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param samplingFeatureUUID The sampling feature UUID for the site on + * Monitor My Watershed. + */ + void begin(Logger& baseLogger, Client* inClient, + const char* registrationToken, const char* samplingFeatureUUID); + /** + * @copydoc dataPublisher::begin(Logger& baseLogger) + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + * @param samplingFeatureUUID The sampling feature UUID for the site on + * Monitor My Watershed. + */ + void begin(Logger& baseLogger, const char* registrationToken, + const char* samplingFeatureUUID); + /** + * @copydoc dataPublisher::begin(Logger& baseLogger) + * @param registrationToken The registration token for the site on Monitor + * My Watershed. + */ + void begin(Logger& baseLogger, const char* registrationToken); + + /** + * @brief Checks if the publisher needs an Internet connection for the next + * publishData call (as opposed to just buffering data internally). + * + * @return True if an internet connection is needed for the next publish. + */ + bool connectionNeeded() override; + + /** + * @brief Utilize an attached modem to open a TCP connection to Monitor My + * Watershed and then stream out a post request over that connection. + * + * This depends on an internet connection already having been made and a + * client being available. + * + * @param outClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance + * @param forceFlush Ask the publisher to flush buffered data immediately. + * @return The http status code of the response. + */ + int16_t publishData(Client* outClient, + bool forceFlush = MS_ALWAYS_FLUSH_PUBLISHERS) override; + + protected: + /** + * @anchor monitormw_post_vars + * @name Portions of the POST request to Monitor My Watershed + * + * @{ + */ + const char* monitorMWPath = nullptr; ///< The api path + const char* monitorMWHost = nullptr; ///< The host name + int monitorMWPort = 80; ///< The host port + static const char* tokenHeader; ///< The token header text + static const char* contentLengthHeader; ///< The content length header text + static const char* contentTypeHeader; ///< The content type header text + /**@}*/ + + /** + * @anchor monitormw_json_vars + * @name Portions of the JSON object for Monitor My Watershed + * + * @{ + */ + static const char* samplingFeatureTag; ///< The JSON feature UUID tag + static const char* timestampTag; ///< The JSON feature timestamp tag + + /**@}*/ + + + LogBuffer _logBuffer; ///< Internal reference to the logger buffer + + // actually transmit rather than just buffer data + /** + * @brief Transmit data from the data buffer to an external site + * + * @param outClient The client to publish the data over + * @return The HTTP response code from the publish attempt + * + * @note A 504 will be returned automatically if the server does not + * respond within 30 seconds. + */ + int16_t flushDataBuffer(Client* outClient); + + private: + /** + * @brief Internal reference to the Monitor My Watershed registration token. + */ + const char* _registrationToken = nullptr; +}; + +#endif // SRC_PUBLISHERS_MONITORMYWATERSHEDPUBLISHER_H_ diff --git a/src/publishers/S3PresignedPublisher.cpp b/src/publishers/S3PresignedPublisher.cpp index 060c66fb4..13dd98b81 100644 --- a/src/publishers/S3PresignedPublisher.cpp +++ b/src/publishers/S3PresignedPublisher.cpp @@ -18,27 +18,30 @@ const char* S3PresignedPublisher::contentLengthHeader = "\r\nContent-Length: "; const char* S3PresignedPublisher::contentTypeHeader = "\r\nContent-Type: "; // Constructors -S3PresignedPublisher::S3PresignedPublisher() : dataPublisher() {} -S3PresignedPublisher::S3PresignedPublisher(Logger& baseLogger, +// Primary constructor with all parameters +S3PresignedPublisher::S3PresignedPublisher(Logger& baseLogger, Client* inClient, const char* caCertName, String (*getUrlFxn)(String), - String (*getFileNameFxn)(void), - int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setCACertName(caCertName); - setURLUpdateFunction(getUrlFxn); - setFileUpdateFunction(getFileNameFxn); + String (*getFileNameFxn)()) + : dataPublisher(baseLogger, inClient) { + if (caCertName) setCACertName(caCertName); + if (getUrlFxn) setURLUpdateFunction(getUrlFxn); + if (getFileNameFxn) setFileUpdateFunction(getFileNameFxn); } + +// Delegating constructors +S3PresignedPublisher::S3PresignedPublisher(Logger& baseLogger, + const char* caCertName, + String (*getUrlFxn)(String), + String (*getFileNameFxn)()) + : S3PresignedPublisher(baseLogger, nullptr, caCertName, getUrlFxn, + getFileNameFxn) {} S3PresignedPublisher::S3PresignedPublisher(Logger& baseLogger, Client* inClient, String (*getUrlFxn)(String), - String (*getFileNameFxn)(void), - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setURLUpdateFunction(getUrlFxn); - setFileUpdateFunction(getFileNameFxn); -} -// Destructor -S3PresignedPublisher::~S3PresignedPublisher() {} + String (*getFileNameFxn)()) + : S3PresignedPublisher(baseLogger, inClient, nullptr, getUrlFxn, + getFileNameFxn) {} +S3PresignedPublisher::S3PresignedPublisher() : dataPublisher() {} void S3PresignedPublisher::setPort(int port) { @@ -51,8 +54,7 @@ void S3PresignedPublisher::setHost(const char* host) { void S3PresignedPublisher::setURLUpdateFunction(String (*getUrlFxn)(String)) { _getUrlFxn = getUrlFxn; } -void S3PresignedPublisher::setFileUpdateFunction( - String (*getFileNameFxn)(void)) { +void S3PresignedPublisher::setFileUpdateFunction(String (*getFileNameFxn)()) { _getFileNameFxn = getFileNameFxn; } @@ -371,9 +373,9 @@ int16_t S3PresignedPublisher::publishData(Client* outClient, bool) { did_respond = outClient->readBytes(tempBuffer, 12); // Process the HTTP response code // The first 9 characters should be "HTTP/1.1 " - if (did_respond > 0) { + if (did_respond >= 12) { char responseCode_char[4]; - memcpy(responseCode_char, tempBuffer + 9, 3); + memcpy(responseCode_char, tempBuffer + HTTP_VERSION_PREFIX_LEN, 3); // Null terminate the string memset(responseCode_char + 3, '\0', 1); responseCode = atoi(responseCode_char); diff --git a/src/publishers/S3PresignedPublisher.h b/src/publishers/S3PresignedPublisher.h index 633a3aae5..b87039254 100644 --- a/src/publishers/S3PresignedPublisher.h +++ b/src/publishers/S3PresignedPublisher.h @@ -134,31 +134,41 @@ class S3PresignedPublisher : public dataPublisher { public: // Constructors - /** - * @brief Construct a new S3 Publisher object with no members set. - */ - S3PresignedPublisher(); /** * @brief Construct a new S3 Publisher object * * @param baseLogger The logger supplying the data to be published + * @param inClient An Arduino client instance to use to print data to. + * Allows the use of any type of client and multiple clients tied to a + * single TinyGSM modem instance * @param caCertName The name of your certificate authority certificate * file - used to validate the server's certificate when connecting to S3 * with SSL. @see setCACertName() * @param getUrlFxn A function to call to get a new pre-signed URL * @param getFileNameFxn A function to call to get a new filename - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! * * @note The inputs to this is the **NAME** of the certificate **file** as * it is stored on you modem module, not the actual certificate content. */ + S3PresignedPublisher(Logger& baseLogger, Client* inClient, + const char* caCertName, + String (*getUrlFxn)(String) = nullptr, + String (*getFileNameFxn)() = nullptr); + /** + * @brief Construct a new S3 Publisher object with certificate + * + * @param baseLogger The logger supplying the data to be published + * @param caCertName The name of the certificate to use for SSL + * verification. This is the name of the certificate file as it is stored on + * you modem module, not the actual certificate content. + * @param getUrlFxn A function to call to get a new pre-signed URL + * @param getFileNameFxn A function to call to get a new filename + */ S3PresignedPublisher(Logger& baseLogger, const char* caCertName, - String (*getUrlFxn)(String) = nullptr, - String (*getFileNameFxn)(void) = nullptr, - int sendEveryX = 1); + String (*getUrlFxn)(String) = nullptr, + String (*getFileNameFxn)() = nullptr); /** - * @brief Construct a new S3 Publisher object + * @brief Construct a new S3 Publisher object without certificate * * @param baseLogger The logger supplying the data to be published * @param inClient An Arduino client instance to use to print data to. @@ -166,23 +176,24 @@ class S3PresignedPublisher : public dataPublisher { * single TinyGSM modem instance * @param getUrlFxn A function to call to get a new pre-signed URL * @param getFileNameFxn A function to call to get a new filename - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ S3PresignedPublisher(Logger& baseLogger, Client* inClient, - String (*getUrlFxn)(String) = nullptr, - String (*getFileNameFxn)(void) = nullptr, - int sendEveryX = 1); + String (*getUrlFxn)(String) = nullptr, + String (*getFileNameFxn)() = nullptr); + /** + * @brief Construct a new S3 Publisher object with no members set. + */ + S3PresignedPublisher(); /** * @brief Destroy the S3 Publisher object */ - virtual ~S3PresignedPublisher(); + ~S3PresignedPublisher() override = default; /** * @brief Set the S3 host name * * This is "s3..amazonaws.com" by default. - * If you need to use a host in a specific region (ie, anything but + * If you need to use a host in a specific region (i.e., anything but * US-East-1) you should set your own host. The host in that case should be: * "s3..amazonaws.com" * @@ -204,7 +215,7 @@ class S3PresignedPublisher : public dataPublisher { void setPort(int port); // Returns the data destination - String getEndpoint(void) override { + String getEndpoint() override { return String(s3_parent_host); } @@ -252,7 +263,7 @@ class S3PresignedPublisher : public dataPublisher { * * @param getFileNameFxn A function to call to get a new filename */ - void setFileUpdateFunction(String (*getFileNameFxn)(void)); + void setFileUpdateFunction(String (*getFileNameFxn)()); /** * @brief Set the name of your certificate authority certificate file. @@ -266,9 +277,9 @@ class S3PresignedPublisher : public dataPublisher { * Certificate Authority - G2). * * This is exactly the same CA certificate as you would use for an MQTT - * connection to AWS IoT (ie, the AWS IoT Publisher). For supported modules - * you can use the AWS_IOT_SetCertificates sketch in the extras folder to - * upload your certificate. + * connection to AWS IoT (i.e., the AWS IoT Publisher). For supported + * modules you can use the AWS_IOT_SetCertificates sketch in the extras + * folder to upload your certificate. * * @param caCertName The name of your certificate authority certificate * file. @@ -310,8 +321,8 @@ class S3PresignedPublisher : public dataPublisher { bool forceFlush = MS_ALWAYS_FLUSH_PUBLISHERS) override; protected: - virtual Client* createClient() override; - virtual void deleteClient(Client* client) override; + Client* createClient() override; + void deleteClient(Client* client) override; const char* s3_parent_host = "s3.amazonaws.com"; ///< The host name int s3Port = 443; ///< The host port @@ -341,7 +352,7 @@ class S3PresignedPublisher : public dataPublisher { * @note This will be *ignored* if the filename is set. If neither the * filename nor the file prefix is set, the logger ID will be used. */ - const char* _filePrefix; + const char* _filePrefix = nullptr; /** * @brief The extension to add to files, if generating a filename based on * the date/time @@ -349,11 +360,11 @@ class S3PresignedPublisher : public dataPublisher { * @note This will be *ignored* if the filename is set. If neither the * filename nor the file extension is set, `#S3_DEFAULT_FILE_EXTENSION`. */ - const char* _fileExtension; + const char* _fileExtension = nullptr; /** * @brief Private reference to function used fetch a new file name. */ - String (*_getFileNameFxn)(void); + String (*_getFileNameFxn)() = nullptr; /** * @brief The name of your certificate authority certificate file */ diff --git a/src/publishers/ThingSpeakPublisher.cpp b/src/publishers/ThingSpeakPublisher.cpp index 81a224de7..436dec2ec 100644 --- a/src/publishers/ThingSpeakPublisher.cpp +++ b/src/publishers/ThingSpeakPublisher.cpp @@ -12,7 +12,7 @@ // ============================================================================ -// Functions for the EnviroDIY data portal receivers. +// Functions for ThingSpeak // ============================================================================ // Constant values for MQTT publish @@ -26,38 +26,39 @@ const int ThingSpeakPublisher::mqttPort = 1883; // Constructors -ThingSpeakPublisher::ThingSpeakPublisher() : dataPublisher() {} -ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) {} +// Primary constructor with all MQTT parameters and client ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) {} -ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, const char* thingSpeakClientName, const char* thingSpeakMQTTUser, const char* thingSpeakMQTTPassword, - const char* thingSpeakChannelID, - int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setMQTTClient(thingSpeakClientName); - setUserName(thingSpeakMQTTUser); - setPassword(thingSpeakMQTTPassword); - setChannelID(thingSpeakChannelID); + const char* thingSpeakChannelID) + : dataPublisher(baseLogger, inClient) { + if (thingSpeakClientName) setMQTTClient(thingSpeakClientName); + if (thingSpeakMQTTUser) setUserName(thingSpeakMQTTUser); + if (thingSpeakMQTTPassword) setPassword(thingSpeakMQTTPassword); + if (thingSpeakChannelID) setChannelID(thingSpeakChannelID); } -ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, Client* inClient, + +// Delegating constructors +ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, const char* thingSpeakClientName, const char* thingSpeakMQTTUser, const char* thingSpeakMQTTPassword, - const char* thingSpeakChannelID, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setMQTTClient(thingSpeakClientName); - setUserName(thingSpeakMQTTUser); - setPassword(thingSpeakMQTTPassword); - setChannelID(thingSpeakChannelID); -} -// Destructor -ThingSpeakPublisher::~ThingSpeakPublisher() {} + const char* thingSpeakChannelID) + : ThingSpeakPublisher(baseLogger, nullptr, thingSpeakClientName, + thingSpeakMQTTUser, thingSpeakMQTTPassword, + thingSpeakChannelID) {} +ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger, Client* inClient) + : ThingSpeakPublisher( + baseLogger, inClient, static_cast(nullptr), + static_cast(nullptr), static_cast(nullptr), + static_cast(nullptr)) {} +ThingSpeakPublisher::ThingSpeakPublisher(Logger& baseLogger) + : ThingSpeakPublisher( + baseLogger, nullptr, static_cast(nullptr), + static_cast(nullptr), static_cast(nullptr), + static_cast(nullptr)) {} +ThingSpeakPublisher::ThingSpeakPublisher() : dataPublisher() {} void ThingSpeakPublisher::setMQTTClient(const char* thingSpeakClientName) { @@ -101,10 +102,8 @@ void ThingSpeakPublisher::begin(Logger& baseLogger, Client* inClient, const char* thingSpeakMQTTUser, const char* thingSpeakMQTTPassword, const char* thingSpeakChannelID) { - setMQTTClient(thingSpeakClientName); - setChannelID(thingSpeakChannelID); - setPassword(thingSpeakMQTTPassword); - setUserName(thingSpeakMQTTUser); + setThingSpeakParams(thingSpeakClientName, thingSpeakMQTTUser, + thingSpeakMQTTPassword, thingSpeakChannelID); dataPublisher::begin(baseLogger, inClient); } void ThingSpeakPublisher::begin(Logger& baseLogger, @@ -112,17 +111,35 @@ void ThingSpeakPublisher::begin(Logger& baseLogger, const char* thingSpeakMQTTUser, const char* thingSpeakMQTTPassword, const char* thingSpeakChannelID) { - setMQTTClient(thingSpeakClientName); - setChannelID(thingSpeakChannelID); - setPassword(thingSpeakMQTTPassword); - setUserName(thingSpeakMQTTUser); + setThingSpeakParams(thingSpeakClientName, thingSpeakMQTTUser, + thingSpeakMQTTPassword, thingSpeakChannelID); dataPublisher::begin(baseLogger); } // This sends the data to ThingSpeak int16_t ThingSpeakPublisher::publishData(Client* outClient, bool) { - bool retVal = false; + // Validate required MQTT parameters before proceeding + if (_thingSpeakChannelID == nullptr || strlen(_thingSpeakChannelID) == 0) { + MS_DBG(F("ERROR: ThingSpeak Channel ID is required but not set!")); + return -1; + } + if (_thingSpeakClientName == nullptr || + strlen(_thingSpeakClientName) == 0) { + MS_DBG(F("ERROR: ThingSpeak Client Name is required but not set!")); + return -1; + } + if (_thingSpeakMQTTUser == nullptr || strlen(_thingSpeakMQTTUser) == 0) { + MS_DBG(F("ERROR: ThingSpeak MQTT User is required but not set!")); + return -1; + } + if (_thingSpeakMQTTPassword == nullptr || + strlen(_thingSpeakMQTTPassword) == 0) { + MS_DBG(F("ERROR: ThingSpeak MQTT Password is required but not set!")); + return -1; + } + + int16_t status = 0; // Make sure we don't have too many fields // A channel can have a max of 8 fields @@ -186,7 +203,8 @@ int16_t ThingSpeakPublisher::publishData(Client* outClient, bool) { MS_DBG(F("Publishing to ThingSpeak")); PRINTOUT(F("\nTopic ["), strlen(topicBuffer), F("]:"), topicBuffer); PRINTOUT(F("Message ["), strlen(txBuffer), F("]:"), txBuffer); - retVal = _mqttClient.publish(topicBuffer, txBuffer, false); + bool publishSuccess = _mqttClient.publish(topicBuffer, txBuffer, false); + status = publishSuccess ? 1 : 0; PRINTOUT(F("ThingSpeak topic published! Current state:"), parseMQTTState(_mqttClient.state())); @@ -194,7 +212,7 @@ int16_t ThingSpeakPublisher::publishData(Client* outClient, bool) { PRINTOUT(F("MQTT connection failed with state:"), parseMQTTState(_mqttClient.state())); delay(1000); - retVal = false; + status = 0; } // Disconnect from MQTT @@ -202,7 +220,7 @@ int16_t ThingSpeakPublisher::publishData(Client* outClient, bool) { MS_RESET_DEBUG_TIMER _mqttClient.disconnect(); MS_DBG(F("Disconnected after"), MS_PRINT_DEBUG_TIMER, F("ms")); - return retVal; + return status; } // This updates your channel field names on ThingSpeak @@ -269,9 +287,9 @@ int16_t ThingSpeakPublisher::publishMetadata(Client* outClient) { did_respond = outClient->readBytes(tempBuffer, 12); // Process the HTTP response code // The first 9 characters should be "HTTP/1.1 " - if (did_respond > 0) { + if (did_respond >= 12) { char responseCode_char[4]; - memcpy(responseCode_char, tempBuffer + 9, 3); + memcpy(responseCode_char, tempBuffer + HTTP_VERSION_PREFIX_LEN, 3); // Null terminate the string memset(responseCode_char + 3, '\0', 1); responseCode = atoi(responseCode_char); diff --git a/src/publishers/ThingSpeakPublisher.h b/src/publishers/ThingSpeakPublisher.h index a201dd014..b7eae6a6d 100644 --- a/src/publishers/ThingSpeakPublisher.h +++ b/src/publishers/ThingSpeakPublisher.h @@ -45,7 +45,7 @@ * array attached to your logger is __crucial__. The results from the variables * in the VariableArray will be sent to ThingSpeak in the order they are in the * array; that is, the first variable in the array will be sent as Field1, the - * second as Field2, etc. Any UUID's or custom variable codes are ignored for + * second as Field2, etc. Any UUIDs or custom variable codes are ignored for * ThingSpeak. They will only appear in the header of your file on the SD card. * Giving a variable a custom variable code like "Field3" will **NOT** make that * variable field 3 on ThingSpeak. The third variable in the array will always @@ -57,23 +57,6 @@ class ThingSpeakPublisher : public dataPublisher { public: // Constructors - /** - * @brief Construct a new ThingSpeak Publisher object with no members - * initialized. - */ - ThingSpeakPublisher(); - /** - * @brief Construct a new ThingSpeak Publisher object - * - * @note If a client is never specified, the publisher will attempt to - * create and use a client on a LoggerModem instance tied to the attached - * logger. - * - * @param baseLogger The logger supplying the data to be published - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! - */ - explicit ThingSpeakPublisher(Logger& baseLogger, int sendEveryX = 1); /** * @brief Construct a new ThingSpeak Publisher object * @@ -81,11 +64,18 @@ class ThingSpeakPublisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param thingSpeakClientName The client name for your MQTT device. This is + * probably the same as your MQTT device's user name. + * @param thingSpeakMQTTUser The user name for your MQTT device. This is + * probably the same as your MQTT device's client name. + * @param thingSpeakMQTTPassword The password for your MQTT device. + * @param thingSpeakChannelID The numeric channel id for your channel. */ ThingSpeakPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX = 1); + const char* thingSpeakClientName, + const char* thingSpeakMQTTUser, + const char* thingSpeakMQTTPassword, + const char* thingSpeakChannelID); /** * @brief Construct a new ThingSpeak Publisher object * @@ -96,13 +86,11 @@ class ThingSpeakPublisher : public dataPublisher { * probably the same as your MQTT device's client name. * @param thingSpeakMQTTPassword The password for your MQTT device. * @param thingSpeakChannelID The numeric channel id for your channel. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ ThingSpeakPublisher(Logger& baseLogger, const char* thingSpeakClientName, const char* thingSpeakMQTTUser, const char* thingSpeakMQTTPassword, - const char* thingSpeakChannelID, int sendEveryX = 1); + const char* thingSpeakChannelID); /** * @brief Construct a new ThingSpeak Publisher object * @@ -110,27 +98,30 @@ class ThingSpeakPublisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param thingSpeakClientName The client name for your MQTT device. This is - * probably the same as your MQTT device's user name. - * @param thingSpeakMQTTUser The user name for your MQTT device. This is - * probably the same as your MQTT device's client name. - * @param thingSpeakMQTTPassword The password for your MQTT device. - * @param thingSpeakChannelID The numeric channel id for your channel. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ - ThingSpeakPublisher(Logger& baseLogger, Client* inClient, - const char* thingSpeakClientName, - const char* thingSpeakMQTTUser, - const char* thingSpeakMQTTPassword, - const char* thingSpeakChannelID, int sendEveryX = 1); + ThingSpeakPublisher(Logger& baseLogger, Client* inClient); + /** + * @brief Construct a new ThingSpeak Publisher object + * + * @note If a client is never specified, the publisher will attempt to + * create and use a client on a LoggerModem instance tied to the attached + * logger. + * + * @param baseLogger The logger supplying the data to be published + */ + explicit ThingSpeakPublisher(Logger& baseLogger); + /** + * @brief Construct a new ThingSpeak Publisher object with all members set + * to defaults or null. + */ + ThingSpeakPublisher(); /** * @brief Destroy the ThingSpeak Publisher object */ - virtual ~ThingSpeakPublisher(); + ~ThingSpeakPublisher() override = default; // Returns the data destination - String getEndpoint(void) override { + String getEndpoint() override { return String(mqttServer); } diff --git a/src/publishers/UbidotsPublisher.cpp b/src/publishers/UbidotsPublisher.cpp index 262e03d49..b6546847c 100644 --- a/src/publishers/UbidotsPublisher.cpp +++ b/src/publishers/UbidotsPublisher.cpp @@ -32,35 +32,33 @@ const char* UbidotsPublisher::payload = "{"; // Constructors -UbidotsPublisher::UbidotsPublisher() : dataPublisher() {} -UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) {} +// Primary constructor with all authentication parameters and client UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, Client* inClient, - int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) {} -UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, const char* authenticationToken, - const char* deviceID, int sendEveryX) - : dataPublisher(baseLogger, sendEveryX) { - setToken(authenticationToken); - _baseLogger->setSamplingFeatureUUID(deviceID); - MS_DBG(F("dataPublisher object created")); + const char* deviceID) + : dataPublisher(baseLogger, inClient) { + if (authenticationToken && authenticationToken[0] != '\0') { + setToken(authenticationToken); + } + if (deviceID && deviceID[0] != '\0') { + _baseLogger->setSamplingFeatureUUID(deviceID); + } } -UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, Client* inClient, + +// Delegating constructors +UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, const char* authenticationToken, - const char* deviceID, int sendEveryX) - : dataPublisher(baseLogger, inClient, sendEveryX) { - setToken(authenticationToken); - _baseLogger->setSamplingFeatureUUID(deviceID); - MS_DBG(F("dataPublisher object created")); -} -// Destructor -UbidotsPublisher::~UbidotsPublisher() {} + const char* deviceID) + : UbidotsPublisher(baseLogger, nullptr, authenticationToken, deviceID) {} +UbidotsPublisher::UbidotsPublisher(Logger& baseLogger, Client* inClient) + : UbidotsPublisher(baseLogger, inClient, nullptr, nullptr) {} +UbidotsPublisher::UbidotsPublisher(Logger& baseLogger) + : UbidotsPublisher(baseLogger, nullptr, nullptr, nullptr) {} +UbidotsPublisher::UbidotsPublisher() : dataPublisher() {} void UbidotsPublisher::setToken(const char* authenticationToken) { _authenticationToken = authenticationToken; - MS_DBG(F("Registration token set!")); } @@ -93,24 +91,30 @@ uint16_t UbidotsPublisher::calculateJsonSize() { void UbidotsPublisher::begin(Logger& baseLogger, Client* inClient, const char* authenticationToken, const char* deviceID) { - setToken(authenticationToken); + if (authenticationToken && authenticationToken[0] != '\0') { + setToken(authenticationToken); + } dataPublisher::begin(baseLogger, inClient); - _baseLogger->setSamplingFeatureUUID(deviceID); + if (deviceID && deviceID[0] != '\0') { + _baseLogger->setSamplingFeatureUUID(deviceID); + } } void UbidotsPublisher::begin(Logger& baseLogger, const char* authenticationToken, const char* deviceID) { - setToken(authenticationToken); + if (authenticationToken && authenticationToken[0] != '\0') { + setToken(authenticationToken); + } dataPublisher::begin(baseLogger); - _baseLogger->setSamplingFeatureUUID(deviceID); + if (deviceID && deviceID[0] != '\0') { + _baseLogger->setSamplingFeatureUUID(deviceID); + } } -// This utilizes an attached modem to make a TCP connection to the -// EnviroDIY/ODM2DataSharingPortal and then streams out a post request -// over that connection. -// The return is the http status code of the response. -// int16_t EnviroDIYPublisher::postDataEnviroDIY(void) +// This utilizes an attached modem to make a TCP connection to Ubidots and then +// streams out a post request over that connection. The return is the http +// status code of the response. int16_t UbidotsPublisher::publishData(Client* outClient, bool) { // Create a buffer for the portions of the request and response char tempBuffer[12] = ""; @@ -119,8 +123,13 @@ int16_t UbidotsPublisher::publishData(Client* outClient, bool) { if (_baseLogger->getSamplingFeatureUUID() == nullptr || strlen(_baseLogger->getSamplingFeatureUUID()) == 0) { PRINTOUT(F("A sampling feature UUID must be set before publishing data " - "to Monitor My Watershed!.")); - return 0; + "to Ubidots!")); + return -1; // Configuration error + } + if (_authenticationToken == nullptr || _authenticationToken[0] == '\0') { + PRINTOUT(F("An authentication token must be set before publishing data " + "to Ubidots!")); + return -1; // Configuration error } MS_DBG(F("Outgoing JSON size:"), calculateJsonSize()); @@ -186,9 +195,9 @@ int16_t UbidotsPublisher::publishData(Client* outClient, bool) { did_respond = outClient->readBytes(tempBuffer, 12); // Process the HTTP response code // The first 9 characters should be "HTTP/1.1 " - if (did_respond > 0) { + if (did_respond >= 12) { char responseCode_char[4]; - memcpy(responseCode_char, tempBuffer + 9, 3); + memcpy(responseCode_char, tempBuffer + HTTP_VERSION_PREFIX_LEN, 3); // Null terminate the string memset(responseCode_char + 3, '\0', 1); responseCode = atoi(responseCode_char); diff --git a/src/publishers/UbidotsPublisher.h b/src/publishers/UbidotsPublisher.h index e1efbc010..cd04b99a0 100644 --- a/src/publishers/UbidotsPublisher.h +++ b/src/publishers/UbidotsPublisher.h @@ -35,7 +35,7 @@ // ============================================================================ -// Functions for the EnviroDIY data portal receivers. +// Functions for Ubidots // ============================================================================ /** * @brief The UbidotsPublisher subclass of dataPublisher for publishing data @@ -46,22 +46,6 @@ class UbidotsPublisher : public dataPublisher { public: // Constructors - /** - * @brief Construct a new Ubidots Publisher object with no members set. - */ - UbidotsPublisher(); - /** - * @brief Construct a new Ubidots Publisher object - * - * @note If a client is never specified, the publisher will attempt to - * create and use a client on a LoggerModem instance tied to the attached - * logger. - * - * @param baseLogger The logger supplying the data to be published - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! - */ - explicit UbidotsPublisher(Logger& baseLogger, int sendEveryX = 1); /** * @brief Construct a new Ubidots Publisher object * @@ -69,10 +53,15 @@ class UbidotsPublisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! + * @param authenticationToken The authentication token from Ubidots, either + * the Organization's Integration Token (under Users > Organization menu, + * visible by Admin only) OR the STEM User's Device Token (under the + * specific device's setup panel). + * @param deviceID The device API Label from Ubidots, derived from the + * user-specified device name. */ - UbidotsPublisher(Logger& baseLogger, Client* inClient, int sendEveryX = 1); + UbidotsPublisher(Logger& baseLogger, Client* inClient, + const char* authenticationToken, const char* deviceID); /** * @brief Construct a new Ubidots Publisher object * @@ -83,11 +72,9 @@ class UbidotsPublisher : public dataPublisher { * specific device's setup panel). * @param deviceID The device API Label from Ubidots, derived from the * user-specified device name. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ UbidotsPublisher(Logger& baseLogger, const char* authenticationToken, - const char* deviceID, int sendEveryX = 1); + const char* deviceID); /** * @brief Construct a new Ubidots Publisher object * @@ -95,22 +82,27 @@ class UbidotsPublisher : public dataPublisher { * @param inClient An Arduino client instance to use to print data to. * Allows the use of any type of client and multiple clients tied to a * single TinyGSM modem instance - * @param authenticationToken The authentication token from Ubidots, either - * the Organization's Integration Token (under Users > Organization menu, - * visible by Admin only) OR the STEM User's Device Token (under the - * specific device's setup panel). - * @param deviceID The device API Label from Ubidots, derived from the - * user-specified device name. - * @param sendEveryX Interval (in units of the logging interval) between - * attempted data transmissions. NOTE: not implemented by this publisher! */ - UbidotsPublisher(Logger& baseLogger, Client* inClient, - const char* authenticationToken, const char* deviceID, - int sendEveryX = 1); + UbidotsPublisher(Logger& baseLogger, Client* inClient); + /** + * @brief Construct a new Ubidots Publisher object + * + * @note If a client is never specified, the publisher will attempt to + * create and use a client on a LoggerModem instance tied to the attached + * logger. + * + * @param baseLogger The logger supplying the data to be published + */ + explicit UbidotsPublisher(Logger& baseLogger); + /** + * @brief Construct a new Ubidots Publisher object with all members set to + * defaults or null. + */ + UbidotsPublisher(); /** - * @brief Destroy the EnviroDIY Publisher object + * @brief Destroy the Ubidots Publisher object */ - virtual ~UbidotsPublisher(); + ~UbidotsPublisher() override = default; // Returns the data destination /** @@ -119,7 +111,7 @@ class UbidotsPublisher : public dataPublisher { * * @return The URL or HOST to receive published data */ - String getEndpoint(void) override { + String getEndpoint() override { return String(ubidotsHost); } @@ -166,7 +158,7 @@ class UbidotsPublisher : public dataPublisher { // Post Data to Ubidots /** - * @brief Utilize an attached modem to open a a TCP connection to the + * @brief Utilize an attached modem to open a TCP connection to the * Ubidots API and then stream out a post request over * that connection. * diff --git a/src/sensors/ANBpH.cpp b/src/sensors/ANBpH.cpp index 2aafc3b90..605100ad2 100644 --- a/src/sensors/ANBpH.cpp +++ b/src/sensors/ANBpH.cpp @@ -10,53 +10,47 @@ #include "ANBpH.h" -// The constructor - need the sensor type, modbus address, power pin, stream for -// data, and number of readings to average +// The constructor ANBpH::ANBpH(byte modbusAddress, Stream* stream, int8_t powerPin, - int8_t powerPin2, int8_t enablePin, uint8_t measurementsToAverage) + int16_t loggingIntervalMinutes, int8_t powerPin2, int8_t enablePin, + uint8_t measurementsToAverage) : Sensor("ANBpHSensor", ANB_PH_NUM_VARIABLES, ANB_PH_WARM_UP_TIME_MS, - ANB_PH_STABILIZATION_TIME_MS, ANB_PH_1ST_VALUE_HIGH_SALT, powerPin, + ANB_PH_STABILIZATION_TIME_MS, ANB_PH_2ND_VALUE_LOW_SALT, powerPin, -1, measurementsToAverage, ANB_PH_INC_CALC_VARIABLES), _anb_sensor(modbusAddress, stream, enablePin), + _modbusAddress(modbusAddress), _stream(stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) { + _loggingIntervalMinutes(loggingIntervalMinutes), + _RS485EnablePin(enablePin) { #ifdef MS_ANB_SENSORS_PH_DEBUG_DEEP _anb_sensor.setDebugStream(&MS_SERIAL_OUTPUT); #endif + setSecondaryPowerPin(powerPin2); + setMaxRetries(ANB_PH_DEFAULT_MEASUREMENT_RETRIES); } +// Delegating constructor ANBpH::ANBpH(byte modbusAddress, Stream& stream, int8_t powerPin, - int8_t powerPin2, int8_t enablePin, uint8_t measurementsToAverage) - : Sensor("ANBpHSensor", ANB_PH_NUM_VARIABLES, ANB_PH_WARM_UP_TIME_MS, - ANB_PH_STABILIZATION_TIME_MS, ANB_PH_1ST_VALUE_HIGH_SALT, powerPin, - -1, measurementsToAverage, ANB_PH_INC_CALC_VARIABLES), - _anb_sensor(modbusAddress, stream, enablePin), - _modbusAddress(modbusAddress), - _stream(&stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) { -#ifdef MS_ANB_SENSORS_PH_DEBUG_DEEP - _anb_sensor.setDebugStream(&MS_SERIAL_OUTPUT); -#endif -} -// Destructor -ANBpH::~ANBpH() {} + int16_t loggingIntervalMinutes, int8_t powerPin2, int8_t enablePin, + uint8_t measurementsToAverage) + : ANBpH(modbusAddress, &stream, powerPin, loggingIntervalMinutes, powerPin2, + enablePin, measurementsToAverage) {} // The sensor installation location on the Mayfly -String ANBpH::getSensorLocation(void) { - String sensorLocation = F("modbus_0x"); +String ANBpH::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve(12); // Reserve for "modbus_0x" + 2 hex chars + sensorLocation = F("modbus_0x"); if (_modbusAddress < 16) sensorLocation += "0"; sensorLocation += String(_modbusAddress, HEX); return sensorLocation; } -bool ANBpH::setup(void) { +bool ANBpH::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup // status bit if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - if (_powerPin2 >= 0) { pinMode(_powerPin2, OUTPUT); } // This sensor needs power for setup! delay(10); @@ -144,26 +138,64 @@ bool ANBpH::setup(void) { MS_DBG(F("..."), powerStyleSet ? F("success") : F("failed")); retVal &= powerStyleSet; - // Set sampling mode to continuous - // Since we are using controlled mode, continuous sampling means taking a - // measurement when asked, rather than at a set interval. - // The first pH measurement is returned significantly faster in continuous - // mode. - MS_DBG(F("Set sensor sampling mode to continuous...")); - bool intervalSet = _anb_sensor.setIntervalTime(0); + bool intervalSet = false; + uint16_t programmedInterval = _loggingIntervalMinutes; + + // Validate and normalize loggingIntervalMinutes based on powerPin +#if defined(MS_ANB_SENSORS_PH_DEBUG) || defined(MS_ANB_SENSORS_PH_DEBUG_DEEP) + int16_t originalInterval = + _loggingIntervalMinutes; // Store original for debug messages +#endif + // Handle special case: interval == 0 when power is cycled + if (_powerPin >= 0 && _loggingIntervalMinutes == 0) { + programmedInterval = 10; + _loggingIntervalMinutes = 10; // Update the stored value + MS_DBG(F("Requested interval of"), originalInterval, + F("minutes is invalid when power is cycled; using"), + programmedInterval, F("minutes.")); + } else if (_powerPin < 0 && _loggingIntervalMinutes == 0) { + programmedInterval = 0; // Allow 0 for always-on mode + // No need to change _loggingIntervalMinutes + } else { + // Shared validation for <10 and >240 limits + programmedInterval = + _loggingIntervalMinutes; // Start with original value + if (_loggingIntervalMinutes < 10) { + programmedInterval = 10; + _loggingIntervalMinutes = 10; // Update the stored value + MS_DBG(F("Requested interval of"), originalInterval, + F("minutes is too short; using"), programmedInterval, + F("minutes.")); + } else if (_loggingIntervalMinutes > 240) { + programmedInterval = 240; + _loggingIntervalMinutes = 240; // Update the stored value + MS_DBG(F("Requested interval of"), originalInterval, + F("minutes is too long; using"), programmedInterval, + F("minutes.")); + } + } + if (_powerPin >= 0) { + // Set sampling interval to the expected sampling interval if the sensor + MS_DBG(F("Set sensor sampling interval to"), programmedInterval, + F("minutes...")); + intervalSet = _anb_sensor.setIntervalTime(programmedInterval); + } else { + // Set sampling interval to continuous if the sensor will be + // continuously powered (i.e., a power style of ALWAYS_POWERED). + MS_DBG(F("Set sensor sampling interval to 0 (continuous)...")); + intervalSet = _anb_sensor.setIntervalTime(0); + } MS_DBG(F("..."), intervalSet ? F("success") : F("failed")); retVal &= intervalSet; // Set Sensor Salinity Mode - MS_DBG(F("Set sensor salinity mode...")); - bool salinitySet = _anb_sensor.setSalinityMode(_salinityMode); - MS_DBG(F("..."), salinitySet ? F("success") : F("failed")); - retVal &= salinitySet; + retVal &= setSalinityMode(_salinityMode); // Set Immersion Rule MS_DBG(F("Set sensor immersion rule to"), - _immersionSensorEnabled ? "enabled" : "disabled", F("...")); - bool immersionSet = _anb_sensor.enableImmersionSensor(); + _immersionSensorEnabled ? F("enabled") : F("disabled"), F("...")); + bool immersionSet = + _anb_sensor.enableImmersionSensor(_immersionSensorEnabled); MS_DBG(F("..."), immersionSet ? F("success") : F("failed")); retVal &= immersionSet; @@ -187,12 +219,29 @@ bool ANBpH::setup(void) { } -// Start measurements -bool ANBpH::startSingleMeasurement(void) { - // Sensor::startSingleMeasurement() checks that if it's awake/active and - // sets the timestamp and status bits. If it returns false, there's no - // reason to go on. - if (!Sensor::startSingleMeasurement()) return false; +bool ANBpH::wake() { + // Sensor::wake() checks if the power pin is on and sets the wake timestamp + // and status bits. If it returns false, there's no reason to go on. + if (!Sensor::wake()) return false; + + MS_DEEP_DBG(F("Checking for modbus response confirming"), + getSensorNameAndLocation(), F("is awake")); + bool is_ready = isSensorReady(&anbSensor::isSensorReady, + ANB_PH_MINIMUM_REQUEST_SPACING, + _millisPowerOn); + if (!is_ready) { + MS_DEEP_DBG(getSensorNameAndLocation(), + F("isn't responding to modbus commands; wake failed!")); + // Set the status error bit (bit 7) + setStatusBit(ERROR_OCCURRED); + // Make sure that the wake time and wake success bit (bit 4) are unset + _millisSensorActivated = 0; + clearStatusBit(WAKE_SUCCESSFUL); + return false; + } + + MS_DEEP_DBG(getSensorNameAndLocation(), + F("responded properly to modbus commands; it must be awake.")); // If the sensor is being power cycled, set the clock before each // measurement. The sensor stores the measurements on its internal storage, @@ -202,7 +251,7 @@ bool ANBpH::startSingleMeasurement(void) { // Send the command to begin taking readings, trying up to 5 times bool success = false; uint8_t ntries = 0; - MS_DBG(F("Start Measurement on"), getSensorNameAndLocation()); + MS_DBG(F("Start scanning on"), getSensorNameAndLocation()); while (!success && ntries < 5) { MS_DEEP_DBG('(', ntries + 1, F("):")); success = _anb_sensor.start(); @@ -210,50 +259,19 @@ bool ANBpH::startSingleMeasurement(void) { } if (success) { - MS_DEEP_DBG(getSensorNameAndLocation(), F("started a scan.")); + MS_DEEP_DBG(getSensorNameAndLocation(), F("started scanning.")); // Update the time that a measurement was requested - _millisMeasurementRequested = millis(); + _millisSensorActivated = millis(); + _lastModbusCommandTime = 0; } else { // Set the status error bit (bit 7) setStatusBit(ERROR_OCCURRED); - // Otherwise, make sure that the measurement start time and success bit - // (bit 6) are unset - MS_DBG(getSensorNameAndLocation(), - F("did not successfully start a measurement.")); - _millisMeasurementRequested = 0; - clearStatusBit(MEASUREMENT_SUCCESSFUL); - } - - return success; -} - - -// This confirms that the sensor is really giving modbus responses so nothing -// further happens if not. - It's a "check if it's awake" function rather than a -// "wake it up" function. -bool ANBpH::wake(void) { - // Sensor::wake() checks if the power pin is on and sets the wake timestamp - // and status bits. If it returns false, there's no reason to go on. - if (!Sensor::wake()) return false; - - MS_DEEP_DBG(F("Checking for modbus response confirming"), - getSensorNameAndLocation(), F("is awake")); - bool is_ready = isSensorReady(&anbSensor::gotModbusResponse, - ANB_PH_MINIMUM_REQUEST_SPACING, - _millisPowerOn); - if (!is_ready) { - MS_DEEP_DBG(getSensorNameAndLocation(), - F("isn't responding to modbus commands; wake failed!")); - // Set the status error bit (bit 7) - setStatusBit(ERROR_OCCURRED); // Make sure that the wake time and wake success bit (bit 4) are unset _millisSensorActivated = 0; clearStatusBit(WAKE_SUCCESSFUL); return false; } - MS_DEEP_DBG(getSensorNameAndLocation(), - F("responded properly to modbus commands; it must be awake.")); return true; } @@ -261,7 +279,7 @@ bool ANBpH::wake(void) { // The function to put the sensor to sleep // Different from the standard in that it stops measurements and empties and // flushes the stream. -bool ANBpH::sleep(void) { +bool ANBpH::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); @@ -274,7 +292,7 @@ bool ANBpH::sleep(void) { return true; } - // Send the command to begin taking readings, trying up to 5 times + // Send the command to stop taking readings, trying up to 5 times bool success = false; uint8_t ntries = 0; MS_DBG(F("Stop Measurement on"), getSensorNameAndLocation()); @@ -304,77 +322,16 @@ bool ANBpH::sleep(void) { return success; } +bool ANBpH::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } -// This turns on sensor power -void ANBpH::powerUp(void) { - if (_powerPin >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); - } - if (_powerPin2 >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin2, OUTPUT); - MS_DBG(F("Applying secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, HIGH); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - } else { - // Mark the time that the sensor was powered - _millisPowerOn = millis(); - } - // Reset enable pin because pins are set to tri-state on sleep - if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - // Set the status bit for sensor power attempt (bit 1) and success (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); -} - - -// This turns off sensor power -void ANBpH::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); - // Unset the power-on time - _millisPowerOn = 0; - // Unset the activation time - _millisSensorActivated = 0; - // Unset the measurement request time - _millisMeasurementRequested = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - } - if (_powerPin2 >= 0) { - MS_DBG(F("Turning off secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, LOW); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Do NOT unset any status bits or timestamps if we didn't really power - // down! - } -} - - -bool ANBpH::addSingleMeasurementResult(void) { - bool success = false; - // Initialize variables for each value - float pH, temp, sal, spcond, raw_cond = -9999; + bool success = false; + float pH = MS_INVALID_VALUE; + float temp = MS_INVALID_VALUE; + float sal = MS_INVALID_VALUE; + float spcond = MS_INVALID_VALUE; + float raw_cond = MS_INVALID_VALUE; ANBHealthCode health = ANBHealthCode::UNKNOWN; ANBStatusCode status = ANBStatusCode::UNKNOWN; ANBDiagnosticCode diagnostic = ANBDiagnosticCode::UNKNOWN; @@ -396,33 +353,38 @@ bool ANBpH::addSingleMeasurementResult(void) { ':', time_buff); #endif - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - // Print Moisture Values - MS_DBG(F("Get Values from"), getSensorNameAndLocation()); - success = _anb_sensor.getValues(pH, temp, sal, spcond, raw_cond, health, - diagnostic); - status = _anb_sensor.getStatusCode(); - - // Print the values for debugging - MS_DBG(F(" pH:"), pH); - MS_DBG(F(" Temperature (C):"), temp); - MS_DBG(F(" Salinity (ppt):"), sal); - MS_DBG(F(" Specific Conductance (µS/cm):"), spcond); - MS_DBG(F(" Raw Conductance (µS/cm):"), raw_cond); - MS_DBG(F(" Health Code:"), static_cast(health), '-', - _anb_sensor.getHealthString(health)); - MS_DBG(F(" Diagnostic Code:"), static_cast(diagnostic), '-', - _anb_sensor.getDiagnosticString(diagnostic)); - MS_DBG(F(" Status Code:"), static_cast(status), '-', - _anb_sensor.getStatusString(status)); - - if (health == ANBHealthCode::NOT_IMMERSED) { - PRINTOUT(F(" WARNING: ANB pH sensor is not immersed!")); - } - - // Put values into the array + MS_DBG(F("Get Values from"), getSensorNameAndLocation()); + success = _anb_sensor.getValues(pH, temp, sal, spcond, raw_cond, health, + diagnostic); + status = _anb_sensor.getStatusCode(); + + // Print the values for debugging + MS_DBG(F(" pH:"), pH); + MS_DBG(F(" Temperature (C):"), temp); + MS_DBG(F(" Salinity (ppt):"), sal); + MS_DBG(F(" Specific Conductance (µS/cm):"), spcond); + MS_DBG(F(" Raw Conductance (µS/cm):"), raw_cond); + MS_DBG(F(" Health Code:"), static_cast(health), '-', + _anb_sensor.getHealthString(health)); + MS_DBG(F(" Diagnostic Code:"), static_cast(diagnostic), '-', + _anb_sensor.getDiagnosticString(diagnostic)); + MS_DBG(F(" Status Code:"), static_cast(status), '-', + _anb_sensor.getStatusString(status)); + + if (health == ANBHealthCode::NOT_IMMERSED) { + PRINTOUT(F(" WARNING: ANB pH sensor is not immersed!")); + } + + // We consider a measurement successful if we got a modbus response and + // the pH value is in range or the health code says the sensor is not + // immersed. We accept the not immersed condition as a successful + // measurement because the sensor will not retry for at least 5 minutes + // after an immersion error. + success &= ((0.0 < pH && pH < 14.00) || + health == ANBHealthCode::NOT_IMMERSED); + + // Put values into the array - if it's a success or our last try + if (success || _currentRetries >= _maxRetries) { verifyAndAddMeasurementResult(ANB_PH_PH_VAR_NUM, pH); verifyAndAddMeasurementResult(ANB_PH_TEMP_VAR_NUM, temp); verifyAndAddMeasurementResult(ANB_PH_SALINITY_VAR_NUM, sal); @@ -434,17 +396,10 @@ bool ANBpH::addSingleMeasurementResult(void) { static_cast(diagnostic)); verifyAndAddMeasurementResult(ANB_PH_STATUS_CODE_VAR_NUM, static_cast(status)); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // Return true when finished - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } // check if the sensor is ready @@ -504,7 +459,7 @@ bool ANBpH::isWarmedUp(bool debug) { getSensorNameAndLocation(), F("timed out after power up.")); return true; // timeout } else if (elapsed_since_power_on > _warmUpTime_ms) { - bool is_ready = isSensorReady(&anbSensor::gotModbusResponse, + bool is_ready = isSensorReady(&anbSensor::isSensorReady, ANB_PH_MINIMUM_REQUEST_SPACING, _millisPowerOn); if (is_ready) { @@ -519,80 +474,15 @@ bool ANBpH::isWarmedUp(bool debug) { } } - -// This checks to see if enough time has passed for stability -bool ANBpH::isStable(bool debug) { -#if defined(MS_ANB_SENSORS_PH_DEBUG_DEEP) || defined(MS_SENSORBASE_DEBUG) - debug = true; -#endif - // If the sensor failed to activate, it will never stabilize, so the - // stabilization time is essentially already passed - if (!getStatusBit(WAKE_SUCCESSFUL)) { - if (debug) { - MS_DBG(getSensorNameAndLocation(), - F("is not active and cannot stabilize!")); - } - return true; - } - - uint32_t elapsed_since_wake_up = millis() - _millisSensorActivated; - uint32_t minTime = _stabilizationTime_ms; - uint32_t maxTime = ANB_PH_STABILIZATION_TIME_MAX; - // If the sensor has been activated and enough time has elapsed, it's stable - if (elapsed_since_wake_up > maxTime) { - MS_DBG(F("It's been"), elapsed_since_wake_up, F("ms, and"), - getSensorNameAndLocation(), - F("timed out waiting for a valid status code.")); - return true; // timeout - } else if (elapsed_since_wake_up > minTime) { - bool is_ready = isSensorReady(&anbSensor::isSensorReady, - ANB_PH_MINIMUM_REQUEST_SPACING, - _millisSensorActivated); - if (is_ready) { - MS_DBG(F("It's been"), elapsed_since_wake_up, F("ms, and"), - getSensorNameAndLocation(), - F("gave a valid status code, indicating it's ready to " - "start a measurement.")); - } - return is_ready; - } else { - // Wait at least the minimum readiness time - return false; - } -} - - -// The minimum time before we start checking for a result depends on whether -// the immersion sensor is enabled or not and on the power style. -// If the immersion sensor is enabled, we wait the minimum time that it -// would return a not-immersed error to start querying. If the immersion -// sensor is not enabled, we wait the minimum time for a measurement to be -// ready based on the power style. -uint32_t ANBpH::getStartImmersionErrorWindow(void) { - if (!_immersionSensorEnabled) { return getEndMeasurementWindow(); } - return _powerPin >= 0 ? ANB_PH_2ND_IMMERSION_ERROR - : ANB_PH_1ST_IMMERSION_ERROR; -} - -uint32_t ANBpH::getEndImmersionErrorWindow(void) { - if (!_immersionSensorEnabled) { return getEndMeasurementWindow(); } - return _powerPin >= 0 ? ANB_PH_2ND_IMMERSION_ERROR_MAX - : ANB_PH_1ST_IMMERSION_ERROR_MAX; -} - -uint32_t ANBpH::getStartMeasurementWindow(void) { - if (_powerPin >= 0) { +uint32_t ANBpH::getStartMeasurementWindow() { + if (_powerPin >= 0 && _currentRetries == 0) { if (_salinityMode == ANBSalinityMode::HIGH_SALINITY) { return ANB_PH_1ST_VALUE_HIGH_SALT; } else { return ANB_PH_1ST_VALUE_LOW_SALT; } } else { - if (_salinityMode == ANBSalinityMode::HIGH_SALINITY) { - return ANB_PH_2ND_VALUE_HIGH_SALT; - } else { - return ANB_PH_2ND_VALUE_LOW_SALT; - } + return 0; } } @@ -600,8 +490,8 @@ uint32_t ANBpH::getStartMeasurementWindow(void) { // the maximum wait time for the second measurement as our maximum wait. // If a pin was provided for power, we assume it's on-demand powered and use // the maximum wait time for the first measurement as our maximum wait. -uint32_t ANBpH::getEndMeasurementWindow(void) { - if (_powerPin >= 0) { +uint32_t ANBpH::getEndMeasurementWindow() { + if (_powerPin >= 0 && _currentRetries == 0) { if (_salinityMode == ANBSalinityMode::HIGH_SALINITY) { return ANB_PH_1ST_VALUE_HIGH_SALT_MAX; } else { @@ -609,9 +499,9 @@ uint32_t ANBpH::getEndMeasurementWindow(void) { } } else { if (_salinityMode == ANBSalinityMode::HIGH_SALINITY) { - return ANB_PH_2ND_VALUE_HIGH_SALT_MAX; + return ANB_PH_2ND_VALUE_HIGH_SALT; } else { - return ANB_PH_2ND_VALUE_LOW_SALT_MAX; + return ANB_PH_2ND_VALUE_LOW_SALT; } } } @@ -633,6 +523,25 @@ bool ANBpH::isMeasurementComplete(bool debug) { } uint32_t elapsed_since_meas_start = millis() - _millisMeasurementRequested; + + // After the first measurement, the sensor will always report that a + // measurement is ready, but a new value will not be available for at + // least 10.5 (high salinity) or 14 (low salinity) seconds. + if (_currentRetries > 0) { + if (elapsed_since_meas_start > _measurementTime_ms) { + if (debug) { + MS_DBG(F("It's been"), elapsed_since_meas_start, + F("ms, and measurement by"), getSensorNameAndLocation(), + F("should be complete!")); + } + return true; + } else { + // If the sensor is measuring but the time hasn't passed, we still + // need to wait + return false; + } + } + // If we're past the maximum wait time, the measurement failed, but our wait // is over if (elapsed_since_meas_start > getEndMeasurementWindow()) { @@ -641,24 +550,12 @@ bool ANBpH::isMeasurementComplete(bool debug) { F("timed out waiting for a measurement to complete.")); return true; // timeout } - // If we haven't gotten to the minimum response time, we need to wait - if (elapsed_since_meas_start < getStartImmersionErrorWindow()) { - return false; - } bool is_ready = false; - // Check every half second if we're in the window where the immersion - // sensor might return a not-immersed error - if (elapsed_since_meas_start > getStartImmersionErrorWindow() && - elapsed_since_meas_start <= getEndImmersionErrorWindow()) { - is_ready = isSensorReady(&anbSensor::isMeasurementComplete, 1000L, - _millisMeasurementRequested); - } // Since the sensor takes so very long to measure when it's power cycled, if // we know it's going to be a while, we drop the query frequency to once // every 15 seconds because there's no point in asking more often than that. - if (elapsed_since_meas_start > getEndImmersionErrorWindow() && - elapsed_since_meas_start <= getStartMeasurementWindow()) { + if (elapsed_since_meas_start <= getStartMeasurementWindow()) { is_ready = isSensorReady(&anbSensor::isMeasurementComplete, 15000L, _millisMeasurementRequested); } @@ -679,8 +576,19 @@ bool ANBpH::isMeasurementComplete(bool debug) { bool ANBpH::setSalinityMode(ANBSalinityMode newSalinityMode) { + MS_DBG(F("Set sensor salinity mode...")); + bool salinitySet = _anb_sensor.setSalinityMode(newSalinityMode); + MS_DBG(F("..."), salinitySet ? F("success") : F("failed")); + if (!salinitySet) { return false; } + // If we succeeded in setting the salinity mode, update the local copy and + // the measurement time _salinityMode = newSalinityMode; - return _anb_sensor.setSalinityMode(newSalinityMode); + if (_salinityMode == ANBSalinityMode::HIGH_SALINITY) { + _measurementTime_ms = ANB_PH_2ND_VALUE_HIGH_SALT; + } else { + _measurementTime_ms = ANB_PH_2ND_VALUE_LOW_SALT; + } + return true; } bool ANBpH::enableImmersionSensor(bool enable) { @@ -704,7 +612,7 @@ bool ANBpH::setSensorRTC() { int8_t day = -1; int8_t month = -1; int16_t year = -1; - uint8_t tz_offset = -1; + uint8_t tz_offset = 0; // Neutral value, will be overwritten by getNowParts Logger::getNowParts(seconds, minutes, hours, day, month, year, tz_offset); #if defined(MS_ANB_SENSORS_PH_DEBUG_DEEP) char time_buff_l[20] = {'\0'}; diff --git a/src/sensors/ANBpH.h b/src/sensors/ANBpH.h index 0080c1600..941f0c067 100644 --- a/src/sensors/ANBpH.h +++ b/src/sensors/ANBpH.h @@ -58,6 +58,11 @@ * the time the logger woke or other sensors took measurements by the time it * takes the pH sensor to warm up and take a reading. * + * @section sensor_anb_ph_config_flags Build flags + * - `-D ANB_PH_DEFAULT_MEASUREMENT_RETRIES=##` + * - used to set the default number of measurement retries for ANB pH + * sensors when communication errors occur + * * @section sensor_anb_ph_ctor Sensor Constructor * {{ @ref ANBpH::ANBpH }} * @@ -101,6 +106,28 @@ /** @ingroup sensor_anb_ph */ /**@{*/ +/** + * @anchor sensor_anb_ph_config + * @name Configuration Defines + * Define for the ANB pH measurement retry behavior. + */ +/**@{*/ +#if !defined(ANB_PH_DEFAULT_MEASUREMENT_RETRIES) || defined(DOXYGEN) +/** + * @brief The default number of measurement retries for ANB pH sensors when + * communication errors occur. + * + * ANB pH sensors use Modbus communication which can be susceptible to + * communication errors, especially in harsh environmental conditions. This + * define sets the default number of retries when a measurement fails. + * + * @note The default value of 5 retries provides good reliability while + * preventing excessive delays in case of persistent communication issues. + */ +#define ANB_PH_DEFAULT_MEASUREMENT_RETRIES 5 +#endif +/**@}*/ + /** * @brief The minimum spacing between requesting responses from the sensor. * @@ -144,56 +171,41 @@ * This is the time for communication to begin. */ #define ANB_PH_WARM_UP_TIME_MS 5400L -/// @brief The maximum time to wait for a modbus response. +/// @brief The maximum time to wait for a non-error modbus response. #define ANB_PH_WARM_UP_TIME_MAX 10000L /// @brief Sensor::_stabilizationTime_ms; the ANB pH sensor does not need to -/// stabilize, but we use this time as the check for ready time. -#define ANB_PH_STABILIZATION_TIME_MS 50 -/// @brief The maximum time to wait for ready to measure. -#define ANB_PH_STABILIZATION_TIME_MAX 5000L - -/// @brief The minimum time before a failure response is returned on the 2nd or -/// subsequent value when the immersion sensor is not immersed. This is a guess -/// based on testing. -#define ANB_PH_2ND_IMMERSION_ERROR 4000L -/// @brief The minimum time before a failure response is returned on the 2nd or -/// subsequent value when the immersion sensor is not immersed. This is a guess -/// based on testing. -#define ANB_PH_2ND_IMMERSION_ERROR_MAX 12000L -/// @brief The minimum time for the 2nd or subsequent values in high -/// salinity (documented new output time of 10.5s) -#define ANB_PH_2ND_VALUE_HIGH_SALT 5000L -/// @brief The maximum time for the 2nd or subsequent values in high -/// salinity. -#define ANB_PH_2ND_VALUE_HIGH_SALT_MAX 15000L -/// @brief The minimum time for the 2nd or subsequent values in low -/// salinity (documented new output time of 14s). -#define ANB_PH_2ND_VALUE_LOW_SALT 6000L -/// @brief The maximum time for the 2nd or subsequent values in low -/// salinity (documented new output time of 14s). -#define ANB_PH_2ND_VALUE_LOW_SALT_MAX 18000L - -/// @brief The minimum time before a failure response is returned on the first -/// measurement when the immersion sensor is not immersed. This is a guess -/// based on testing. -#define ANB_PH_1ST_IMMERSION_ERROR 6000L -/// @brief The maximum time before a failure response is returned on the first -/// measurement when the immersion sensor is not immersed. This is a guess -/// based on testing. -#define ANB_PH_1ST_IMMERSION_ERROR_MAX 12000L -/// @brief The minimum time for the first value in high salinity (documented min -/// time of 129s - 9s). -#define ANB_PH_1ST_VALUE_HIGH_SALT 120000L +/// stabilize - the stabilization and referencing time is included in the time +/// before the first value. +#define ANB_PH_STABILIZATION_TIME_MS 0L + +/// @brief The minimum time for the first value in high salinity (check 5 +/// seconds before the documented min time of 129s). +/// @note If the immersion sensor is enabled and the sensor is not immersed, a +/// failure response may be returned sooner +#define ANB_PH_1ST_VALUE_HIGH_SALT 124000L /// @brief The maximum time for the first value in high salinity (documented max /// time of 238s for a long interval delay + 10s). #define ANB_PH_1ST_VALUE_HIGH_SALT_MAX 248000L /// @brief The minimum time for the first value in low salinity (documented min -/// time is 184s, but I got responses at 160s). -#define ANB_PH_1ST_VALUE_LOW_SALT 155000L +/// time is 184s, but I got responses at 160s so we check 1s before that). +/// @note If the immersion sensor is enabled and the sensor is not immersed, a +/// failure response may be returned sooner +#define ANB_PH_1ST_VALUE_LOW_SALT 159000L /// @brief The maximum time for the first value in low salinity (documented max /// time of 255s for a long interval delay + 10s). #define ANB_PH_1ST_VALUE_LOW_SALT_MAX 265000L + +/// @brief The minimum time for the 2nd or subsequent values in high +/// salinity (documented new output time of 10.5s, add 100ms buffer). +/// @warning After the first reading, the sensor will *always* say the sensor is +/// ready! But there will not be a **new** value available before this time. +#define ANB_PH_2ND_VALUE_HIGH_SALT 10600L +/// @brief The minimum time for the 2nd or subsequent values in low +/// salinity (documented new output time of 14s, add 100ms buffer). +/// @warning After the first reading, the sensor will *always* say the sensor is +/// ready! But there will not be a **new** value available before this time. +#define ANB_PH_2ND_VALUE_LOW_SALT 14100L /**@}*/ /** @@ -209,8 +221,14 @@ * {{ @ref ANBpH_pH::ANBpH_pH }} */ /**@{*/ -/// @brief Decimals places in string representation; soil moisture should have 1 -/// - resolution is 0.01. +/// @brief Minimum pH value. +#define ANB_PH_PH_MIN 0 +/// @brief Maximum pH value. +/// @note The valid range is up to 14, but we allow up to 100 to keep the 99.99 +/// error code from being out of range.) +#define ANB_PH_PH_MAX 100 +/// @brief Decimal places in string representation; pH should have 2 - +/// resolution is 0.01. #define ANB_PH_PH_RESOLUTION 2 /// @brief Sensor variable number; pH is stored in sensorValues[0]. #define ANB_PH_PH_VAR_NUM 0 @@ -233,7 +251,11 @@ * {{ @ref ANBpH_Temp::ANBpH_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature in degrees Celsius. +#define ANB_PH_TEMP_MIN_C -5.0 +/// @brief Maximum temperature in degrees Celsius. +#define ANB_PH_TEMP_MAX_C 40.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define ANB_PH_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -263,10 +285,12 @@ * @note If both the pH and salinity output is 99.99, check the * transducer health code for instruction. * + * @todo Find and define minimum and maximum salinity measurement range + * * {{ @ref ANBpH_Salinity::ANBpH_Salinity }} */ /**@{*/ -/// @brief Decimals places in string representation; salinity should have 2. +/// @brief Decimal places in string representation; salinity should have 2. #define ANB_PH_SALINITY_RESOLUTION 2 /// @brief Sensor variable number; salinity is stored in sensorValues[2]. #define ANB_PH_SALINITY_VAR_NUM 2 @@ -297,10 +321,13 @@ * @note If both the pH and specific conductance output is 99.99, check the * transducer health code for instruction. * + * @todo Find and define minimum and maximum specific conductance measurement + * range + * * {{ @ref ANBpH_SpCond::ANBpH_SpCond }} */ /**@{*/ -/// @brief Decimals places in string representation; specific conductance +/// @brief Decimal places in string representation; specific conductance /// should have 2. #define ANB_PH_SPCOND_RESOLUTION 2 /// @brief Sensor variable number; specific conductance is stored in @@ -334,11 +361,14 @@ * @note If both the pH and raw conductivity output is 99.99, check the * transducer health code for instruction. * + * @todo Find and define minimum and maximum electrical conductivity measurement + * range + * * {{ @ref ANBpH_EC::ANBpH_EC }} */ /**@{*/ -/// @brief Decimals places in string representation; raw electrical conductivity -/// should have 2. +/// @brief Decimal places in string representation; raw electrical conductivity +/// should have 3. #define ANB_PH_EC_RESOLUTION 3 /// @brief Sensor variable number; conductivity is stored in sensorValues[4]. #define ANB_PH_EC_VAR_NUM 4 @@ -381,9 +411,9 @@ */ /**@{*/ // clang-format on -/// @brief Decimals places in string representation; the health code has 0. +/// @brief Decimal places in string representation; the health code has 0. #define ANB_PH_HEALTH_CODE_RESOLUTION 0 -/// @brief Sensor variable number; health code is stored in sensorValues[4] +/// @brief Sensor variable number; health code is stored in sensorValues[5] #define ANB_PH_HEALTH_CODE_VAR_NUM 5 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -417,9 +447,9 @@ */ /**@{*/ // clang-format on -/// @brief Decimals places in string representation; the diagnostic code has 0. +/// @brief Decimal places in string representation; the diagnostic code has 0. #define ANB_PH_DIAGNOSTIC_CODE_RESOLUTION 0 -/// @brief Sensor variable number; diagnostic code is stored in sensorValues[4] +/// @brief Sensor variable number; diagnostic code is stored in sensorValues[6] #define ANB_PH_DIAGNOSTIC_CODE_VAR_NUM 6 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -454,9 +484,9 @@ */ /**@{*/ // clang-format on -/// @brief Decimals places in string representation; the error code has 0. +/// @brief Decimal places in string representation; the error code has 0. #define ANB_PH_STATUS_CODE_RESOLUTION 0 -/// @brief Sensor variable number; error code is stored in sensorValues[4] +/// @brief Sensor variable number; error code is stored in sensorValues[7] #define ANB_PH_STATUS_CODE_VAR_NUM 7 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -474,6 +504,9 @@ /* clang-format off */ /** * @brief The Sensor sub-class for the [ANB pH sensors](@ref sensor_anb_ph) + * + * @note For the ANB pH sensor, the sensor::_measurementTime_ms is the time of the 2nd or subsequent reading. + * The time for the first reading after power on is variable and much longer. */ /* clang-format on */ class ANBpH : public Sensor { @@ -487,6 +520,11 @@ class ANBpH : public Sensor { * can be used. * @param powerPin The pin on the mcu controlling power to the ANB pH * sensor. Use -1 if it is continuously powered. + * @param loggingIntervalMinutes The logging interval in minutes. Even when + * the sensor is being powered off between readings, it needs to be told how + * often it will be powered on. This is not used if the sensor power is not + * being controlled by the mcu. Must be between 10 and 240 minutes when + * power is cycled; use 0 only for always‑powered mode. * @param powerPin2 The pin on the mcu controlling power to the RS485 * adapter, if it is different from that used to power the sensor. Use -1 * or omit if not applicable. @@ -497,21 +535,30 @@ class ANBpH : public Sensor { * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. - */ - ANBpH(byte modbusAddress, Stream* stream, int8_t powerPin, int8_t powerPin2, + * + * @warning This library does _**NOT**_ verify that the logging interval set + * for this sensor matches the logging interval of the logger. The actual + * power on/off and measurement times will be based on the logging interval + * of the logger, so if these are not the same, the timing of the + * measurements may be very different than expected. I do not understand + * why the sensor needs to know the logging interval when it is powered off, + * but it does. I suspect it uses this to balance power across the various + * sensing elements to maximize the life of the sensor. + */ + ANBpH(byte modbusAddress, Stream* stream, int8_t powerPin, + int16_t loggingIntervalMinutes, int8_t powerPin2 = -1, int8_t enablePin = -1, uint8_t measurementsToAverage = 1); - /// @copydoc ANBpH::ANBpH(byte, Stream*, int8_t, int8_t, int8_t, uint8_t) - ANBpH(byte modbusAddress, Stream& stream, int8_t powerPin, int8_t powerPin2, + /// @copydoc ANBpH::ANBpH(byte, Stream*, int8_t, int16_t, int8_t, int8_t, + /// uint8_t) + ANBpH(byte modbusAddress, Stream& stream, int8_t powerPin, + int16_t loggingIntervalMinutes, int8_t powerPin2 = -1, int8_t enablePin = -1, uint8_t measurementsToAverage = 1); /** * @brief Destroy the ANB pH object - no action taken */ - virtual ~ANBpH(); + ~ANBpH() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -524,38 +571,34 @@ class ANBpH : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - bool wake(void) override; - bool sleep(void) override; - bool startSingleMeasurement(void) override; - bool addSingleMeasurementResult(void) override; - - // Override these to use two power pins - void powerUp(void) override; - void powerDown(void) override; - + bool setup() override; /** - * @copydoc Sensor::isWarmedUp(bool debug) + * @brief Confirms that the sensor is giving a valid status code in response + * to modbus commands, re-sets the RTC, and starts measurements. * - * For the ANB pH sensor, this waits for both the power-on warm up and for a - * valid response from the sensor to a Modbus command. + * Unlike base Sensor::wake(), this starts measurements (scanning). ANB pH + * sensors have a built-in stabilization and referencing time before they + * report the first value so when we start scanning, we're starting the wait + * for stabilization and then must wait the (very long) "first measurement" + * time before requesting the first result. * - * @note The timing here is probably not very variable. + * @return True if the sensor started scanning. */ - bool isWarmedUp(bool debug = false) override; + bool wake() override; + bool sleep() override; + + bool addSingleMeasurementResult() override; /** - * @brief Check whether or not enough time has passed between the sensor - * responding to any modbus command to giving a valid status code - which - * indicates that it's ready to take a measurement. + * @copydoc Sensor::isWarmedUp(bool debug) * - * @param debug True to output the result to the debugging Serial - * @return True indicates that enough time has passed that the sensor is + * For the ANB pH sensor, this waits for both the power-on warm up and for a + * valid status code response from the sensor - which indicates that it's * ready to take a measurement. * * @note The timing here is probably not very variable. */ - bool isStable(bool debug = false) override; + bool isWarmedUp(bool debug = false) override; /** * @brief Check whether or not the pH sensor has completed a measurement. @@ -612,15 +655,17 @@ class ANBpH : public Sensor { * ANB pH sensor. */ Stream* _stream; + /** + * @brief The logging interval in minutes. Even when the sensor is being + * powered off between readings, it needs to be told how often it will be + * powered on. + */ + int16_t _loggingIntervalMinutes; /** * @brief Private reference to the RS-485 adapter's flow direction control * pin. */ int8_t _RS485EnablePin; - /** - * @brief Private reference to the power pin fro the RS-485 adapter. - */ - int8_t _powerPin2; /** * @brief Private reference to the salinity mode for the ANB pH sensor. * @remark The salinity mode is set to low salinity by default. @@ -655,36 +700,20 @@ class ANBpH : public Sensor { uint32_t spacing = ANB_PH_MINIMUM_REQUEST_SPACING, uint32_t startTime = 0); - /** - * @brief Get the start of the estimated time window before an immersion - * error is returned based on power cycling and the immersion sensor - * enablement. * - * @return The start of the estimated time window before an immersion error - * is returned. - */ - uint32_t getStartImmersionErrorWindow(void); - /** - * @brief Get the end of the estimated time window before an immersion - * error is returned based on power cycling and the immersion sensor - * enablement. - * @return The end of the estimated time window before an immersion error - * is returned. - */ - uint32_t getEndImmersionErrorWindow(void); /** * @brief Get the start of the estimated time window for a measurement to * complete based on the sensor's current configuration. * @return The start of the estimated time window for a measurement to * complete. */ - uint32_t getStartMeasurementWindow(void); + uint32_t getStartMeasurementWindow(); /** * @brief Get the end of the estimated time window for a measurement to * complete based on the sensor's current configuration. * @return The end of the estimated time window for a measurement to * complete. */ - uint32_t getEndMeasurementWindow(void); + uint32_t getEndMeasurementWindow(); /** * @brief Set the sensor's real time clock (RTC) to the current time. * @@ -696,7 +725,7 @@ class ANBpH : public Sensor { * * @return True if the RTC was successfully set, false if not. */ - bool setSensorRTC(void); + bool setSensorRTC(); }; @@ -719,23 +748,12 @@ class ANBpH_pH : public Variable { */ explicit ANBpH_pH(ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_PH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_PH_VAR_NUM, - (uint8_t)ANB_PH_PH_RESOLUTION, ANB_PH_PH_VAR_NAME, - ANB_PH_PH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ANBpH_pH object. - * - * @note This must be tied with a parent ANBpH before it can be - * used. - */ - ANBpH_pH() - : Variable((uint8_t)ANB_PH_PH_VAR_NUM, (uint8_t)ANB_PH_PH_RESOLUTION, - ANB_PH_PH_VAR_NAME, ANB_PH_PH_UNIT_NAME, - ANB_PH_PH_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_PH_VAR_NUM, ANB_PH_PH_RESOLUTION, + ANB_PH_PH_VAR_NAME, ANB_PH_PH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ANBpH_pH object - no action needed. */ - ~ANBpH_pH() {} + ~ANBpH_pH() override = default; }; /* clang-format off */ @@ -758,23 +776,13 @@ class ANBpH_Temp : public Variable { */ explicit ANBpH_Temp(ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_TEMP_VAR_NUM, - (uint8_t)ANB_PH_TEMP_RESOLUTION, ANB_PH_TEMP_VAR_NAME, - ANB_PH_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ANBpH_Temp object. - * - * @note This must be tied with a parent ANBpH before it can be - * used. - */ - ANBpH_Temp() - : Variable((uint8_t)ANB_PH_TEMP_VAR_NUM, - (uint8_t)ANB_PH_TEMP_RESOLUTION, ANB_PH_TEMP_VAR_NAME, - ANB_PH_TEMP_UNIT_NAME, ANB_PH_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_TEMP_VAR_NUM, ANB_PH_TEMP_RESOLUTION, + ANB_PH_TEMP_VAR_NAME, ANB_PH_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the ANBpH_Temp object - no action needed. */ - ~ANBpH_Temp() {} + ~ANBpH_Temp() override = default; }; /* clang-format off */ @@ -797,26 +805,14 @@ class ANBpH_Salinity : public Variable { */ explicit ANBpH_Salinity(ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_SALINITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_SALINITY_VAR_NUM, - (uint8_t)ANB_PH_SALINITY_RESOLUTION, - ANB_PH_SALINITY_VAR_NAME, ANB_PH_SALINITY_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new ANBpH_Salinity object. - * - * @note This must be tied with a parent ANBpH object before it can be - * used. - */ - ANBpH_Salinity() - : Variable((uint8_t)ANB_PH_SALINITY_VAR_NUM, - (uint8_t)ANB_PH_SALINITY_RESOLUTION, - ANB_PH_SALINITY_VAR_NAME, ANB_PH_SALINITY_UNIT_NAME, - ANB_PH_SALINITY_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_SALINITY_VAR_NUM, + ANB_PH_SALINITY_RESOLUTION, ANB_PH_SALINITY_VAR_NAME, + ANB_PH_SALINITY_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ANBpH_Salinity() object - no action * needed. */ - ~ANBpH_Salinity() {} + ~ANBpH_Salinity() override = default; }; @@ -840,22 +836,13 @@ class ANBpH_SpCond : public Variable { */ explicit ANBpH_SpCond(ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_SPCOND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_SPCOND_VAR_NUM, - (uint8_t)ANB_PH_SPCOND_RESOLUTION, ANB_PH_SPCOND_VAR_NAME, - ANB_PH_SPCOND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ANBpH_SpCond object. - * - * @note This must be tied with a parent ANBpH object before it can be used. - */ - ANBpH_SpCond() - : Variable((uint8_t)ANB_PH_SPCOND_VAR_NUM, - (uint8_t)ANB_PH_SPCOND_RESOLUTION, ANB_PH_SPCOND_VAR_NAME, - ANB_PH_SPCOND_UNIT_NAME, ANB_PH_SPCOND_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_SPCOND_VAR_NUM, ANB_PH_SPCOND_RESOLUTION, + ANB_PH_SPCOND_VAR_NAME, ANB_PH_SPCOND_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the ANBpH_SpCond object - no action needed. */ - ~ANBpH_SpCond() {} + ~ANBpH_SpCond() override = default; }; /* clang-format off */ @@ -878,23 +865,12 @@ class ANBpH_EC : public Variable { */ explicit ANBpH_EC(ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_EC_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_EC_VAR_NUM, - (uint8_t)ANB_PH_EC_RESOLUTION, ANB_PH_EC_VAR_NAME, - ANB_PH_EC_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ANBpH_EC object. - * - * @note This must be tied with a parent ANBpH object before it can be - * used. - */ - ANBpH_EC() - : Variable((uint8_t)ANB_PH_EC_VAR_NUM, (uint8_t)ANB_PH_EC_RESOLUTION, - ANB_PH_EC_VAR_NAME, ANB_PH_EC_UNIT_NAME, - ANB_PH_EC_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_EC_VAR_NUM, ANB_PH_EC_RESOLUTION, + ANB_PH_EC_VAR_NAME, ANB_PH_EC_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ANBpH_EC object - no action needed. */ - ~ANBpH_EC() {} + ~ANBpH_EC() override = default; }; @@ -920,26 +896,14 @@ class ANBpH_HealthCode : public Variable { explicit ANBpH_HealthCode( ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_HEALTH_CODE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_HEALTH_CODE_VAR_NUM, - (uint8_t)ANB_PH_HEALTH_CODE_RESOLUTION, - ANB_PH_HEALTH_CODE_VAR_NAME, ANB_PH_HEALTH_CODE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new ANBpH_HealthCode object. - * - * @note This must be tied with a parent ANBpH object before it can be - * used. - */ - ANBpH_HealthCode() - : Variable((uint8_t)ANB_PH_HEALTH_CODE_VAR_NUM, - (uint8_t)ANB_PH_HEALTH_CODE_RESOLUTION, - ANB_PH_HEALTH_CODE_VAR_NAME, ANB_PH_HEALTH_CODE_UNIT_NAME, - ANB_PH_HEALTH_CODE_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_HEALTH_CODE_VAR_NUM, + ANB_PH_HEALTH_CODE_RESOLUTION, ANB_PH_HEALTH_CODE_VAR_NAME, + ANB_PH_HEALTH_CODE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ANBpH_HealthCode object - no action * needed. */ - ~ANBpH_HealthCode() {} + ~ANBpH_HealthCode() override = default; }; @@ -965,27 +929,15 @@ class ANBpH_DiagnosticCode : public Variable { explicit ANBpH_DiagnosticCode( ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_DIAGNOSTIC_CODE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_DIAGNOSTIC_CODE_VAR_NUM, - (uint8_t)ANB_PH_DIAGNOSTIC_CODE_RESOLUTION, + : Variable(parentSense, ANB_PH_DIAGNOSTIC_CODE_VAR_NUM, + ANB_PH_DIAGNOSTIC_CODE_RESOLUTION, ANB_PH_DIAGNOSTIC_CODE_VAR_NAME, ANB_PH_DIAGNOSTIC_CODE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ANBpH_DiagnosticCode object. - * - * @note This must be tied with a parent ANBpH object before it can be - * used. - */ - ANBpH_DiagnosticCode() - : Variable((uint8_t)ANB_PH_DIAGNOSTIC_CODE_VAR_NUM, - (uint8_t)ANB_PH_DIAGNOSTIC_CODE_RESOLUTION, - ANB_PH_DIAGNOSTIC_CODE_VAR_NAME, - ANB_PH_DIAGNOSTIC_CODE_UNIT_NAME, - ANB_PH_DIAGNOSTIC_CODE_DEFAULT_CODE) {} /** * @brief Destroy the ANBpH_DiagnosticCode object - no action * needed. */ - ~ANBpH_DiagnosticCode() {} + ~ANBpH_DiagnosticCode() override = default; }; @@ -1011,28 +963,16 @@ class ANBpH_StatusCode : public Variable { explicit ANBpH_StatusCode( ANBpH* parentSense, const char* uuid = "", const char* varCode = ANB_PH_STATUS_CODE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANB_PH_STATUS_CODE_VAR_NUM, - (uint8_t)ANB_PH_STATUS_CODE_RESOLUTION, - ANB_PH_STATUS_CODE_VAR_NAME, ANB_PH_STATUS_CODE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new ANBpH_StatusCode object. - * - * @note This must be tied with a parent ANBpH object before it can be - * used. - */ - ANBpH_StatusCode() - : Variable((uint8_t)ANB_PH_STATUS_CODE_VAR_NUM, - (uint8_t)ANB_PH_STATUS_CODE_RESOLUTION, - ANB_PH_STATUS_CODE_VAR_NAME, ANB_PH_STATUS_CODE_UNIT_NAME, - ANB_PH_STATUS_CODE_DEFAULT_CODE) {} + : Variable(parentSense, ANB_PH_STATUS_CODE_VAR_NUM, + ANB_PH_STATUS_CODE_RESOLUTION, ANB_PH_STATUS_CODE_VAR_NAME, + ANB_PH_STATUS_CODE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ANBpH_StatusCode object - no action * needed. */ - ~ANBpH_StatusCode() {} + ~ANBpH_StatusCode() override = default; }; /**@}*/ #endif // SRC_SENSORS_ANB_SENSORS_PH_H_ -// cSpell:ignore millisiemenPerCentimeter ppth SPCOND +// cSpell:words millisiemenPerCentimeter ppth SPCOND diff --git a/src/sensors/AOSongAM2315.cpp b/src/sensors/AOSongAM2315.cpp index c28f13436..377b496ea 100644 --- a/src/sensors/AOSongAM2315.cpp +++ b/src/sensors/AOSongAM2315.cpp @@ -9,6 +9,7 @@ */ #include "AOSongAM2315.h" +#include // The constructor - because this is I2C, only need the power pin @@ -18,25 +19,18 @@ AOSongAM2315::AOSongAM2315(TwoWire* theI2C, int8_t powerPin, : Sensor("AOSongAM2315", AM2315_NUM_VARIABLES, AM2315_WARM_UP_TIME_MS, AM2315_STABILIZATION_TIME_MS, AM2315_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage), - _i2c(theI2C) { - am2315ptr = new Adafruit_AM2315(_i2c); -} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} +// Delegating constructor AOSongAM2315::AOSongAM2315(int8_t powerPin, uint8_t measurementsToAverage) - : Sensor("AOSongAM2315", AM2315_NUM_VARIABLES, AM2315_WARM_UP_TIME_MS, - AM2315_STABILIZATION_TIME_MS, AM2315_MEASUREMENT_TIME_MS, powerPin, - -1, measurementsToAverage, AM2315_INC_CALC_VARIABLES), - _i2c(&Wire) { - am2315ptr = new Adafruit_AM2315(_i2c); -} -AOSongAM2315::~AOSongAM2315() {} + : AOSongAM2315(&Wire, powerPin, measurementsToAverage) {} -String AOSongAM2315::getSensorLocation(void) { +String AOSongAM2315::getSensorLocation() { return F("I2C_0xB8"); } -bool AOSongAM2315::setup(void) { +bool AOSongAM2315::setup() { _i2c->begin(); // Start the wire library (sensor power not required) // Eliminate any potential extra waits in the wire library // These waits would be caused by a readBytes or parseX being called @@ -50,35 +44,34 @@ bool AOSongAM2315::setup(void) { } -bool AOSongAM2315::addSingleMeasurementResult(void) { - // Initialize float variables - float temp_val = -9999; - float humid_val = -9999; - bool ret_val = false; +bool AOSongAM2315::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + bool success = false; + float temp_val = MS_INVALID_VALUE; + float humid_val = MS_INVALID_VALUE; - ret_val = am2315ptr->readTemperatureAndHumidity(&temp_val, &humid_val); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - if (!ret_val || isnan(temp_val)) temp_val = -9999; - if (!ret_val || isnan(humid_val)) humid_val = -9999; - - MS_DBG(F(" Temp:"), temp_val, F("°C")); - MS_DBG(F(" Humidity:"), humid_val, '%'); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + Adafruit_AM2315 am2315(_i2c); + if (!am2315.begin()) { + MS_DBG(getSensorNameAndLocation(), F("AM2315 begin() failed")); + return finalizeMeasurementAttempt(false); } + success = am2315.readTemperatureAndHumidity(&temp_val, &humid_val); + - verifyAndAddMeasurementResult(AM2315_TEMP_VAR_NUM, temp_val); - verifyAndAddMeasurementResult(AM2315_HUMIDITY_VAR_NUM, humid_val); + success &= !isnan(temp_val) && !isnan(humid_val); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + MS_DBG(F(" Temp:"), temp_val, F("°C")); + MS_DBG(F(" Humidity:"), humid_val, '%'); + + if (success) { + verifyAndAddMeasurementResult(AM2315_TEMP_VAR_NUM, temp_val); + verifyAndAddMeasurementResult(AM2315_HUMIDITY_VAR_NUM, humid_val); + } - return ret_val; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/AOSongAM2315.h b/src/sensors/AOSongAM2315.h index 3fbf53cc3..dbaed603d 100644 --- a/src/sensors/AOSongAM2315.h +++ b/src/sensors/AOSongAM2315.h @@ -75,7 +75,7 @@ // Include other in-library and external dependencies #include "VariableBase.h" #include "SensorBase.h" -#include +#include /** @ingroup sensor_am2315 */ /**@{*/ @@ -118,7 +118,11 @@ * {{ @ref AOSongAM2315_Humidity::AOSongAM2315_Humidity }} */ /**@{*/ -/// @brief Decimals places in string representation; humidity should have 1 (0.1 +/// @brief Minimum humidity in percent relative humidity. +#define AM2315_HUMIDITY_MIN_RH 0 +/// @brief Maximum humidity in percent relative humidity. +#define AM2315_HUMIDITY_MAX_RH 100 +/// @brief Decimal places in string representation; humidity should have 1 (0.1 /// % RH for the 16 bit sensor). #define AM2315_HUMIDITY_RESOLUTION 1 /// @brief Sensor variable number; humidity is stored in sensorValues[0]. @@ -145,7 +149,11 @@ * {{ @ref AOSongAM2315_Temp::AOSongAM2315_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1. +/// @brief Minimum temperature in degrees Celsius. +#define AM2315_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define AM2315_TEMP_MAX_C 125.0 +/// @brief Decimal places in string representation; temperature should have 1. /// (0.1°C for the 16 bit sensor) #define AM2315_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -213,16 +221,25 @@ class AOSongAM2315 : public Sensor { */ explicit AOSongAM2315(int8_t powerPin, uint8_t measurementsToAverage = 1); /** - * @brief Destroy the AOSongAM2315 object - no action needed. + * @brief Destroy the AOSongAM2315 object */ - ~AOSongAM2315(); + ~AOSongAM2315() override = default; + + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + AOSongAM2315(const AOSongAM2315&) = delete; + AOSongAM2315& operator=(const AOSongAM2315&) = delete; + + // Delete move constructor and move assignment operator + AOSongAM2315(AOSongAM2315&&) = delete; + AOSongAM2315& operator=(AOSongAM2315&&) = delete; /** * @brief Report the I2C address of the AM2315 - which is always 0xB8. * * @return Text describing how the sensor is attached to the mcu. */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -235,22 +252,15 @@ class AOSongAM2315 : public Sensor { * @return True if the setup was successful. For the AOSong AM2315 * the result will always be true. */ - bool setup(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; private: /** * @brief An internal reference to the hardware Wire instance. */ TwoWire* _i2c; - /** - * @brief Internal reference to the Adafruit sensor class - */ - Adafruit_AM2315* am2315ptr; }; @@ -276,24 +286,13 @@ class AOSongAM2315_Humidity : public Variable { explicit AOSongAM2315_Humidity( AOSongAM2315* parentSense, const char* uuid = "", const char* varCode = AM2315_HUMIDITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)AM2315_HUMIDITY_VAR_NUM, - (uint8_t)AM2315_HUMIDITY_RESOLUTION, - AM2315_HUMIDITY_VAR_NAME, AM2315_HUMIDITY_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new AOSongAM2315_Humidity object. - * - * @note This must be tied with a parent AOSongAM2315 before it can be used. - */ - AOSongAM2315_Humidity() - : Variable((uint8_t)AM2315_HUMIDITY_VAR_NUM, - (uint8_t)AM2315_HUMIDITY_RESOLUTION, - AM2315_HUMIDITY_VAR_NAME, AM2315_HUMIDITY_UNIT_NAME, - AM2315_HUMIDITY_DEFAULT_CODE) {} + : Variable(parentSense, AM2315_HUMIDITY_VAR_NUM, + AM2315_HUMIDITY_RESOLUTION, AM2315_HUMIDITY_VAR_NAME, + AM2315_HUMIDITY_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AOSongAM2315_Humidity object - no action needed. */ - ~AOSongAM2315_Humidity() {} + ~AOSongAM2315_Humidity() override = default; }; @@ -318,22 +317,13 @@ class AOSongAM2315_Temp : public Variable { */ explicit AOSongAM2315_Temp(AOSongAM2315* parentSense, const char* uuid = "", const char* varCode = AM2315_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)AM2315_TEMP_VAR_NUM, - (uint8_t)AM2315_TEMP_RESOLUTION, AM2315_TEMP_VAR_NAME, - AM2315_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AOSongAM2315_Temp object. - * - * @note This must be tied with a parent AOSongAM2315 before it can be used. - */ - AOSongAM2315_Temp() - : Variable((uint8_t)AM2315_TEMP_VAR_NUM, - (uint8_t)AM2315_TEMP_RESOLUTION, AM2315_TEMP_VAR_NAME, - AM2315_TEMP_UNIT_NAME, AM2315_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, AM2315_TEMP_VAR_NUM, AM2315_TEMP_RESOLUTION, + AM2315_TEMP_VAR_NAME, AM2315_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the AOSongAM2315_Temp object - no action needed. */ - ~AOSongAM2315_Temp() {} + ~AOSongAM2315_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_AOSONGAM2315_H_ diff --git a/src/sensors/AOSongDHT.cpp b/src/sensors/AOSongDHT.cpp index 76886214e..8c984a8b2 100644 --- a/src/sensors/AOSongDHT.cpp +++ b/src/sensors/AOSongDHT.cpp @@ -18,20 +18,19 @@ AOSongDHT::AOSongDHT(int8_t powerPin, int8_t dataPin, const uint8_t type, DHT_STABILIZATION_TIME_MS, DHT_MEASUREMENT_TIME_MS, powerPin, dataPin, measurementsToAverage, DHT_INC_CALC_VARIABLES), dht_internal(dataPin, type), - _dhtType(type) {} - -// Destructor - does nothing. -AOSongDHT::~AOSongDHT() {} + _dhtType(type) { + setMaxRetries(DHT_DEFAULT_MEASUREMENT_RETRIES); +} -bool AOSongDHT::setup(void) { +bool AOSongDHT::setup() { dht_internal.begin(); // Start up the sensor (only sets pin modes, sensor // power not required) return Sensor::setup(); // this will set pin modes and the setup status bit } -String AOSongDHT::getSensorName(void) { +String AOSongDHT::getSensorName() { switch (_dhtType) { case 11: return "AOSongDHT11"; case 12: return "AOSongDHT12"; @@ -41,60 +40,37 @@ String AOSongDHT::getSensorName(void) { } -bool AOSongDHT::addSingleMeasurementResult(void) { - bool success = false; +bool AOSongDHT::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Initialize float variables - float humid_val = -9999; - float temp_val = -9999; - float hi_val = -9999; + bool success = false; + float humid_val = MS_INVALID_VALUE; + float temp_val = MS_INVALID_VALUE; + float hi_val = MS_INVALID_VALUE; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - // Reading temperature or humidity takes about 250 milliseconds! - // Make 5 attempts to get a decent reading - for (uint8_t i = 0; i < 5; i++) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // First read the humidity - humid_val = dht_internal.readHumidity(); - // Read temperature as Celsius (the default) - temp_val = dht_internal.readTemperature(); - // Check if any reads failed - // If they are NaN (not a number) then something went wrong - if (!isnan(humid_val) && !isnan(temp_val)) { - // Compute heat index in Celsius (isFahrenheit = false) - hi_val = dht_internal.computeHeatIndex(temp_val, humid_val, - false); - MS_DBG(F(" Temp:"), temp_val, F("°C")); - MS_DBG(F(" Humidity:"), humid_val, '%'); - MS_DBG(F(" Calculated Heat Index:"), hi_val, F("°C")); - success = true; - break; - } else { - if (i < 4) { - MS_DBG(F(" Failed to read from DHT sensor, Retrying...")); - delay(100); - } else { - MS_DBG(F(" Failed to read from DHT sensor!")); - if (isnan(humid_val)) humid_val = -9999; - if (isnan(temp_val)) temp_val = -9999; - } - } - } + // Reading temperature or humidity takes about 250 milliseconds! + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + // First read the humidity + humid_val = dht_internal.readHumidity(); + // Read temperature as Celsius (the default) + temp_val = dht_internal.readTemperature(); + // Check if any reads failed + // If they are NaN (not a number) then something went wrong + if (!isnan(humid_val) && !isnan(temp_val)) { + // Compute heat index in Celsius (isFahrenheit = false) + hi_val = dht_internal.computeHeatIndex(temp_val, humid_val, false); + MS_DBG(F(" Temp:"), temp_val, F("°C")); + MS_DBG(F(" Humidity:"), humid_val, '%'); + MS_DBG(F(" Calculated Heat Index:"), hi_val, F("°C")); + verifyAndAddMeasurementResult(DHT_TEMP_VAR_NUM, temp_val); + verifyAndAddMeasurementResult(DHT_HUMIDITY_VAR_NUM, humid_val); + verifyAndAddMeasurementResult(DHT_HI_VAR_NUM, hi_val); + success = true; } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F(" DHT read failed (NaN temperature/humidity).")); } - // Store the results in the sensorValues array - verifyAndAddMeasurementResult(DHT_TEMP_VAR_NUM, temp_val); - verifyAndAddMeasurementResult(DHT_HUMIDITY_VAR_NUM, humid_val); - verifyAndAddMeasurementResult(DHT_HI_VAR_NUM, hi_val); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/AOSongDHT.h b/src/sensors/AOSongDHT.h index 849eae226..adb20080c 100644 --- a/src/sensors/AOSongDHT.h +++ b/src/sensors/AOSongDHT.h @@ -45,6 +45,11 @@ * @section sensor_dht_datasheet Sensor Datasheet * [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/AOSong-DHT22-Datasheet.pdf) * + * @section sensor_dht_config_flags Build flags + * - `-D DHT_DEFAULT_MEASUREMENT_RETRIES=##` + * - used to set the default number of measurement retries for DHT sensors + * when communication errors occur + * * @section sensor_dht_ctor Sensor Constructor * {{ @ref AOSongDHT::AOSongDHT }} * @@ -102,6 +107,28 @@ static const uint8_t AM2301{21}; /**< AM2301 */ /** @ingroup sensor_dht */ /**@{*/ +/** + * @anchor sensor_dht_config + * @name Configuration Defines + * Define for the DHT measurement retry behavior. + */ +/**@{*/ +#if !defined(DHT_DEFAULT_MEASUREMENT_RETRIES) || defined(DOXYGEN) +/** + * @brief The default number of measurement retries for DHT sensors when + * communication errors occur. + * + * DHT sensors use a single-wire protocol that can be susceptible to timing + * issues and communication errors. This define sets the default number of + * retries when a measurement fails. + * + * @note The default value of 5 retries provides good reliability while + * preventing excessive delays in case of persistent communication issues. + */ +#define DHT_DEFAULT_MEASUREMENT_RETRIES 5 +#endif +/**@}*/ + /** * @anchor sensor_dht_var_counts * @name Sensor Variable Counts @@ -140,7 +167,11 @@ static const uint8_t AM2301{21}; /**< AM2301 */ * {{ @ref AOSongDHT_Humidity::AOSongDHT_Humidity }} */ /**@{*/ -/// @brief Decimals places in string representation; humidity should have 1 (0.1 +/// @brief Minimum humidity in percent relative humidity. +#define DHT_HUMIDITY_MIN_RH 0 +/// @brief Maximum humidity in percent relative humidity. +#define DHT_HUMIDITY_MAX_RH 100 +/// @brief Decimal places in string representation; humidity should have 1 (0.1 /// % RH for DHT22 and 1 % RH for DHT11) #define DHT_HUMIDITY_RESOLUTION 1 /// @brief Sensor variable number; humidity is stored in sensorValues[0]. @@ -167,7 +198,11 @@ static const uint8_t AM2301{21}; /**< AM2301 */ * {{ @ref AOSongDHT_Temp::AOSongDHT_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature in degrees Celsius. +#define DHT_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define DHT_TEMP_MAX_C 80.0 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define DHT_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -194,7 +229,7 @@ static const uint8_t AM2301{21}; /**< AM2301 */ * {{ @ref AOSongDHT_HI::AOSongDHT_HI }} */ /**@{*/ -/// @brief Decimals places in string representation; heat index should have 1 - +/// @brief Decimal places in string representation; heat index should have 1 - /// resolution is 0.1°C #define DHT_HI_RESOLUTION 1 /// @brief Sensor variable number; HI is stored in sensorValues[2]. @@ -242,22 +277,13 @@ class AOSongDHT : public Sensor { /** * @brief Destroy the AOSongDHT object - no action needed. */ - ~AOSongDHT(); + ~AOSongDHT() override = default; - /** - * @copydoc Sensor::setup() - */ - bool setup(void) override; + bool setup() override; - /** - * @copydoc Sensor::getSensorName() - */ - String getSensorName(void) override; + String getSensorName() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; private: DHT dht_internal; ///< Internal reference to the Adafruit DHT object @@ -286,22 +312,13 @@ class AOSongDHT_Humidity : public Variable { */ explicit AOSongDHT_Humidity(AOSongDHT* parentSense, const char* uuid = "", const char* varCode = DHT_HUMIDITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DHT_HUMIDITY_VAR_NUM, - (uint8_t)DHT_HUMIDITY_RESOLUTION, DHT_HUMIDITY_VAR_NAME, - DHT_HUMIDITY_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AOSongDHT_Humidity object. - * - * @note This must be tied with a parent AOSongDHT before it can be used. - */ - AOSongDHT_Humidity() - : Variable((uint8_t)DHT_HUMIDITY_VAR_NUM, - (uint8_t)DHT_HUMIDITY_RESOLUTION, DHT_HUMIDITY_VAR_NAME, - DHT_HUMIDITY_UNIT_NAME, DHT_HUMIDITY_DEFAULT_CODE) {} + : Variable(parentSense, DHT_HUMIDITY_VAR_NUM, DHT_HUMIDITY_RESOLUTION, + DHT_HUMIDITY_VAR_NAME, DHT_HUMIDITY_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the AOSongDHT_Humidity object - no action needed. */ - ~AOSongDHT_Humidity() {} + ~AOSongDHT_Humidity() override = default; }; @@ -327,22 +344,12 @@ class AOSongDHT_Temp : public Variable { */ explicit AOSongDHT_Temp(AOSongDHT* parentSense, const char* uuid = "", const char* varCode = DHT_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DHT_TEMP_VAR_NUM, - (uint8_t)DHT_TEMP_RESOLUTION, DHT_TEMP_VAR_NAME, - DHT_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AOSongDHT_Temp object. - * - * @note This must be tied with a parent AOSongDHT before it can be used. - */ - AOSongDHT_Temp() - : Variable((uint8_t)DHT_TEMP_VAR_NUM, (uint8_t)DHT_TEMP_RESOLUTION, - DHT_TEMP_VAR_NAME, DHT_TEMP_UNIT_NAME, - DHT_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, DHT_TEMP_VAR_NUM, DHT_TEMP_RESOLUTION, + DHT_TEMP_VAR_NAME, DHT_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AOSongDHT_Temp object - no action needed. */ - ~AOSongDHT_Temp() {} + ~AOSongDHT_Temp() override = default; }; @@ -368,23 +375,14 @@ class AOSongDHT_HI : public Variable { */ explicit AOSongDHT_HI(AOSongDHT* parentSense, const char* uuid = "", const char* varCode = DHT_HI_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DHT_HI_VAR_NUM, - (uint8_t)DHT_HI_RESOLUTION, DHT_HI_VAR_NAME, - DHT_HI_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AOSongDHT_HI object. - * - * @note This must be tied with a parent AOSongDHT before it can be used. - */ - AOSongDHT_HI() - : Variable((uint8_t)DHT_HI_VAR_NUM, (uint8_t)DHT_HI_RESOLUTION, - DHT_HI_VAR_NAME, DHT_HI_UNIT_NAME, DHT_HI_DEFAULT_CODE) {} + : Variable(parentSense, DHT_HI_VAR_NUM, DHT_HI_RESOLUTION, + DHT_HI_VAR_NAME, DHT_HI_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AOSongDHT_HI object - no action needed. */ - ~AOSongDHT_HI() {} + ~AOSongDHT_HI() override = default; }; /**@}*/ #endif // SRC_SENSORS_AOSONGDHT_H_ -// cSpell:ignore DHTHI +// cSpell:words DHTHI diff --git a/src/sensors/AlphasenseCO2.cpp b/src/sensors/AlphasenseCO2.cpp index 4269cf28d..96aef959e 100644 --- a/src/sensors/AlphasenseCO2.cpp +++ b/src/sensors/AlphasenseCO2.cpp @@ -12,110 +12,104 @@ #include "AlphasenseCO2.h" -#include +#include "TIADS1x15.h" // The constructor - need the power pin and the data pin -AlphasenseCO2::AlphasenseCO2(int8_t powerPin, aco2_adsDiffMux_t adsDiffMux, - uint8_t i2cAddress, uint8_t measurementsToAverage) +AlphasenseCO2::AlphasenseCO2(int8_t powerPin, int8_t analogChannel, + int8_t analogReferenceChannel, + uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("AlphasenseCO2", ALPHASENSE_CO2_NUM_VARIABLES, ALPHASENSE_CO2_WARM_UP_TIME_MS, ALPHASENSE_CO2_STABILIZATION_TIME_MS, - ALPHASENSE_CO2_MEASUREMENT_TIME_MS, powerPin, -1, + ALPHASENSE_CO2_MEASUREMENT_TIME_MS, powerPin, analogChannel, measurementsToAverage, ALPHASENSE_CO2_INC_CALC_VARIABLES), - _adsDiffMux(adsDiffMux), - _i2cAddress(i2cAddress) {} + + _analogReferenceChannel(analogReferenceChannel), + // If no analog voltage reader was provided, create a default one + _analogVoltageReader( + analogVoltageReader == nullptr + ? new TIADS1x15Reader(ALPHASENSE_CO2_VOLTAGE_MULTIPLIER) + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} // Destructor -AlphasenseCO2::~AlphasenseCO2() {} - - -String AlphasenseCO2::getSensorLocation(void) { -#ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); -#else - String sensorLocation = F("ADS1015_0x"); -#endif - sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("; differential between channels 2 and 3"); - return sensorLocation; +AlphasenseCO2::~AlphasenseCO2() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String AlphasenseCO2::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + return _analogVoltageReader->getAnalogLocation(_dataPin, + _analogReferenceChannel); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } } -bool AlphasenseCO2::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float co2Current = -9999; - float calibResult = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. -#ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version -#else - Adafruit_ADS1015 ads; // Use this for the 12-bit version -#endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - // Bump the gain up to 1x = +/- 4.096V range - // Sensor return range is 0-2.5V, but the next gain option is 2x which - // only allows up to 2.048V - ads.setGain(GAIN_ONE); - // Begin ADC - ads.begin(_i2cAddress); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the voltage differential across the two voltage pins - adcCounts = ads.readADC_Differential_2_3(); - // Convert ADC counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_Differential_2_3() converted to volts:"), - adcVoltage); - - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - // Skip results out of range - // Convert voltage to current (mA) - assuming a 250 Ohm resistor is - // in series - co2Current = (adcVoltage / 250) * 1000; - // Convert current to ppm (using a formula recommended by the sensor - // manufacturer) - calibResult = 312.5 * co2Current - 1250; - MS_DBG(F(" calibResult:"), calibResult); - } else { - // set invalid voltages back to -9999 - adcVoltage = -9999; +bool AlphasenseCO2::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); } } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); } - verifyAndAddMeasurementResult(ALPHASENSE_CO2_VAR_NUM, calibResult); - verifyAndAddMeasurementResult(ALPHASENSE_CO2_VOLTAGE_VAR_NUM, adcVoltage); + return sensorSetupSuccess && analogVoltageReaderSuccess; +} + - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); +bool AlphasenseCO2::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - return true; + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read differential voltage using the AnalogVoltageReader interface + bool success = _analogVoltageReader->readVoltageDifferential( + _dataPin, _analogReferenceChannel, adcVoltage); + + if (success) { + // Convert voltage to current (mA) - assuming a 250 Ohm resistor is in + // series + float co2Current = (adcVoltage / ALPHASENSE_CO2_SENSE_RESISTOR_OHM) * + 1000.0f; + MS_DBG(F(" co2Current:"), co2Current); + + // Convert current to ppm (using a formula recommended by the sensor + // manufacturer) + float calibResult = (ALPHASENSE_CO2_MFG_SCALE * co2Current) - + ALPHASENSE_CO2_MFG_OFFSET; + MS_DBG(F(" calibResult:"), calibResult); + verifyAndAddMeasurementResult(ALPHASENSE_CO2_VOLTAGE_VAR_NUM, + adcVoltage); + verifyAndAddMeasurementResult(ALPHASENSE_CO2_VAR_NUM, calibResult); } else { - return false; + MS_DBG(F(" Failed to read differential voltage from analog reader")); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/AlphasenseCO2.h b/src/sensors/AlphasenseCO2.h index 2d4de16b3..93ee307f8 100644 --- a/src/sensors/AlphasenseCO2.h +++ b/src/sensors/AlphasenseCO2.h @@ -15,8 +15,6 @@ * Carbon Dioxide (CO2) sensor. This library will almost certainly also work * with the Alphasense IRC-AT CO2 sensor (which uses a thermopile detector), * although the warmup and stabilization times might be different. - * - * This depends on the Adafruit ADS1X15 v2.x library. */ /* clang-format off */ /** @@ -53,10 +51,14 @@ * [Datasheet](https://www.alphasense.com/wp-content/uploads/2018/04/IRC-A1.pdf) * * @section sensor_alphasense_co2_flags Build flags - * - ```-D MS_USE_ADS1015``` - * - switches from the 16-bit ADS1115 to the 12 bit ADS1015 - * - ```-D ALPHASENSE_CO2_CALIBRATION_FACTOR=x``` - * - Changes the calibration factor from 1 to x + * - ```-D ALPHASENSE_CO2_SENSE_RESISTOR_OHM=x``` + * - Changes the sense resistor value from 250.0 ohms to x ohms + * - ```-D ALPHASENSE_CO2_MFG_SCALE=x``` + * - Changes the manufacturer scale factor from 312.5 ppm/mA to x ppm/mA + * - ```-D ALPHASENSE_CO2_MFG_OFFSET=x``` + * - Changes the manufacturer offset from 1250.0 ppm to x ppm + * - ```-D ALPHASENSE_CO2_VOLTAGE_MULTIPLIER=x``` + * - Changes the voltage multiplier from 1.0 to x * * @section sensor_alphasense_co2_ctor Sensor Constructor * {{ @ref AlphasenseCO2::AlphasenseCO2 }} @@ -93,48 +95,60 @@ #include "VariableBase.h" #include "SensorBase.h" +// Forward declaration +class AnalogVoltageReader; + /** @ingroup sensor_alphasense_co2 */ /**@{*/ /** - * @anchor sensor_alphasense_co2_var_counts - * @name Sensor Variable Counts - * The number of variables that can be returned by the Apogee SQ-212 + * @anchor sensor_alphasense_co2_config + * @name Configuration Defines + * Defines to set the calibration of the Alphasense CO2 sensor. */ /**@{*/ -/// @brief Sensor::_numReturnedValues; the Alphasense CO2 sensor can report 2 -/// values, raw voltage and calculated CO2. -#define ALPHASENSE_CO2_NUM_VARIABLES 2 -/// @brief Sensor::_incCalcValues; CO2 is calculated from the raw voltage. -#define ALPHASENSE_CO2_INC_CALC_VARIABLES 1 -/**@}*/ - +#if !defined(ALPHASENSE_CO2_SENSE_RESISTOR_OHM) || defined(DOXYGEN) /** - * @anchor sensor__alphasense_co2_config - * @name Configuration Defines - * Defines to set the calibration of the Alphasense CO2 sensor and the address - * of the ADD. + * @brief Sense resistor value in ohms for current conversion */ -/**@{*/ -#if !defined(ALPHASENSE_CO2_CALIBRATION_FACTOR) || defined(DOXYGEN) +#define ALPHASENSE_CO2_SENSE_RESISTOR_OHM 250.0f +#endif +// Compile-time validation of configuration constants +static_assert(ALPHASENSE_CO2_SENSE_RESISTOR_OHM > 0, + "Sense resistor value must be positive"); +#if !defined(ALPHASENSE_CO2_MFG_SCALE) || defined(DOXYGEN) +/** + * @brief Manufacturer scale factor for CO2 conversion (ppm/mA) + */ +#define ALPHASENSE_CO2_MFG_SCALE 312.5f +#endif +static_assert(ALPHASENSE_CO2_MFG_SCALE > 0, + "Manufacturer scale factor must be positive"); +#if !defined(ALPHASENSE_CO2_MFG_OFFSET) || defined(DOXYGEN) /** - * @brief The calibration factor between output in volts and CO2 - * (microeinsteinPerSquareMeterPerSecond) 1 µmol mˉ² sˉ¹ per mV (reciprocal of - * sensitivity) + * @brief Manufacturer offset for CO2 conversion (ppm) */ -#define ALPHASENSE_CO2_CALIBRATION_FACTOR 1 +#define ALPHASENSE_CO2_MFG_OFFSET 1250.0f #endif +#if !defined(ALPHASENSE_CO2_VOLTAGE_MULTIPLIER) || defined(DOXYGEN) /** - * @brief Enum for the pins used for differential voltages. + * @brief Voltage multiplier for direct voltage reading */ -typedef enum : uint16_t { - DIFF_MUX_0_1, ///< differential across pins 0 and 1 - DIFF_MUX_0_3, ///< differential across pins 0 and 3 - DIFF_MUX_1_3, ///< differential across pins 1 and 3 - DIFF_MUX_2_3 ///< differential across pins 2 and 3 -} aco2_adsDiffMux_t; -/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 +#define ALPHASENSE_CO2_VOLTAGE_MULTIPLIER 1.0f +#endif +/**@}*/ + +/** + * @anchor sensor_alphasense_co2_var_counts + * @name Sensor Variable Counts + * The number of variables that can be returned by the Alphasense CO2 sensor + */ +/**@{*/ +/// @brief Sensor::_numReturnedValues; the Alphasense CO2 sensor can report 2 +/// values, raw voltage and calculated CO2. +#define ALPHASENSE_CO2_NUM_VARIABLES 2 +/// @brief Sensor::_incCalcValues; CO2 is calculated from the raw voltage. +#define ALPHASENSE_CO2_INC_CALC_VARIABLES 1 /**@}*/ /** @@ -180,6 +194,10 @@ typedef enum : uint16_t { * {{ @ref AlphasenseCO2_CO2 }} */ /**@{*/ +/// @brief Minimum CO2 concentration in parts per million. +#define ALPHASENSE_CO2_MIN_PPM 0 +/// @brief Maximum CO2 concentration in parts per million. +#define ALPHASENSE_CO2_MAX_PPM 5000 /// Variable number; CO2 is stored in sensorValues[0]. #define ALPHASENSE_CO2_VAR_NUM 0 /// @brief Variable name in [ODM2 controlled @@ -192,11 +210,11 @@ typedef enum : uint16_t { /// @brief Default variable short code; "AlphasenseCO2ppm" #define ALPHASENSE_CO2_DEFAULT_CODE "AlphasenseCO2ppm" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; CO2 should have 0 when +/// @brief Decimal places in string representation; CO2 should have 0 when /// using an ADS1015. #define ALPHASENSE_CO2_RESOLUTION 0 #else -/// @brief Decimals places in string representation; CO2 should have 4 when +/// @brief Decimal places in string representation; CO2 should have 4 when /// using an ADS1115. #define ALPHASENSE_CO2_RESOLUTION 4 #endif @@ -206,7 +224,8 @@ typedef enum : uint16_t { * @anchor sensor_alphasense_co2_voltage * @name Voltage * The voltage variable from an Alphasense IRC-A1 CO2 - * - Range is 0 to 3.6V [when ADC is powered at 3.3V] + * - Range depends on the ADC and sense resistor, but should not be more than + * the maximum power supply voltage of 5V. * - Accuracy is ± 0.5% * - 16-bit ADC (ADS1115): < 0.25% (gain error), <0.25 LSB (offset error) * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): < 0.15% @@ -220,6 +239,10 @@ typedef enum : uint16_t { * {{ @ref AlphasenseCO2_Voltage }} */ /**@{*/ +/// @brief Minimum voltage in volts. +#define ALPHASENSE_CO2_VOLTAGE_MIN_V 0 +/// @brief Maximum voltage in volts. +#define ALPHASENSE_CO2_VOLTAGE_MAX_V 5 /// Variable number; voltage is stored in sensorValues[1]. #define ALPHASENSE_CO2_VOLTAGE_VAR_NUM 1 /// @brief Variable name in [ODM2 controlled @@ -231,11 +254,11 @@ typedef enum : uint16_t { /// @brief Default variable short code; "AlphasenseCO2Voltage" #define ALPHASENSE_CO2_VOLTAGE_DEFAULT_CODE "AlphasenseCO2Voltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1 when +/// @brief Decimal places in string representation; voltage should have 1 when /// used with an ADS1015. #define ALPHASENSE_CO2_VOLTAGE_RESOLUTION 1 #else -/// @brief Decimals places in string representation; voltage should have 4 when +/// @brief Decimal places in string representation; voltage should have 4 when /// used with an ADS1115. #define ALPHASENSE_CO2_VOLTAGE_RESOLUTION 4 #endif @@ -251,59 +274,77 @@ class AlphasenseCO2 : public Sensor { public: /** * @brief Construct a new Alphasense IRC-A1 CO2 object - need the power pin - * and the on the ADS1x15. Designed to read differential voltage between ads - * channels 2 and 3 + * and the analog data and reference channels. * - * @note ModularSensors only supports connecting the ADS1x15 to the primary - * hardware I2C instance defined in the Arduino core. Connecting the ADS to - * a secondary hardware or software I2C instance is *not* supported! + * By default, this constructor will internally create a default + * AnalogVoltageReader implementation for voltage readings, but a pointer to + * a custom AnalogVoltageReader object can be passed in if desired. * * @param powerPin The pin on the mcu controlling power to the * Alphasense CO2 sensor. Use -1 if it is continuously powered. * - The Alphasense CO2 sensor requires 2-5 V DC; current draw 20-60 mA - * - The ADS1115 requires 2.0-5.5V but is assumed to be powered at 3.3V - * @param adsDiffMux Which two pins _on the TI ADS1115_ that will measure - * differential voltage. See #aco2_adsDiffMux_t. - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) + * @param analogChannel The primary analog channel for differential + * measurement. Negative or invalid channel numbers or pairings between the + * analogChannel and analogReferenceChannel are not clamped and will cause + * the reading to fail and emit a warning. + * @param analogReferenceChannel The secondary (reference) analog channel + * for differential measurement. Negative or invalid channel numbers or + * pairings between the analogChannel and analogReferenceChannel are not + * clamped and will cause the reading to fail and emit a warning. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a - * default value of 7 [seconds], which is one period of the cycle. - * @note The ADS is expected to be either continuously powered or have - * its power controlled by the same pin as the Alphasense CO2 sensor. This - * library does not support any other configuration. + * default value of 7. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses a TI ADS1115 or ADS1015. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. + * + * @warning In library versions 0.37.0 and earlier, a different constructor + * was used that required an enum object instead of two different analog + * channel inputs for the differential voltage measurement. If you are using + * code from a previous version of the library, make sure to update your + * code to use the new constructor and provide the correct analog channel + * inputs for the differential voltage measurement. */ - AlphasenseCO2(int8_t powerPin, aco2_adsDiffMux_t adsDiffMux = DIFF_MUX_2_3, - uint8_t i2cAddress = ADS1115_ADDRESS, - uint8_t measurementsToAverage = 7); + AlphasenseCO2(int8_t powerPin, int8_t analogChannel, + int8_t analogReferenceChannel, + uint8_t measurementsToAverage = 7, + AnalogVoltageReader* analogVoltageReader = nullptr); /** - * @brief Destroy the AlphasenseCO2 object - no action needed + * @brief Destroy the AlphasenseCO2 object */ - ~AlphasenseCO2(); + ~AlphasenseCO2() override; - /** - * @brief Report the I1C address of the ADS and the channel that the - * Alphasense CO2 sensor is attached to. - * - * @return **String** Text describing how the sensor is attached to the mcu. - */ - String getSensorLocation(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + AlphasenseCO2(const AlphasenseCO2&) = delete; + AlphasenseCO2& operator=(const AlphasenseCO2&) = delete; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete move constructor and move assignment operator + AlphasenseCO2(AlphasenseCO2&&) = delete; + AlphasenseCO2& operator=(AlphasenseCO2&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: + /** - * @brief Which two pins _on the TI ADS1115_ that will measure differential - * voltage from the Turbidity Plus. See #aco2_adsDiffMux_t - */ - aco2_adsDiffMux_t _adsDiffMux; - /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 + * @brief The second (reference) pin for differential voltage measurements. + * + * @note The primary pin is stored as Sensor::_dataPin. */ - uint8_t _i2cAddress; + int8_t _analogReferenceChannel = -1; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -331,23 +372,13 @@ class AlphasenseCO2_CO2 : public Variable { explicit AlphasenseCO2_CO2( AlphasenseCO2* parentSense, const char* uuid = "", const char* varCode = ALPHASENSE_CO2_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ALPHASENSE_CO2_VAR_NUM, - (uint8_t)ALPHASENSE_CO2_RESOLUTION, ALPHASENSE_CO2_VAR_NAME, + : Variable(parentSense, ALPHASENSE_CO2_VAR_NUM, + ALPHASENSE_CO2_RESOLUTION, ALPHASENSE_CO2_VAR_NAME, ALPHASENSE_CO2_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AlphasenseCO2_CO2 object. - * - * @note This must be tied with a parent AlphasenseCO2 before it can be - * used. - */ - AlphasenseCO2_CO2() - : Variable((uint8_t)ALPHASENSE_CO2_VAR_NUM, - (uint8_t)ALPHASENSE_CO2_RESOLUTION, ALPHASENSE_CO2_VAR_NAME, - ALPHASENSE_CO2_UNIT_NAME, ALPHASENSE_CO2_DEFAULT_CODE) {} /** * @brief Destroy the AlphasenseCO2_CO2 object - no action needed. */ - ~AlphasenseCO2_CO2() {} + ~AlphasenseCO2_CO2() override = default; }; @@ -373,26 +404,14 @@ class AlphasenseCO2_Voltage : public Variable { explicit AlphasenseCO2_Voltage( AlphasenseCO2* parentSense, const char* uuid = "", const char* varCode = ALPHASENSE_CO2_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ALPHASENSE_CO2_VOLTAGE_VAR_NUM, - (uint8_t)ALPHASENSE_CO2_VOLTAGE_RESOLUTION, + : Variable(parentSense, ALPHASENSE_CO2_VOLTAGE_VAR_NUM, + ALPHASENSE_CO2_VOLTAGE_RESOLUTION, ALPHASENSE_CO2_VOLTAGE_VAR_NAME, ALPHASENSE_CO2_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AlphasenseCO2_Voltage object. - * - * @note This must be tied with a parent AlphasenseCO2 before it can be - * used. - */ - AlphasenseCO2_Voltage() - : Variable((uint8_t)ALPHASENSE_CO2_VOLTAGE_VAR_NUM, - (uint8_t)ALPHASENSE_CO2_VOLTAGE_RESOLUTION, - ALPHASENSE_CO2_VOLTAGE_VAR_NAME, - ALPHASENSE_CO2_VOLTAGE_UNIT_NAME, - ALPHASENSE_CO2_VOLTAGE_DEFAULT_CODE) {} /** * @brief Destroy the AlphasenseCO2_Voltage object - no action needed. */ - ~AlphasenseCO2_Voltage() {} + ~AlphasenseCO2_Voltage() override = default; }; /**@}*/ #endif // SRC_SENSORS_ALPHASENSECO2_H_ diff --git a/src/sensors/AnalogElecConductivity.cpp b/src/sensors/AnalogElecConductivity.cpp index 4b34ad709..fef8ed810 100644 --- a/src/sensors/AnalogElecConductivity.cpp +++ b/src/sensors/AnalogElecConductivity.cpp @@ -11,92 +11,134 @@ */ #include "AnalogElecConductivity.h" +#include "ProcessorAnalog.h" // For Mayfly version; the battery resistor depends on it -AnalogElecConductivity::AnalogElecConductivity(int8_t powerPin, int8_t dataPin, - float Rseries_ohms, - float sensorEC_Konst, - uint8_t measurementsToAverage) +AnalogElecConductivity::AnalogElecConductivity( + int8_t powerPin, int8_t dataPin, float Rseries_ohms, float sensorEC_Konst, + uint8_t measurementsToAverage, AnalogVoltageReader* analogVoltageReader) : Sensor("AnalogElecConductivity", ANALOGELECCONDUCTIVITY_NUM_VARIABLES, ANALOGELECCONDUCTIVITY_WARM_UP_TIME_MS, ANALOGELECCONDUCTIVITY_STABILIZATION_TIME_MS, ANALOGELECCONDUCTIVITY_MEASUREMENT_TIME_MS, powerPin, dataPin, measurementsToAverage, ANALOGELECCONDUCTIVITY_INC_CALC_VARIABLES), _Rseries_ohms(Rseries_ohms), - _sensorEC_Konst(sensorEC_Konst) {} -// Destructor -AnalogElecConductivity::~AnalogElecConductivity() {} + _sensorEC_Konst(sensorEC_Konst), + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new ProcessorAnalogReader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} -String AnalogElecConductivity::getSensorLocation(void) { - String sensorLocation = F("anlgEc Proc Data/Pwr"); - sensorLocation += String(_dataPin) + "/" + String(_powerPin); - return sensorLocation; +// Destructor +AnalogElecConductivity::~AnalogElecConductivity() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } } -float AnalogElecConductivity::readEC() { - return readEC(_dataPin); +String AnalogElecConductivity::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve(48); // Approximate expected size + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + sensorLocation = _analogVoltageReader->getAnalogLocation(_dataPin, -1); + } else { + sensorLocation = F("Unknown_AnalogVoltageReader"); + } + sensorLocation += F("_Pwr"); + sensorLocation += String(_powerPin); + return sensorLocation; } -float AnalogElecConductivity::readEC(uint8_t analogPinNum) { - uint32_t sensorEC_adc; - float Rwater_ohms; // literal value of water - float EC_uScm = -9999; // units are uS per cm +bool AnalogElecConductivity::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; - // First measure the analog voltage. - // The return value from analogRead() is IN BITS NOT IN VOLTS!! - // Take a priming reading. - // First reading will be low - discard - analogRead(analogPinNum); - // Take the reading we'll keep - sensorEC_adc = analogRead(analogPinNum); - MS_DEEP_DBG("adc bits=", sensorEC_adc); - - if (0 == sensorEC_adc) { - // Prevent underflow, can never be outside of PROCESSOR_ADC_RANGE - sensorEC_adc = 1; + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); + } + } else { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); } - // Estimate Resistance of Liquid - - // see the header for an explanation of this calculation - Rwater_ohms = _Rseries_ohms / - ((static_cast(PROCESSOR_ADC_RANGE) / - static_cast(sensorEC_adc)) - - 1); - MS_DEEP_DBG("ohms=", Rwater_ohms); - - // Convert to EC - EC_uScm = 1000000 / (Rwater_ohms * _sensorEC_Konst); - MS_DEEP_DBG("cond=", EC_uScm); - - return EC_uScm; + return sensorSetupSuccess && analogVoltageReaderSuccess; } -bool AnalogElecConductivity::addSingleMeasurementResult(void) { - float sensorEC_uScm = -9999; +bool AnalogElecConductivity::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - sensorEC_uScm = readEC(_dataPin); - MS_DBG(F("Water EC (uSm/cm)"), sensorEC_uScm); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); } - verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, - sensorEC_uScm); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + // Check if we have a resistance and cell constant + if (_Rseries_ohms <= 0 || _sensorEC_Konst <= 0) { + MS_DBG(getSensorNameAndLocation(), + F(" has an invalid cell constant or resistor value!")); + return finalizeMeasurementAttempt(false); + } - // Return true when finished - return true; + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the analog voltage using the AnalogVoltageReader interface + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + adcVoltage); + + if (success) { + // Estimate Resistance of Liquid + // see the header for an explanation of this calculation + // Convert voltage back to ADC equivalent for existing calculation + float supplyVoltage = _analogVoltageReader->getSupplyVoltage(); + if (supplyVoltage <= 0.0f) { + MS_DBG(F(" Invalid supply voltage from analog reader")); + return finalizeMeasurementAttempt(false); + } + float adcRatio = adcVoltage / supplyVoltage; + + if (adcRatio >= 1.0f) { + // Prevent division issues when voltage reaches supply voltage + MS_DBG(F(" ADC ratio clamped from"), adcRatio, F("to"), + ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO); + adcRatio = ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO; + } else if (adcRatio < 0.0f) { + MS_DBG(F(" Negative ADC ratio ("), adcRatio, + F("); negative supply or ADC voltage")); + return finalizeMeasurementAttempt(false); + } + + float Rwater_ohms = _Rseries_ohms * adcRatio / (1.0f - adcRatio); + MS_DBG(F(" Resistance:"), Rwater_ohms, F("ohms")); + + // Convert to EC + float EC_uScm = MS_INVALID_VALUE; // units are uS per cm + if (Rwater_ohms > 0.0f) { + EC_uScm = 1000000.0f / (Rwater_ohms * _sensorEC_Konst); + MS_DBG(F("Water EC (uS/cm):"), EC_uScm); + verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, + EC_uScm); + } else { + MS_DBG(F(" Invalid resistance; cannot calculate EC")); + success = false; + } + } else { + MS_DBG(F(" Failed to get valid voltage from analog reader")); + } + return finalizeMeasurementAttempt(success); } // cSpell:ignore AnalogElecConductivity Rseries_ohms sensorEC_Konst Rwater_ohms diff --git a/src/sensors/AnalogElecConductivity.h b/src/sensors/AnalogElecConductivity.h index ac2f1bde8..62715ceb1 100644 --- a/src/sensors/AnalogElecConductivity.h +++ b/src/sensors/AnalogElecConductivity.h @@ -116,6 +116,15 @@ * - `-D MS_PROCESSOR_ADC_REFERENCE_MODE=xxx` * - used to set the processor ADC value reference mode * - @see #MS_PROCESSOR_ADC_REFERENCE_MODE + * - `-D ANALOGELECCONDUCTIVITY_RSERIES_OHMS=###` + * - used to set the default resistance of the measuring resistor in ohms + * - @see #ANALOGELECCONDUCTIVITY_RSERIES_OHMS + * - `-D ANALOGELECCONDUCTIVITY_KONST=x.x` + * - used to set the cell constant for EC measurements + * - @see #ANALOGELECCONDUCTIVITY_KONST + * - `-D ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO=x.xxx` + * - used to set the maximum ADC ratio to prevent division by zero + * - @see #ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO * * @section sensor_analog_cond_ctor Sensor Constructor * {{ @ref AnalogElecConductivity::AnalogElecConductivity }} @@ -162,22 +171,6 @@ /** @ingroup sensor_analog_cond */ /**@{*/ -/** - * @anchor sensor_analog_cond_parts_var_counts - * @name Sensor Variable Counts - * The number of variables that can be returned by the analog conductivity - * sensor - */ -/**@{*/ -/// @brief Sensor::_numReturnedValues; we only get one value from the analog -/// conductivity sensor. -#define ANALOGELECCONDUCTIVITY_NUM_VARIABLES 1 -/// @brief Sensor::_incCalcValues; we don't calculate any additional values -/// though we recommend users include a temperature sensor and calculate -/// specific conductance in their own program. -#define ANALOGELECCONDUCTIVITY_INC_CALC_VARIABLES 0 -/**@}*/ - /** * @anchor sensor_analog_cond_parts_config * @name Configuration Defines @@ -185,15 +178,15 @@ * conductivity sensor depending on the processor and ADC in use. */ /**@{*/ -#if !defined(RSERIES_OHMS_DEF) || defined(DOXYGEN) +#if !defined(ANALOGELECCONDUCTIVITY_RSERIES_OHMS) || defined(DOXYGEN) /** * @brief The default resistance (in ohms) of the measuring resistor. * This should not be less than 300 ohms when measuring EC in water. */ -#define RSERIES_OHMS_DEF 499 -#endif // RSERIES_OHMS_DEF +#define ANALOGELECCONDUCTIVITY_RSERIES_OHMS 499.0f +#endif // ANALOGELECCONDUCTIVITY_RSERIES_OHMS -#if !defined(SENSOREC_KONST_DEF) || defined(DOXYGEN) +#if !defined(ANALOGELECCONDUCTIVITY_KONST) || defined(DOXYGEN) /** * @brief Cell Constant For EC Measurements. * @@ -205,8 +198,42 @@ * and fluid to get a better estimate for K. * Default to 1.0, and can be set at startup. */ -#define SENSOREC_KONST_DEF 1.0 -#endif // SENSOREC_KONST_DEF +#define ANALOGELECCONDUCTIVITY_KONST 1.0f +#endif // ANALOGELECCONDUCTIVITY_KONST + +#if !defined(ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO) || defined(DOXYGEN) +/** + * @brief Maximum ADC ratio to prevent division by zero in resistance + * calculation. + * + * This value clamps the ADC ratio when the measured voltage approaches the + * supply voltage, preventing division by zero in the water resistance + * calculation. Must be less than 1.0. + */ +#define ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO 0.999f +#endif // ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO + +// Compile-time validation of ADC maximum ratio constraint +static_assert( + ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO > 0.0f && + ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO < 1.0f, + "ANALOGELECCONDUCTIVITY_ADC_MAX_RATIO must be in the range (0.0, 1.0)"); +/**@}*/ + +/** + * @anchor sensor_analog_cond_parts_var_counts + * @name Sensor Variable Counts + * The number of variables that can be returned by the analog conductivity + * sensor + */ +/**@{*/ +/// @brief Sensor::_numReturnedValues; we only get one value from the analog +/// conductivity sensor. +#define ANALOGELECCONDUCTIVITY_NUM_VARIABLES 1 +/// @brief Sensor::_incCalcValues; we don't calculate any additional values +/// though we recommend users include a temperature sensor and calculate +/// specific conductance in their own program. +#define ANALOGELECCONDUCTIVITY_INC_CALC_VARIABLES 0 /**@}*/ /** @@ -237,11 +264,14 @@ * @name Electrical Conductance * The electrical conductance variable from a home-made analog sensor. * + * @todo Find and define minimum and maximum electrical conductivity + * measurement range + * * {{ @ref AnalogElecConductivity_EC::AnalogElecConductivity_EC }} */ /**@{*/ /** - * @brief Decimals places in string representation; EC should have 1 + * @brief Decimal places in string representation; EC should have 1 * * Range of 0-3V3 with 10bit ADC - resolution of 0.003 = 3 µS/cm. */ @@ -260,6 +290,9 @@ #define ANALOGELECCONDUCTIVITY_EC_DEFAULT_CODE "anlgEc" /**@}*/ +// Forward declaration +class AnalogVoltageReader; + /** * @brief Class for the analog [Electrical Conductivity monitor](@ref * sensor_analog_cond) @@ -276,36 +309,63 @@ class AnalogElecConductivity : public Sensor { * @param dataPin The processor ADC port pin to read the voltage from the EC * probe. Not all processor pins can be used as analog pins. Those usable * as analog pins generally are numbered with an "A" in front of the number - * - ie, A1. + * - i.e., A1. * @param Rseries_ohms The resistance of the resistor series (R) in the - * line; optional with default value of 499. + * line; optional with default value of + * #ANALOGELECCONDUCTIVITY_RSERIES_OHMS (499). * @param sensorEC_Konst The cell constant for the sensing circuit; optional - * with default value of 2.88 - which is what has been measured for a - * typical standard sized lamp-type plug. + * with default value of #ANALOGELECCONDUCTIVITY_KONST (1.0f) - this should + * be calibrated for each specific sensing setup. * @param measurementsToAverage The number of measurements to average; * optional with default value of 1. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses the processor's internal ADC. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. */ - AnalogElecConductivity(int8_t powerPin, int8_t dataPin, - float Rseries_ohms = RSERIES_OHMS_DEF, - float sensorEC_Konst = SENSOREC_KONST_DEF, - uint8_t measurementsToAverage = 1); + AnalogElecConductivity( + int8_t powerPin, int8_t dataPin, + float Rseries_ohms = ANALOGELECCONDUCTIVITY_RSERIES_OHMS, + float sensorEC_Konst = ANALOGELECCONDUCTIVITY_KONST, + uint8_t measurementsToAverage = 1, + AnalogVoltageReader* analogVoltageReader = nullptr); /** - * @brief Destroy the AnalogElecConductivity object - no action needed. + * @brief Destroy the AnalogElecConductivity object. + * + * Deletes the internal analog voltage reader if this object owns it. */ - ~AnalogElecConductivity(); + ~AnalogElecConductivity() override; + + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + AnalogElecConductivity(const AnalogElecConductivity&) = delete; + AnalogElecConductivity& operator=(const AnalogElecConductivity&) = delete; + + // Delete move constructor and move assignment operator + AnalogElecConductivity(AnalogElecConductivity&&) = delete; + AnalogElecConductivity& operator=(AnalogElecConductivity&&) = delete; /** * @brief Report the sensor info. * * @return Text describing how the sensor is attached to the mcu. */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** - * @copydoc Sensor::addSingleMeasurementResult() + * @brief Do any one-time preparations needed before the sensor will be able + * to take readings. + * + * This also initializes the internal analog voltage reader. + * + * @return True if the setup was successful. */ - bool addSingleMeasurementResult(void) override; + bool setup() override; + + bool addSingleMeasurementResult() override; /** * @brief Set EC constants for internal calculations. @@ -321,28 +381,18 @@ class AnalogElecConductivity : public Sensor { _Rseries_ohms = sourceResistance_ohms; } - /** - * @brief reads the calculated EC from an analog pin using the analog pin - * number set in the constructor. - * - * @return The electrical conductance value - */ - float readEC(void); - /** - * @brief reads the calculated EC from an analog pin. - * - * @param analogPinNum Analog port pin number - * @return The electrical conductance value - */ - float readEC(uint8_t analogPinNum); - private: /// @brief The resistance of the circuit resistor plus any series port /// resistance - float _Rseries_ohms = RSERIES_OHMS_DEF; + float _Rseries_ohms = ANALOGELECCONDUCTIVITY_RSERIES_OHMS; /// @brief the cell constant for the circuit - float _sensorEC_Konst = SENSOREC_KONST_DEF; + float _sensorEC_Konst = ANALOGELECCONDUCTIVITY_KONST; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; /** @@ -366,30 +416,19 @@ class AnalogElecConductivity_EC : public Variable { AnalogElecConductivity_EC( AnalogElecConductivity* parentSense, const char* uuid = "", const char* varCode = ANALOGELECCONDUCTIVITY_EC_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ANALOGELECCONDUCTIVITY_EC_VAR_NUM, - (uint8_t)ANALOGELECCONDUCTIVITY_EC_RESOLUTION, + : Variable(parentSense, ANALOGELECCONDUCTIVITY_EC_VAR_NUM, + ANALOGELECCONDUCTIVITY_EC_RESOLUTION, ANALOGELECCONDUCTIVITY_EC_VAR_NAME, ANALOGELECCONDUCTIVITY_EC_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AnalogElecConductivity_EC object. - * - * @note This must be tied with a parent AnalogElecConductivity before it - * can be used. - */ - AnalogElecConductivity_EC() - : Variable((uint8_t)ANALOGELECCONDUCTIVITY_EC_VAR_NUM, - (uint8_t)ANALOGELECCONDUCTIVITY_EC_RESOLUTION, - ANALOGELECCONDUCTIVITY_EC_VAR_NAME, - ANALOGELECCONDUCTIVITY_EC_UNIT_NAME, - ANALOGELECCONDUCTIVITY_EC_DEFAULT_CODE) {} + /** * @brief Destroy the AnalogElecConductivity_EC object - no action needed. */ - ~AnalogElecConductivity_EC() {} + ~AnalogElecConductivity_EC() override = default; }; /**@}*/ #endif // SRC_SENSORS_ANALOGELECCONDUCTIVITY_H_ -// cSpell:ignore AnalogElecConductivity Rseries_ohms sensorEC_Konst Rwater -// cSpell:ignore _elec_ _Konst anlgEc +// cSpell:words AnalogElecConductivity Rseries_ohms sensorEC_Konst Rwater +// cSpell:words _elec_ _Konst anlgEc diff --git a/src/sensors/AnalogVoltageReader.h b/src/sensors/AnalogVoltageReader.h new file mode 100644 index 000000000..ce402f81d --- /dev/null +++ b/src/sensors/AnalogVoltageReader.h @@ -0,0 +1,227 @@ +/** + * @file AnalogVoltageReader.h + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * + * @brief Contains the AnalogVoltageReader abstract class for providing a + * unified interface for analog voltage reading sensors. + * + * This abstract base class provides a common interface for sensors that can + * read single-ended analog voltages, either through external ADCs (like TI + * ADS1x15) or built-in processor ADCs. + */ + +// Header Guards +#ifndef SRC_SENSORS_ANALOGVOLTAGEREADER_H_ +#define SRC_SENSORS_ANALOGVOLTAGEREADER_H_ + +// Include the library config before anything else +#include "ModSensorConfig.h" + +// Include the debugging config +#include "ModSensorDebugConfig.h" + +// Define the print label[s] for the debugger +#ifdef MS_ANALOGVOLTAGEREADER_DEBUG +#define MS_DEBUGGING_STD "AnalogVoltageReader" +#endif + +// Include the debugger +#include "ModSensorDebugger.h" +// Undefine the debugger label[s] +#undef MS_DEBUGGING_STD + +/** + * @brief Abstract base class for analog voltage reading systems. + * + * This class provides a unified interface for analog voltage reading, + * whether from external ADCs (like TI ADS1x15) or processor built-in ADCs. + * Classes that inherit from this base must implement five pure virtual + * methods: + * - begin() for hardware initialization, + * - readVoltageSingleEnded() for single-ended voltage measurements, + * - readVoltageDifferential() for differential voltage measurements, + * - getAnalogLocation() to provide sensor location identification, and + * - calculateAnalogResolutionVolts() to compute voltage resolution. + */ +class AnalogVoltageReader { + public: + /** + * @brief Construct a new AnalogVoltageReader object + * + * @param voltageMultiplier The voltage multiplier for any voltage dividers + * @param supplyVoltage The supply/operating voltage for the analog system + */ + explicit AnalogVoltageReader(float voltageMultiplier = 1.0f, + float supplyVoltage = OPERATING_VOLTAGE) + : // NOTE: These clamps are intentionally silent — Serial/MS_DBG is NOT + // safe to call during construction (the Serial object may not be + // initialized yet on Arduino targets). Use setSupplyVoltage() at + // runtime for logged clamping. + _voltageMultiplier((voltageMultiplier > 0.0f) ? voltageMultiplier + : 1.0f), + _supplyVoltage((supplyVoltage > 0.0f) ? supplyVoltage + : OPERATING_VOLTAGE) {} + /** + * @brief Destroy the AnalogVoltageReader object + */ + virtual ~AnalogVoltageReader() = default; + + /** + * @brief Initialize the analog voltage reading system + * + * This pure virtual function must be implemented by derived classes to + * perform any initialization that cannot be safely done in the constructor. + * This includes hardware setup that depends on other systems being + * initialized, Serial communication being available, or operations that + * might need to communicate with external devices. + * + * @return True if the initialization was successful, false otherwise + */ + virtual bool begin() = 0; + + /** + * @brief Set the voltage multiplier for voltage divider calculations + * + * @param voltageMultiplier The multiplier value for voltage scaling + * + * @note The multiplier must be positive (> 0). Values <= 0 will be + * set to 1 to prevent division-by-zero errors and maintain valid voltage + * calculations. + */ + virtual void setVoltageMultiplier(float voltageMultiplier) { + // If the input voltage multiplier is not positive, set it to 1. + if (!(voltageMultiplier > 0.0f)) { // rejects NaN, -inf, and <= 0 + MS_DBG(F("Invalid voltage multiplier "), voltageMultiplier, + F(", clamping to 1.0")); + _voltageMultiplier = 1.0f; + } else { + _voltageMultiplier = voltageMultiplier; + } + } + + /** + * @brief Get the voltage multiplier value + * + * @return The current voltage multiplier + */ + float getVoltageMultiplier() const { + return _voltageMultiplier; + } + + /** + * @brief Set the supply voltage for the analog system + * + * @param supplyVoltage The supply voltage in volts + * + * @note The supply voltage must be positive (> 0). Values <= 0 will be + * clamped to OPERATING_VOLTAGE to maintain a valid reference. + */ + virtual void setSupplyVoltage(float supplyVoltage) { + if (!(supplyVoltage > 0.0f)) { // rejects NaN, -inf, and <= 0 + MS_DBG(F("Invalid supply voltage "), supplyVoltage, + F(", clamping to "), OPERATING_VOLTAGE, F("V")); + _supplyVoltage = OPERATING_VOLTAGE; + } else { + _supplyVoltage = supplyVoltage; + } + } + + /** + * @brief Get the supply voltage for the analog system + * + * @return The current supply voltage in volts + */ + float getSupplyVoltage() const { + return _supplyVoltage; + } + + /** + * @brief Read a single-ended voltage measurement + * + * This pure virtual function must be implemented by derived classes to + * provide their specific method of reading analog voltages. + * + * @param analogChannel The analog channel/pin for voltage readings. + * Negative or invalid channel numbers are not clamped and will cause the + * reading to fail and emit a warning. + * @param resultValue Reference to store the resulting voltage measurement + * @return True if the voltage reading was successful and within valid range + */ + virtual bool readVoltageSingleEnded(int8_t analogChannel, + float& resultValue) = 0; + + /** + * @brief Read a differential voltage measurement + * + * This pure virtual function must be implemented by derived classes to + * provide their specific method of reading differential voltages. + * + * If the sensor does not support differential measurements, this function + * should set the resultValue to #MS_INVALID_VALUE and return false. + * + * @param analogChannel The primary analog channel for differential + * measurement. Negative or invalid channel numbers or pairings between the + * analogChannel and analogReferenceChannel are not clamped and will cause + * the reading to fail and emit a warning. + * @param analogReferenceChannel The secondary (reference) analog channel + * for differential measurement. Negative or invalid channel numbers or + * pairings between the analogChannel and analogReferenceChannel are not + * clamped and will cause the reading to fail and emit a warning. + * @param resultValue Reference to store the resulting voltage measurement + * @return True if the voltage reading was successful and within valid range + */ + virtual bool readVoltageDifferential(int8_t analogChannel, + int8_t analogReferenceChannel, + float& resultValue) = 0; + + /** + * @brief Construct a String describing the analog sensor location from the + * input channels. + * + * This pure virtual function must be implemented by derived classes to + * provide their specific sensor location identification string. + * + * @param analogChannel The primary analog channel (primary channel for + * differential measurements, or the sole channel for single-ended + * measurements). + * @param analogReferenceChannel The secondary (reference) analog channel + * for differential measurement. Set to -1 for a single-ended analog sensor. + * + * @return A string describing the analog sensor location + */ + virtual String getAnalogLocation(int8_t analogChannel, + int8_t analogReferenceChannel) = 0; + + /** + * @brief Calculate the analog resolution in volts based on the ADC + * characteristics + * + * This pure virtual function must be implemented by derived classes to + * provide their specific analog resolution calculation based on their ADC + * characteristics (bit resolution, gain settings, supply voltage, etc.). + * + * @return The analog resolution in volts per LSB + */ + virtual float calculateAnalogResolutionVolts() = 0; + + protected: + /** + * @brief Stored voltage multiplier value + * + * Multiplier to apply for voltage divider calculations + */ + float _voltageMultiplier; + + /** + * @brief Stored supply voltage value + * + * For TIADS1x15: the ADS supply voltage + * For ProcessorAnalog: the processor operating voltage + */ + float _supplyVoltage; +}; + +#endif // SRC_SENSORS_ANALOGVOLTAGEREADER_H_ diff --git a/src/sensors/ApogeeSQ212.cpp b/src/sensors/ApogeeSQ212.cpp index d013f297c..eab6ae331 100644 --- a/src/sensors/ApogeeSQ212.cpp +++ b/src/sensors/ApogeeSQ212.cpp @@ -13,104 +13,98 @@ #include "ApogeeSQ212.h" -#include +#include "TIADS1x15.h" // The constructor - need the power pin and the data pin -ApogeeSQ212::ApogeeSQ212(int8_t powerPin, uint8_t adsChannel, - uint8_t i2cAddress, uint8_t measurementsToAverage) +ApogeeSQ212::ApogeeSQ212(int8_t powerPin, int8_t analogChannel, + uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("ApogeeSQ212", SQ212_NUM_VARIABLES, SQ212_WARM_UP_TIME_MS, SQ212_STABILIZATION_TIME_MS, SQ212_MEASUREMENT_TIME_MS, powerPin, - -1, measurementsToAverage, SQ212_INC_CALC_VARIABLES), - _adsChannel(adsChannel), - _i2cAddress(i2cAddress) {} + analogChannel, measurementsToAverage, SQ212_INC_CALC_VARIABLES), + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new TIADS1x15Reader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} // Destructor -ApogeeSQ212::~ApogeeSQ212() {} - - -String ApogeeSQ212::getSensorLocation(void) { -#ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); -#else - String sensorLocation = F("ADS1015_0x"); -#endif - sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("_Channel"); - sensorLocation += String(_adsChannel); - return sensorLocation; +ApogeeSQ212::~ApogeeSQ212() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String ApogeeSQ212::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + return _analogVoltageReader->getAnalogLocation(_dataPin, -1); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } } -bool ApogeeSQ212::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float calibResult = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. -#ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version -#else - Adafruit_ADS1015 ads; // Use this for the 12-bit version -#endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - // Bump the gain up to 1x = +/- 4.096V range - // Sensor return range is 0-2.5V, but the next gain option is 2x which - // only allows up to 2.048V - ads.setGain(GAIN_ONE); - // Begin ADC - ads.begin(_i2cAddress); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the ADC raw count - adcCounts = ads.readADC_SingleEnded(_adsChannel); - // Convert ADC raw counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_SingleEnded("), _adsChannel, F("):"), - adcVoltage); - - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - // Skip results out of range - // Apogee SQ-212 Calibration Factor = 1.0 μmol m-2 s-1 per mV - calibResult = 1000 * adcVoltage * SQ212_CALIBRATION_FACTOR; - MS_DBG(F(" calibResult:"), calibResult); - } else { - // set invalid voltages back to -9999 - adcVoltage = -9999; +bool ApogeeSQ212::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); } } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); } - verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult); - verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage); + return sensorSetupSuccess && analogVoltageReaderSuccess; +} + - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); +bool ApogeeSQ212::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - return true; + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the single-ended analog voltage using the AnalogVoltageReader + // interface. + // NOTE: All implementations of the AnalogVoltageReader class validate both + // the input channel and the resulting voltage, so we can trust that a + // successful read will give us a valid voltage value to work with. + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + adcVoltage); + + if (success) { + // Apply the calibration factor. + // The Apogee SQ-212 is factory calibrated with a calibration factor + // of 1.0 μmol m-2 s-1 per mV. If the user wants to use a custom value, + // it must be set as a preprocessor definition when compiling the + // library, e.g. by adding -DSQ212_CALIBRATION_FACTOR=0.95 to the + // compiler flags. + float calibResult = 1000.0f * adcVoltage * SQ212_CALIBRATION_FACTOR; + MS_DBG(F(" calibResult:"), calibResult); + verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult); + verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage); } else { - return false; + MS_DBG(F(" Failed to get valid voltage from analog reader")); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/ApogeeSQ212.h b/src/sensors/ApogeeSQ212.h index 4da555419..f3bd9046b 100644 --- a/src/sensors/ApogeeSQ212.h +++ b/src/sensors/ApogeeSQ212.h @@ -12,8 +12,6 @@ * ApogeeSQ212_PAR and ApogeeSQ212_Voltage. * * These are used for the Apogee SQ-212 quantum light sensor. - * - * This depends on the Adafruit ADS1X15 v2.x library. */ /* clang-format off */ /** @@ -56,8 +54,6 @@ * SQ-212-215 Manual.pdf) * * @section sensor_sq212_flags Build flags - * - ```-D MS_USE_ADS1015``` - * - switches from the 16-bit ADS1115 to the 12 bit ADS1015 * - ```-D SQ212_CALIBRATION_FACTOR=x``` * - Changes the calibration factor from 1 to x * @@ -96,22 +92,12 @@ #include "VariableBase.h" #include "SensorBase.h" +// Forward declaration +class AnalogVoltageReader; + /** @ingroup sensor_sq212 */ /**@{*/ -/** - * @anchor sensor_sq212_var_counts - * @name Sensor Variable Counts - * The number of variables that can be returned by the Apogee SQ-212 - */ -/**@{*/ -/// @brief Sensor::_numReturnedValues; the SQ212 can report 2 values, raw -/// voltage and calculated PAR. -#define SQ212_NUM_VARIABLES 2 -/// @brief Sensor::_incCalcValues; PAR is calculated from the raw voltage. -#define SQ212_INC_CALC_VARIABLES 1 -/**@}*/ - /** * @anchor sensor_sq212_config * @name Configuration Defines @@ -126,9 +112,19 @@ */ #define SQ212_CALIBRATION_FACTOR 1 #endif +/**@}*/ -/// The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 +/** + * @anchor sensor_sq212_var_counts + * @name Sensor Variable Counts + * The number of variables that can be returned by the Apogee SQ-212 + */ +/**@{*/ +/// @brief Sensor::_numReturnedValues; the SQ212 can report 2 values, raw +/// voltage and calculated PAR. +#define SQ212_NUM_VARIABLES 2 +/// @brief Sensor::_incCalcValues; PAR is calculated from the raw voltage. +#define SQ212_INC_CALC_VARIABLES 1 /**@}*/ /** @@ -145,15 +141,16 @@ */ #define SQ212_WARM_UP_TIME_MS 2 /** - * @brief Sensor::_stabilizationTime_ms; the ADS1115 is stable after 2ms. + * @brief Sensor::_stabilizationTime_ms; the default ADS1115 voltage reader is + * stable after 2ms. * * The stabilization time of the SQ-212 itself is not known! * * @todo Measure stabilization time of the SQ-212 */ #define SQ212_STABILIZATION_TIME_MS 2 -/// @brief Sensor::_measurementTime_ms; ADS1115 takes almost 2ms to complete a -/// measurement (860/sec). +/// @brief Sensor::_measurementTime_ms; with the default ADS1115 voltage reader, +/// it takes almost 2ms to complete a measurement (860/sec). #define SQ212_MEASUREMENT_TIME_MS 2 /**@}*/ @@ -174,14 +171,18 @@ */ /**@{*/ #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; PAR should have 0 when +/// @brief Decimal places in string representation; PAR should have 0 when /// using an ADS1015. #define SQ212_PAR_RESOLUTION 0 #else -/// @brief Decimals places in string representation; PAR should have 4 when +/// @brief Decimal places in string representation; PAR should have 4 when /// using an ADS1115. #define SQ212_PAR_RESOLUTION 4 #endif +/// @brief Minimum PAR value in microeinsteins per square meter per second. +#define SQ212_PAR_MIN 0 +/// @brief Maximum PAR value in microeinsteins per square meter per second. +#define SQ212_PAR_MAX 2500 /// Variable number; PAR is stored in sensorValues[0]. #define SQ212_PAR_VAR_NUM 0 /// @brief Variable name in [ODM2 controlled @@ -200,7 +201,7 @@ * @anchor sensor_sq212_voltage * @name Voltage * The voltage variable from an Apogee SQ-212 - * - Range is 0 to 3.6V [when ADC is powered at 3.3V] + * - Range is 0 to 2.5V for SQ-212/SQ-222, 0 to 5V for SQ-215/SQ-225 * - Accuracy is ± 0.5% * - 16-bit ADC (ADS1115): < 0.25% (gain error), <0.25 LSB (offset error) * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): < 0.15% @@ -216,6 +217,10 @@ /**@{*/ /// Variable number; voltage is stored in sensorValues[1]. #define SQ212_VOLTAGE_VAR_NUM 1 +/// @brief Minimum voltage in volts. +#define SQ212_VOLTAGE_MIN_V 0 +/// @brief Maximum voltage in volts. +#define SQ212_VOLTAGE_MAX_V 5.0 /// @brief Variable name in [ODM2 controlled /// vocabulary](http://vocabulary.odm2.org/variablename/); "voltage" #define SQ212_VOLTAGE_VAR_NAME "voltage" @@ -225,11 +230,11 @@ /// @brief Default variable short code; "SQ212Voltage" #define SQ212_VOLTAGE_DEFAULT_CODE "SQ212Voltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1 when +/// @brief Decimal places in string representation; voltage should have 1 when /// used with an ADS1015. #define SQ212_VOLTAGE_RESOLUTION 1 #else -/// @brief Decimals places in string representation; voltage should have 4 when +/// @brief Decimal places in string representation; voltage should have 4 when /// used with an ADS1115. #define SQ212_VOLTAGE_RESOLUTION 4 #endif @@ -244,57 +249,71 @@ class ApogeeSQ212 : public Sensor { public: /** * @brief Construct a new Apogee SQ-212 object - need the power pin and the - * data channel on the ADS1x15. + * analog data channel. * - * @note ModularSensors only supports connecting the ADS1x15 to the primary - * hardware I2C instance defined in the Arduino core. Connecting the ADS to - * a secondary hardware or software I2C instance is *not* supported! + * By default, this constructor will internally create a default + * AnalogVoltageReader implementation for voltage readings, but a pointer to + * a custom AnalogVoltageReader object can be passed in if desired. * * @param powerPin The pin on the mcu controlling power to the Apogee * SQ-212. Use -1 if it is continuously powered. * - The SQ-212 requires 3.3 to 24 V DC; current draw 10 µA - * - The ADS1115 requires 2.0-5.5V but is assumed to be powered at 3.3V - * @param adsChannel The analog data channel the Apogee SQ-212 is connected - * to _on the TI ADS1115_ (0-3). - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) + * @param analogChannel The analog data channel or processor pin for voltage + * measurements. The significance of the channel number depends on the + * specific AnalogVoltageReader implementation used for voltage readings. + * For example, with the TI ADS1x15, this would be the ADC channel (0-3) + * that the sensor is connected to. Negative or invalid channel numbers are + * not clamped and will cause the reading to fail and emit a warning. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. - * @note The ADS is expected to be either continuously powered or have - * its power controlled by the same pin as the SQ-212. This library does - * not support any other configuration. - */ - ApogeeSQ212(int8_t powerPin, uint8_t adsChannel, - uint8_t i2cAddress = ADS1115_ADDRESS, - uint8_t measurementsToAverage = 1); - /** - * @brief Destroy the ApogeeSQ212 object - no action needed + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses a TI ADS1115 or ADS1015. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. + * + * @warning In library versions 0.37.0 and earlier, a different constructor + * was used that the I2C address of the ADS1x15 was an optional input + * parameter which came *before* the optional input parameter for the number + * of measurements to average. The input parameter for the I2C address has + * been *removed* and the input for the number of measurements to average + * has been moved up in the order! Please update your code to prevent a + * compiler error or a silent reading error. */ - ~ApogeeSQ212(); - + ApogeeSQ212(int8_t powerPin, int8_t analogChannel, + uint8_t measurementsToAverage = 1, + AnalogVoltageReader* analogVoltageReader = nullptr); /** - * @brief Report the I1C address of the ADS and the channel that the SQ-212 - * is attached to. + * @brief Destroy the ApogeeSQ212 object * - * @return Text describing how the sensor is attached to the mcu. + * If the object owns the analog voltage reader (i.e., it was internally + * created), the reader is deleted. */ - String getSensorLocation(void) override; + ~ApogeeSQ212() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + ApogeeSQ212(const ApogeeSQ212&) = delete; + ApogeeSQ212& operator=(const ApogeeSQ212&) = delete; + + // Delete move constructor and move assignment operator + ApogeeSQ212(ApogeeSQ212&&) = delete; + ApogeeSQ212& operator=(ApogeeSQ212&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: - /** - * @brief Internal reference to the ADS channel number of the Apogee SQ-212 - */ - uint8_t _adsChannel; - /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 - */ - uint8_t _i2cAddress; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -321,22 +340,12 @@ class ApogeeSQ212_PAR : public Variable { */ explicit ApogeeSQ212_PAR(ApogeeSQ212* parentSense, const char* uuid = "", const char* varCode = SQ212_PAR_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)SQ212_PAR_VAR_NUM, - (uint8_t)SQ212_PAR_RESOLUTION, SQ212_PAR_VAR_NAME, - SQ212_PAR_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ApogeeSQ212_PAR object. - * - * @note This must be tied with a parent ApogeeSQ212 before it can be used. - */ - ApogeeSQ212_PAR() - : Variable((uint8_t)SQ212_PAR_VAR_NUM, (uint8_t)SQ212_PAR_RESOLUTION, - SQ212_PAR_VAR_NAME, SQ212_PAR_UNIT_NAME, - SQ212_PAR_DEFAULT_CODE) {} + : Variable(parentSense, SQ212_PAR_VAR_NUM, SQ212_PAR_RESOLUTION, + SQ212_PAR_VAR_NAME, SQ212_PAR_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ApogeeSQ212_PAR object - no action needed. */ - ~ApogeeSQ212_PAR() {} + ~ApogeeSQ212_PAR() override = default; }; @@ -364,22 +373,13 @@ class ApogeeSQ212_Voltage : public Variable { explicit ApogeeSQ212_Voltage( ApogeeSQ212* parentSense, const char* uuid = "", const char* varCode = SQ212_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)SQ212_VOLTAGE_VAR_NUM, - (uint8_t)SQ212_VOLTAGE_RESOLUTION, SQ212_VOLTAGE_VAR_NAME, - SQ212_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ApogeeSQ212_Voltage object. - * - * @note This must be tied with a parent ApogeeSQ212 before it can be used. - */ - ApogeeSQ212_Voltage() - : Variable((uint8_t)SQ212_VOLTAGE_VAR_NUM, - (uint8_t)SQ212_VOLTAGE_RESOLUTION, SQ212_VOLTAGE_VAR_NAME, - SQ212_VOLTAGE_UNIT_NAME, SQ212_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, SQ212_VOLTAGE_VAR_NUM, SQ212_VOLTAGE_RESOLUTION, + SQ212_VOLTAGE_VAR_NAME, SQ212_VOLTAGE_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the ApogeeSQ212_Voltage object - no action needed. */ - ~ApogeeSQ212_Voltage() {} + ~ApogeeSQ212_Voltage() override = default; }; /**@}*/ #endif // SRC_SENSORS_APOGEESQ212_H_ diff --git a/src/sensors/AtlasParent.cpp b/src/sensors/AtlasParent.cpp index e1a307217..47a4c9502 100644 --- a/src/sensors/AtlasParent.cpp +++ b/src/sensors/AtlasParent.cpp @@ -10,7 +10,6 @@ */ #include "AtlasParent.h" -#include // The constructors @@ -24,29 +23,28 @@ AtlasParent::AtlasParent(TwoWire* theI2C, int8_t powerPin, stabilizationTime_ms, measurementTime_ms, powerPin, -1, measurementsToAverage, incCalcValues), _i2cAddressHex(i2cAddressHex), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} +// Delegating constructor AtlasParent::AtlasParent(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage, const char* sensorName, const uint8_t totalReturnedValues, uint32_t warmUpTime_ms, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms, uint8_t incCalcValues) - : Sensor(sensorName, totalReturnedValues, warmUpTime_ms, - stabilizationTime_ms, measurementTime_ms, powerPin, -1, - measurementsToAverage, incCalcValues), - _i2cAddressHex(i2cAddressHex), - _i2c(&Wire) {} -// Destructors -AtlasParent::~AtlasParent() {} + : AtlasParent(&Wire, powerPin, i2cAddressHex, measurementsToAverage, + sensorName, totalReturnedValues, warmUpTime_ms, + stabilizationTime_ms, measurementTime_ms, incCalcValues) {} -String AtlasParent::getSensorLocation(void) { - String address = F("I2C_0x"); +String AtlasParent::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool AtlasParent::setup(void) { +bool AtlasParent::setup() { _i2c->begin(); // Start the wire library (sensor power not required) // Eliminate any potential extra waits in the wire library // These waits would be caused by a readBytes or parseX being called @@ -62,7 +60,7 @@ bool AtlasParent::setup(void) { // The function to put the sensor to sleep // The Atlas sensors must be told to sleep -bool AtlasParent::sleep(void) { +bool AtlasParent::sleep() { if (!checkPowerOn()) { return true; } if (_millisSensorActivated == 0) { MS_DBG(getSensorNameAndLocation(), F("was not measuring!")); @@ -100,7 +98,7 @@ bool AtlasParent::sleep(void) { // To start a measurement we write the command "R" to the sensor // NOTE: documentation says to use a capital "R" but the examples provided // by Atlas use a lower case "r". -bool AtlasParent::startSingleMeasurement(void) { +bool AtlasParent::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -135,64 +133,67 @@ bool AtlasParent::startSingleMeasurement(void) { } -bool AtlasParent::addSingleMeasurementResult(void) { - bool success = false; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - // call the circuit and request 40 bytes (this may be more than we need) - _i2c->requestFrom(static_cast(_i2cAddressHex), 40, 1); - // the first byte is the response code, we read this separately. - int code = _i2c->read(); - - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Parse the response code - switch (code) { - case 1: // the command was successful. - MS_DBG(F(" Measurement successful")); - success = true; - break; - - case 2: // the command has failed. - MS_DBG(F(" Measurement Failed")); - break; +bool AtlasParent::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - case 254: // the command has not yet been finished calculating. - MS_DBG(F(" Measurement Pending")); - break; + bool success = false; - case 255: // there is no further data to send. - MS_DBG(F(" No Data")); + // call the circuit and request up to the maximum buffer size (this may be + // more than we need) + int bytesReceived = _i2c->requestFrom(static_cast(_i2cAddressHex), + ATLAS_I2C_RESPONSE_BUFFER_SIZE, 1); + if (bytesReceived == 0) { + MS_DBG(getSensorNameAndLocation(), F("I2C read failed - no response")); + return finalizeMeasurementAttempt(false); + } + // the first byte is the response code, we read this separately. + int code = _i2c->read(); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + // Parse the response code + switch (code) { + case ATLAS_RESPONSE_SUCCESS: // the command was successful. + MS_DBG(F(" Measurement successful")); + success = true; + break; + + case ATLAS_RESPONSE_FAILED: // the command has failed. + MS_DBG(F(" Measurement Failed")); + break; + + case ATLAS_RESPONSE_PENDING: // the command has not yet been finished + // calculating. + MS_DBG(F(" Measurement Pending")); + break; + + case ATLAS_RESPONSE_NO_DATA: // there is no further data to send. + MS_DBG(F(" No Data")); + break; + + default: MS_DBG(F(" Unexpected response code:"), code); break; + } + // If the response code is successful, parse the remaining results + if (success) { + for (uint8_t i = 0; i < _numReturnedValues; i++) { + if (_i2c->available() == 0) { + MS_DBG(F(" Incomplete response; aborting parse")); + success = false; break; - - default: break; - } - // If the response code is successful, parse the remaining results - if (success) { - for (uint8_t i = 0; i < _numReturnedValues; i++) { - float result = _i2c->parseFloat(); - if (isnan(result)) { result = -9999; } - if (result < -1020) { result = -9999; } - MS_DBG(F(" Result #"), i, ':', result); - verifyAndAddMeasurementResult(i, result); } - } - } else { - // If there's no measurement, need to make sure we send over all - // of the "failed" result values - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); - for (uint8_t i = 0; i < _numReturnedValues; i++) { - verifyAndAddMeasurementResult(i, static_cast(-9999)); + float result = _i2c->parseFloat(); + if (isnan(result) || result < ATLAS_MIN_VALID_RESULT) { + result = MS_INVALID_VALUE; + success = false; + MS_DBG(F(" Invalid response for result #"), i); + // Don't break - subsequent values may be ok + } + MS_DBG(F(" Result #"), i, ':', result); + verifyAndAddMeasurementResult(i, result); } } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } @@ -207,7 +208,7 @@ bool AtlasParent::waitForProcessing(uint32_t timeout) { while (!processed && millis() - start < timeout) { _i2c->requestFrom(static_cast(_i2cAddressHex), 1, 1); auto code = static_cast(_i2c->read()); - if (code == 1) processed = true; + if (code == ATLAS_RESPONSE_SUCCESS) processed = true; } return processed; } diff --git a/src/sensors/AtlasParent.h b/src/sensors/AtlasParent.h index 5d6ac8118..4349c1b4e 100644 --- a/src/sensors/AtlasParent.h +++ b/src/sensors/AtlasParent.h @@ -93,12 +93,33 @@ #include "SensorBase.h" #include +/** @ingroup atlas_group */ +/**@{*/ + +/** + * @anchor atlas_response_codes + * @name Atlas Response Codes + * Standard response codes returned by Atlas EZO circuits + */ +/**@{*/ +/// @brief The command was successful +#define ATLAS_RESPONSE_SUCCESS 1 +/// @brief The command has failed +#define ATLAS_RESPONSE_FAILED 2 +/// @brief The command has not yet been finished calculating +#define ATLAS_RESPONSE_PENDING 254 +/// @brief There is no further data to send +#define ATLAS_RESPONSE_NO_DATA 255 +/// @brief Maximum I2C response buffer size for Atlas circuits +#define ATLAS_I2C_RESPONSE_BUFFER_SIZE 40 +/// @brief Minimum valid result threshold for Atlas sensor readings +#define ATLAS_MIN_VALID_RESULT -1020.0f +/**@}*/ + /** * @brief A parent class for Atlas EZO circuits and sensors * * This contains the main I2C functionality for all Atlas EZO circuits. - * - * @ingroup atlas_group */ class AtlasParent : public Sensor { public: @@ -171,17 +192,16 @@ class AtlasParent : public Sensor { uint32_t measurementTime_ms = 0, uint8_t incCalcValues = 0); /** - * @brief Destroy the Atlas Parent object. Also destroy the software I2C - * instance if one was created. + * @brief Destroy the Atlas Parent object. */ - virtual ~AtlasParent(); + ~AtlasParent() override = default; /** * @brief Return the I2C address of the EZO circuit. * * @return Text describing how the sensor is attached to the mcu. */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -193,39 +213,17 @@ class AtlasParent : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; // NOTE: The sensor should wake as soon as any command is sent. // I assume that means we can use the command to take a reading to both // wake it and ask for a reading. - // bool wake(void) override; + // bool wake() override; - /** - * @brief Puts the sensor to sleep, if necessary. - * - * This also un-sets the #_millisSensorActivated timestamp (sets it to 0). - * This does NOT power down the sensor! - * - * @return True if the sleep function completed successfully. - */ - bool sleep(void) override; + bool sleep() override; - /** - * @brief Tell the sensor to start a single measurement, if needed. - * - * This also sets the #_millisMeasurementRequested timestamp. - * - * @note This function does NOT include any waiting for the sensor to be - * warmed up or stable! - * - * @return True if the start measurement function completed - * successfully. - */ - bool startSingleMeasurement(void) override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; protected: /** @@ -255,5 +253,5 @@ class AtlasParent : public Sensor { */ bool waitForProcessing(uint32_t timeout = 1000L); }; - +/**@}*/ #endif // SRC_SENSORS_ATLASPARENT_H_ diff --git a/src/sensors/AtlasScientificCO2.cpp b/src/sensors/AtlasScientificCO2.cpp index 15c734717..f873330e2 100644 --- a/src/sensors/AtlasScientificCO2.cpp +++ b/src/sensors/AtlasScientificCO2.cpp @@ -21,16 +21,11 @@ AtlasScientificCO2::AtlasScientificCO2(TwoWire* theI2C, int8_t powerPin, ATLAS_CO2_WARM_UP_TIME_MS, ATLAS_CO2_STABILIZATION_TIME_MS, ATLAS_CO2_MEASUREMENT_TIME_MS, ATLAS_CO2_INC_CALC_VARIABLES) { } +// Delegating constructor AtlasScientificCO2::AtlasScientificCO2(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) - : AtlasParent(powerPin, i2cAddressHex, measurementsToAverage, - "AtlasScientificCO2", ATLAS_CO2_NUM_VARIABLES, - ATLAS_CO2_WARM_UP_TIME_MS, ATLAS_CO2_STABILIZATION_TIME_MS, - ATLAS_CO2_MEASUREMENT_TIME_MS, ATLAS_CO2_INC_CALC_VARIABLES) { -} - -// Destructor -AtlasScientificCO2::~AtlasScientificCO2() {} + : AtlasScientificCO2(&Wire, powerPin, i2cAddressHex, + measurementsToAverage) {} // Setup diff --git a/src/sensors/AtlasScientificCO2.h b/src/sensors/AtlasScientificCO2.h index c16743a82..2b33bd5de 100644 --- a/src/sensors/AtlasScientificCO2.h +++ b/src/sensors/AtlasScientificCO2.h @@ -73,6 +73,18 @@ /** @ingroup sensor_atlas_co2 */ /**@{*/ +/** + * @anchor sensor_atlas_co2_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas CO2 sensor + */ +/**@{*/ +#ifndef ATLAS_CO2_I2C_ADDR +/// @brief The default I2C address of the Atlas CO2 sensor is 0x69 (105) +#define ATLAS_CO2_I2C_ADDR 0x69 +#endif +/**@}*/ + /** * @anchor sensor_atlas_co2_var_counts * @name Sensor Variable Counts @@ -85,16 +97,6 @@ #define ATLAS_CO2_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_co2_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas CO2 sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas CO2 sensor is 0x69 (105) -#define ATLAS_CO2_I2C_ADDR 0x69 -/**@}*/ - /** * @anchor sensor_atlas_co2_timing * @name Sensor Timing @@ -122,7 +124,11 @@ * {{ @ref AtlasScientificCO2_CO2::AtlasScientificCO2_CO2 }} */ /**@{*/ -/// @brief Decimals places in string representation; CO2 should have 1 - +/// @brief Minimum CO2 concentration in parts per million. +#define ATLAS_CO2_MIN_PPM 0 +/// @brief Maximum CO2 concentration in parts per million. +#define ATLAS_CO2_MAX_PPM 10000 +/// @brief Decimal places in string representation; CO2 should have 1 - /// resolution is 1 ppm. #define ATLAS_CO2_RESOLUTION 1 /// @brief Sensor variable number; CO2 is stored in sensorValues[0]. @@ -149,7 +155,11 @@ * {{ @ref AtlasScientificCO2_Temp::AtlasScientificCO2_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; CO2TEMP should have 0 - +/// @brief Minimum temperature in degrees Celsius. +#define ATLAS_CO2TEMP_MIN_C -20 +/// @brief Maximum temperature in degrees Celsius. +#define ATLAS_CO2TEMP_MAX_C 50 +/// @brief Decimal places in string representation; CO2TEMP should have 0 - /// resolution is 1°C. #define ATLAS_CO2TEMP_RESOLUTION 0 /// @brief Sensor variable number; CO2TEMP is stored in sensorValues[1]. @@ -228,10 +238,9 @@ class AtlasScientificCO2 : public AtlasParent { uint8_t measurementsToAverage = 1); /** - * @brief Destroy the Atlas Scientific CO2 object. Also destroy the - * software I2C instance if one was created. + * @brief Destroy the Atlas Scientific CO2 object. */ - ~AtlasScientificCO2(); + ~AtlasScientificCO2() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -243,7 +252,7 @@ class AtlasScientificCO2 : public AtlasParent { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; }; /* clang-format off */ @@ -270,23 +279,12 @@ class AtlasScientificCO2_CO2 : public Variable { explicit AtlasScientificCO2_CO2( AtlasScientificCO2* parentSense, const char* uuid = "", const char* varCode = ATLAS_CO2_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_CO2_VAR_NUM, - (uint8_t)ATLAS_CO2_RESOLUTION, ATLAS_CO2_VAR_NAME, - ATLAS_CO2_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificCO2_CO2 object. - * - * @note This must be tied with a parent AtlasScientificCO2 before it can be - * used. - */ - AtlasScientificCO2_CO2() - : Variable((uint8_t)ATLAS_CO2_VAR_NUM, (uint8_t)ATLAS_CO2_RESOLUTION, - ATLAS_CO2_VAR_NAME, ATLAS_CO2_UNIT_NAME, - ATLAS_CO2_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_CO2_VAR_NUM, ATLAS_CO2_RESOLUTION, + ATLAS_CO2_VAR_NAME, ATLAS_CO2_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificCO2_CO2 object - no action needed. */ - ~AtlasScientificCO2_CO2() {} + ~AtlasScientificCO2_CO2() override = default; }; /* clang-format off */ @@ -313,23 +311,13 @@ class AtlasScientificCO2_Temp : public Variable { explicit AtlasScientificCO2_Temp( AtlasScientificCO2* parentSense, const char* uuid = "", const char* varCode = ATLAS_CO2TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_CO2TEMP_VAR_NUM, - (uint8_t)ATLAS_CO2TEMP_RESOLUTION, ATLAS_CO2TEMP_VAR_NAME, - ATLAS_CO2TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificCO2_Temp object. - * - * @note This must be tied with a parent AtlasScientificCO2 before it can be - * used. - */ - AtlasScientificCO2_Temp() - : Variable((uint8_t)ATLAS_CO2TEMP_VAR_NUM, - (uint8_t)ATLAS_CO2TEMP_RESOLUTION, ATLAS_CO2TEMP_VAR_NAME, - ATLAS_CO2TEMP_UNIT_NAME, ATLAS_CO2TEMP_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_CO2TEMP_VAR_NUM, ATLAS_CO2TEMP_RESOLUTION, + ATLAS_CO2TEMP_VAR_NAME, ATLAS_CO2TEMP_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the AtlasScientificCO2_Temp object - no action needed. */ - ~AtlasScientificCO2_Temp() {} + ~AtlasScientificCO2_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICCO2_H_ diff --git a/src/sensors/AtlasScientificDO.cpp b/src/sensors/AtlasScientificDO.cpp index 1611bc95a..c25f0dbb3 100644 --- a/src/sensors/AtlasScientificDO.cpp +++ b/src/sensors/AtlasScientificDO.cpp @@ -6,7 +6,7 @@ * @author Initial development for Atlas Sensors was done by Adam Gold * Files were edited by Sara Damiano * - * @brief Implements the AtlasScientificCO2 class. + * @brief Implements the AtlasScientificDO class. */ // Included Dependencies @@ -20,15 +20,11 @@ AtlasScientificDO::AtlasScientificDO(TwoWire* theI2C, int8_t powerPin, "AtlasScientificDO", ATLAS_DO_NUM_VARIABLES, ATLAS_DO_WARM_UP_TIME_MS, ATLAS_DO_STABILIZATION_TIME_MS, ATLAS_DO_MEASUREMENT_TIME_MS, ATLAS_DO_INC_CALC_VARIABLES) {} +// Delegating constructor AtlasScientificDO::AtlasScientificDO(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) - : AtlasParent(powerPin, i2cAddressHex, measurementsToAverage, - "AtlasScientificDO", ATLAS_DO_NUM_VARIABLES, - ATLAS_DO_WARM_UP_TIME_MS, ATLAS_DO_STABILIZATION_TIME_MS, - ATLAS_DO_MEASUREMENT_TIME_MS, ATLAS_DO_INC_CALC_VARIABLES) {} - -// Destructor -AtlasScientificDO::~AtlasScientificDO() {} + : AtlasScientificDO(&Wire, powerPin, i2cAddressHex, measurementsToAverage) { +} // Setup diff --git a/src/sensors/AtlasScientificDO.h b/src/sensors/AtlasScientificDO.h index 7d00e8078..ca61d7fdb 100644 --- a/src/sensors/AtlasScientificDO.h +++ b/src/sensors/AtlasScientificDO.h @@ -72,6 +72,18 @@ /** @ingroup sensor_atlas_do */ /**@{*/ +/** + * @anchor sensor_atlas_do_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas DO sensor + */ +/**@{*/ +#ifndef ATLAS_DO_I2C_ADDR +/// @brief The default I2C address of the Atlas DO sensor is 0x61 (97) +#define ATLAS_DO_I2C_ADDR 0x61 +#endif +/**@}*/ + /** * @anchor sensor_atlas_do_var_counts * @name Sensor Variable Counts @@ -85,16 +97,6 @@ #define ATLAS_DO_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_do_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas DO sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas DO sensor is 0x61 (97) -#define ATLAS_DO_I2C_ADDR 0x61 -/**@}*/ - /** * @anchor sensor_atlas_do_timing * @name Sensor Timing @@ -131,7 +133,11 @@ * {{ @ref AtlasScientificDO_DOmgL::AtlasScientificDO_DOmgL }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen +/// @brief Minimum dissolved oxygen concentration in milligrams per liter. +#define ATLAS_DOMGL_MIN_MGPL 0.01 +/// @brief Maximum dissolved oxygen concentration in milligrams per liter. +#define ATLAS_DOMGL_MAX_MGPL 100.0 +/// @brief Decimal places in string representation; dissolved oxygen /// concentration should have 2 - resolution is 0.01 mg/L. #define ATLAS_DOMGL_RESOLUTION 2 /// @brief Sensor variable number; dissolved oxygen concentration is stored in @@ -159,7 +165,11 @@ * {{ @ref AtlasScientificDO_DOpct::AtlasScientificDO_DOpct }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen percent +/// @brief Minimum dissolved oxygen percent saturation. +#define ATLAS_DOPCT_MIN_PCT 0.1 +/// @brief Maximum dissolved oxygen percent saturation. +#define ATLAS_DOPCT_MAX_PCT 400.0 +/// @brief Decimal places in string representation; dissolved oxygen percent /// should have 1 - resolution is 0.1 % saturation. #define ATLAS_DOPCT_RESOLUTION 1 /// @brief Sensor variable number; dissolved oxygen percent is stored in @@ -239,7 +249,7 @@ class AtlasScientificDO : public AtlasParent { /** * @brief Destroy the Atlas Scientific DO object */ - ~AtlasScientificDO(); + ~AtlasScientificDO() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -251,7 +261,7 @@ class AtlasScientificDO : public AtlasParent { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; }; /* clang-format off */ @@ -278,23 +288,13 @@ class AtlasScientificDO_DOmgL : public Variable { explicit AtlasScientificDO_DOmgL( AtlasScientificDO* parentSense, const char* uuid = "", const char* varCode = ATLAS_DOMGL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_DOMGL_VAR_NUM, - (uint8_t)ATLAS_DOMGL_RESOLUTION, ATLAS_DOMGL_VAR_NAME, - ATLAS_DOMGL_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificDO_DOmgL object. - * - * @note This must be tied with a parent AtlasScientificDO before it can be - * used. - */ - AtlasScientificDO_DOmgL() - : Variable((uint8_t)ATLAS_DOMGL_VAR_NUM, - (uint8_t)ATLAS_DOMGL_RESOLUTION, ATLAS_DOMGL_VAR_NAME, - ATLAS_DOMGL_UNIT_NAME, ATLAS_DOMGL_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_DOMGL_VAR_NUM, ATLAS_DOMGL_RESOLUTION, + ATLAS_DOMGL_VAR_NAME, ATLAS_DOMGL_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the AtlasScientificDO_DOmgL object - no action needed. */ - ~AtlasScientificDO_DOmgL() {} + ~AtlasScientificDO_DOmgL() override = default; }; /* clang-format off */ @@ -321,23 +321,13 @@ class AtlasScientificDO_DOpct : public Variable { explicit AtlasScientificDO_DOpct( AtlasScientificDO* parentSense, const char* uuid = "", const char* varCode = ATLAS_DOPCT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_DOPCT_VAR_NUM, - (uint8_t)ATLAS_DOPCT_RESOLUTION, ATLAS_DOPCT_VAR_NAME, - ATLAS_DOPCT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificDO_DOpct object. - * - * @note This must be tied with a parent AtlasScientificDO before it can be - * used. - */ - AtlasScientificDO_DOpct() - : Variable((uint8_t)ATLAS_DOPCT_VAR_NUM, - (uint8_t)ATLAS_DOPCT_RESOLUTION, ATLAS_DOPCT_VAR_NAME, - ATLAS_DOPCT_UNIT_NAME, ATLAS_DOPCT_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_DOPCT_VAR_NUM, ATLAS_DOPCT_RESOLUTION, + ATLAS_DOPCT_VAR_NAME, ATLAS_DOPCT_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the AtlasScientificDO_DOpct object - no action needed. */ - ~AtlasScientificDO_DOpct() {} + ~AtlasScientificDO_DOpct() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICDO_H_ diff --git a/src/sensors/AtlasScientificEC.cpp b/src/sensors/AtlasScientificEC.cpp index f3d2f6311..35c8eceee 100644 --- a/src/sensors/AtlasScientificEC.cpp +++ b/src/sensors/AtlasScientificEC.cpp @@ -21,16 +21,11 @@ AtlasScientificEC::AtlasScientificEC(TwoWire* theI2C, int8_t powerPin, ATLAS_COND_WARM_UP_TIME_MS, ATLAS_COND_STABILIZATION_TIME_MS, ATLAS_COND_MEASUREMENT_TIME_MS, ATLAS_COND_INC_CALC_VARIABLES) {} +// Delegating constructor AtlasScientificEC::AtlasScientificEC(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) - : AtlasParent(powerPin, i2cAddressHex, measurementsToAverage, - "AtlasScientificEC", ATLAS_COND_NUM_VARIABLES, - ATLAS_COND_WARM_UP_TIME_MS, ATLAS_COND_STABILIZATION_TIME_MS, - ATLAS_COND_MEASUREMENT_TIME_MS, - ATLAS_COND_INC_CALC_VARIABLES) {} - -// Destructor -AtlasScientificEC::~AtlasScientificEC() {} + : AtlasScientificEC(&Wire, powerPin, i2cAddressHex, measurementsToAverage) { +} // Setup diff --git a/src/sensors/AtlasScientificEC.h b/src/sensors/AtlasScientificEC.h index 1eb810cb1..0bccd09cc 100644 --- a/src/sensors/AtlasScientificEC.h +++ b/src/sensors/AtlasScientificEC.h @@ -80,6 +80,20 @@ /** @ingroup sensor_atlas_cond */ /**@{*/ +/** + * @anchor sensor_atlas_cond_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas conductivity + * sensor + */ +/**@{*/ +#ifndef ATLAS_COND_I2C_ADDR +/// @brief The default I2C address of the Atlas conductivity sensor is 0x64 +/// (100) +#define ATLAS_COND_I2C_ADDR 0x64 +#endif +/**@}*/ + /** * @anchor sensor_atlas_cond_var_counts * @name Sensor Variable Counts @@ -95,18 +109,6 @@ #define ATLAS_COND_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_cond_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas conductivity - * sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas conductivity sensor is 0x64 -/// (100) -#define ATLAS_COND_I2C_ADDR 0x64 -/**@}*/ - /** * @anchor sensor_atlas_cond_timing * @name Sensor Timing @@ -142,7 +144,11 @@ * {{ @ref AtlasScientificEC_Cond::AtlasScientificEC_Cond }} */ /**@{*/ -/// @brief Decimals places in string representation; conductivity should have 3. +/// @brief Minimum electrical conductivity in microsiemens per centimeter. +#define ATLAS_COND_MIN_USCM 0.07 +/// @brief Maximum electrical conductivity in microsiemens per centimeter. +#define ATLAS_COND_MAX_USCM 500000.0 +/// @brief Decimal places in string representation; conductivity should have 3. #define ATLAS_COND_RESOLUTION 3 /// @brief Sensor variable number; conductivity is stored in sensorValues[0]. #define ATLAS_COND_VAR_NUM 0 @@ -168,7 +174,11 @@ * {{ @ref AtlasScientificEC_TDS::AtlasScientificEC_TDS }} */ /**@{*/ -/// @brief Decimals places in string representation; TDS should have 3. +/// @brief Minimum total dissolved solids in parts per million. +#define ATLAS_TDS_MIN_PPM 0.01 +/// @brief Maximum total dissolved solids in parts per million. +#define ATLAS_TDS_MAX_PPM 300000.0 +/// @brief Decimal places in string representation; TDS should have 3. #define ATLAS_TDS_RESOLUTION 3 /// @brief Sensor variable number; TDS is stored in sensorValues[1]. #define ATLAS_TDS_VAR_NUM 1 @@ -189,12 +199,16 @@ * @name Salinity * The salinity variable from an Atlas EC (conductivity) sensor * - Accuracy is ± 2% - * - Range is 0.07 − 500,000+ μS/cm + * - Range is 0 - 42 PSU * * {{ @ref AtlasScientificEC_Salinity::AtlasScientificEC_Salinity }} */ /**@{*/ -/// @brief Decimals places in string representation; salinity should have 3. +/// @brief Minimum salinity in practical salinity units. +#define ATLAS_SALINITY_MIN_PSU 0.0 +/// @brief Maximum salinity in practical salinity units. +#define ATLAS_SALINITY_MAX_PSU 42.0 +/// @brief Decimal places in string representation; salinity should have 3. #define ATLAS_SALINITY_RESOLUTION 3 /// @brief Sensor variable number; salinity is stored in sensorValues[2]. #define ATLAS_SALINITY_VAR_NUM 2 @@ -222,7 +236,11 @@ */ /* clang-format on */ /**@{*/ -/// @brief Decimals places in string representation; specific gravity should +/// @brief Minimum specific gravity (dimensionless). +#define ATLAS_SG_MIN 0.99 +/// @brief Maximum specific gravity (dimensionless). +#define ATLAS_SG_MAX 1.042 +/// @brief Decimal places in string representation; specific gravity should /// have 3. #define ATLAS_SG_RESOLUTION 3 /// @brief Sensor variable number; specific gravity is stored in @@ -302,7 +320,7 @@ class AtlasScientificEC : public AtlasParent { /** * @brief Destroy the Atlas Scientific EC object */ - ~AtlasScientificEC(); + ~AtlasScientificEC() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -314,7 +332,7 @@ class AtlasScientificEC : public AtlasParent { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; }; /* clang-format off */ @@ -341,23 +359,12 @@ class AtlasScientificEC_Cond : public Variable { explicit AtlasScientificEC_Cond( AtlasScientificEC* parentSense, const char* uuid = "", const char* varCode = ATLAS_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_COND_VAR_NUM, - (uint8_t)ATLAS_COND_RESOLUTION, ATLAS_COND_VAR_NAME, - ATLAS_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificEC_Cond object. - * - * @note This must be tied with a parent AtlasScientificEC before it can be - * used. - */ - AtlasScientificEC_Cond() - : Variable((uint8_t)ATLAS_COND_VAR_NUM, (uint8_t)ATLAS_COND_RESOLUTION, - ATLAS_COND_VAR_NAME, ATLAS_COND_UNIT_NAME, - ATLAS_COND_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_COND_VAR_NUM, ATLAS_COND_RESOLUTION, + ATLAS_COND_VAR_NAME, ATLAS_COND_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificEC_Cond object - no action needed. */ - ~AtlasScientificEC_Cond() {} + ~AtlasScientificEC_Cond() override = default; }; /* clang-format off */ @@ -384,23 +391,12 @@ class AtlasScientificEC_TDS : public Variable { explicit AtlasScientificEC_TDS(AtlasScientificEC* parentSense, const char* uuid = "", const char* varCode = ATLAS_TDS_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_TDS_VAR_NUM, - (uint8_t)ATLAS_TDS_RESOLUTION, ATLAS_TDS_VAR_NAME, - ATLAS_TDS_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificEC_TDS object. - * - * @note This must be tied with a parent AtlasScientificEC before it can be - * used. - */ - AtlasScientificEC_TDS() - : Variable((uint8_t)ATLAS_TDS_VAR_NUM, (uint8_t)ATLAS_TDS_RESOLUTION, - ATLAS_TDS_VAR_NAME, ATLAS_TDS_UNIT_NAME, - ATLAS_TDS_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_TDS_VAR_NUM, ATLAS_TDS_RESOLUTION, + ATLAS_TDS_VAR_NAME, ATLAS_TDS_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificEC_TDS object - no action needed. */ - ~AtlasScientificEC_TDS() {} + ~AtlasScientificEC_TDS() override = default; }; /* clang-format off */ @@ -427,24 +423,14 @@ class AtlasScientificEC_Salinity : public Variable { explicit AtlasScientificEC_Salinity( AtlasScientificEC* parentSense, const char* uuid = "", const char* varCode = ATLAS_SALINITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_SALINITY_VAR_NUM, - (uint8_t)ATLAS_SALINITY_RESOLUTION, ATLAS_SALINITY_VAR_NAME, + : Variable(parentSense, ATLAS_SALINITY_VAR_NUM, + ATLAS_SALINITY_RESOLUTION, ATLAS_SALINITY_VAR_NAME, ATLAS_SALINITY_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificEC_Salinity object. - * - * @note This must be tied with a parent AtlasScientificEC before it can be - * used. - */ - AtlasScientificEC_Salinity() - : Variable((uint8_t)ATLAS_SALINITY_VAR_NUM, - (uint8_t)ATLAS_SALINITY_RESOLUTION, ATLAS_SALINITY_VAR_NAME, - ATLAS_SALINITY_UNIT_NAME, ATLAS_SALINITY_DEFAULT_CODE) {} /** * @brief Destroy the AtlasScientificEC_Salinity() object - no action * needed. */ - ~AtlasScientificEC_Salinity() {} + ~AtlasScientificEC_Salinity() override = default; }; /* clang-format off */ @@ -471,24 +457,13 @@ class AtlasScientificEC_SpecificGravity : public Variable { explicit AtlasScientificEC_SpecificGravity( AtlasScientificEC* parentSense, const char* uuid = "", const char* varCode = ATLAS_SG_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_SG_VAR_NUM, - (uint8_t)ATLAS_SG_RESOLUTION, ATLAS_SG_VAR_NAME, - ATLAS_SG_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificEC_SpecificGravity object. - * - * @note This must be tied with a parent AtlasScientificEC before it can be - * used. - */ - AtlasScientificEC_SpecificGravity() - : Variable((uint8_t)ATLAS_SG_VAR_NUM, (uint8_t)ATLAS_SG_RESOLUTION, - ATLAS_SG_VAR_NAME, ATLAS_SG_UNIT_NAME, - ATLAS_SG_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_SG_VAR_NUM, ATLAS_SG_RESOLUTION, + ATLAS_SG_VAR_NAME, ATLAS_SG_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificEC_SpecificGravity() object - no action * needed. */ - ~AtlasScientificEC_SpecificGravity() {} + ~AtlasScientificEC_SpecificGravity() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICEC_H_ diff --git a/src/sensors/AtlasScientificORP.h b/src/sensors/AtlasScientificORP.h index 6e1cbd703..c33933625 100644 --- a/src/sensors/AtlasScientificORP.h +++ b/src/sensors/AtlasScientificORP.h @@ -49,6 +49,18 @@ /** @ingroup sensor_atlas_orp */ /**@{*/ +/** + * @anchor sensor_atlas_orp_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas ORP sensor + */ +/**@{*/ +#ifndef ATLAS_ORP_I2C_ADDR +/// @brief The default I2C address of the Atlas ORP sensor is 0x62 (98) +#define ATLAS_ORP_I2C_ADDR 0x62 +#endif +/**@}*/ + /** * @anchor sensor_atlas_orp_var_counts * @name Sensor Variable Counts @@ -62,16 +74,6 @@ #define ATLAS_ORP_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_orp_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas ORP sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas ORP sensor is 0x62 (98) -#define ATLAS_ORP_I2C_ADDR 0x62 -/**@}*/ - /** * @anchor sensor_atlas_orp_timing * @name Sensor Timing @@ -109,8 +111,12 @@ * {{ @ref AtlasScientificORP_Potential::AtlasScientificORP_Potential }} */ /**@{*/ -/// @brief Decimals places in string representation; ORP should have 1 - -/// resolution is 0.1 mV. +/// @brief Minimum oxidation reduction potential in millivolts. +#define ATLAS_ORP_MIN_MV -1019.9 +/// @brief Maximum oxidation reduction potential in millivolts. +#define ATLAS_ORP_MAX_MV 1019.9 +/// @brief Decimal places in string representation; ORP should have 1 +/// (resolution is 0.1 mV). #define ATLAS_ORP_RESOLUTION 1 /// @brief Sensor variable number; ORP is stored in sensorValues[0]. #define ATLAS_ORP_VAR_NUM 0 @@ -197,7 +203,7 @@ class AtlasScientificORP : public AtlasParent { /** * @brief Destroy the Atlas Scientific ORP object */ - ~AtlasScientificORP() {} + ~AtlasScientificORP() override = default; }; @@ -225,24 +231,13 @@ class AtlasScientificORP_Potential : public Variable { explicit AtlasScientificORP_Potential( AtlasScientificORP* parentSense, const char* uuid = "", const char* varCode = ATLAS_ORP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_ORP_VAR_NUM, - (uint8_t)ATLAS_ORP_RESOLUTION, ATLAS_ORP_VAR_NAME, - ATLAS_ORP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificORP_Potential object. - * - * @note This must be tied with a parent AtlasScientificORP before it can be - * used. - */ - AtlasScientificORP_Potential() - : Variable((uint8_t)ATLAS_ORP_VAR_NUM, (uint8_t)ATLAS_ORP_RESOLUTION, - ATLAS_ORP_VAR_NAME, ATLAS_ORP_UNIT_NAME, - ATLAS_ORP_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_ORP_VAR_NUM, ATLAS_ORP_RESOLUTION, + ATLAS_ORP_VAR_NAME, ATLAS_ORP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificORP_Potential() object - no action * needed. */ - ~AtlasScientificORP_Potential() {} + ~AtlasScientificORP_Potential() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICORP_H_ diff --git a/src/sensors/AtlasScientificRTD.h b/src/sensors/AtlasScientificRTD.h index e3023c5ab..770cb421a 100644 --- a/src/sensors/AtlasScientificRTD.h +++ b/src/sensors/AtlasScientificRTD.h @@ -54,6 +54,19 @@ /** @ingroup sensor_atlas_rtd */ /**@{*/ +/** + * @anchor sensor_atlas_rtd_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas RTD (temperature) + * sensor + */ +/**@{*/ +#ifndef ATLAS_RTD_I2C_ADDR +/// @brief The default I2C address of the Atlas RTD sensor is 0x66 (102) +#define ATLAS_RTD_I2C_ADDR 0x66 +#endif +/**@}*/ + /** * @anchor sensor_atlas_rtd_var_counts * @name Sensor Variable Counts @@ -68,17 +81,6 @@ #define ATLAS_RTD_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_rtd_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas RTD (temperature) - * sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas RTD sensor is 0x66 (102) -#define ATLAS_RTD_I2C_ADDR 0x66 -/**@}*/ - /** * @anchor sensor_atlas_rtd_timing * @name Sensor Timing @@ -117,7 +119,11 @@ * {{ @ref AtlasScientificRTD_Temp::AtlasScientificRTD_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 3 - +/// @brief Minimum temperature in degrees Celsius. +#define ATLAS_RTD_MIN_C -126.0 +/// @brief Maximum temperature in degrees Celsius. +#define ATLAS_RTD_MAX_C 125.0 +/// @brief Decimal places in string representation; temperature should have 3 - /// resolution is 0.001°C. #define ATLAS_RTD_RESOLUTION 3 /// @brief Sensor variable number; RTD is stored in sensorValues[0]. @@ -205,7 +211,7 @@ class AtlasScientificRTD : public AtlasParent { /** * @brief Destroy the Atlas Scientific RTD object */ - ~AtlasScientificRTD() {} + ~AtlasScientificRTD() override = default; }; /* clang-format off */ @@ -232,23 +238,12 @@ class AtlasScientificRTD_Temp : public Variable { explicit AtlasScientificRTD_Temp( AtlasScientificRTD* parentSense, const char* uuid = "", const char* varCode = ATLAS_RTD_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_RTD_VAR_NUM, - (uint8_t)ATLAS_RTD_RESOLUTION, ATLAS_RTD_VAR_NAME, - ATLAS_RTD_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificRTD_Temp object. - * - * @note This must be tied with a parent AtlasScientificRTD before it can be - * used. - */ - AtlasScientificRTD_Temp() - : Variable((uint8_t)ATLAS_RTD_VAR_NUM, (uint8_t)ATLAS_RTD_RESOLUTION, - ATLAS_RTD_VAR_NAME, ATLAS_RTD_UNIT_NAME, - ATLAS_RTD_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_RTD_VAR_NUM, ATLAS_RTD_RESOLUTION, + ATLAS_RTD_VAR_NAME, ATLAS_RTD_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificRTD_Temp object - no action needed. */ - ~AtlasScientificRTD_Temp() {} + ~AtlasScientificRTD_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICRTD_H_ diff --git a/src/sensors/AtlasScientificpH.h b/src/sensors/AtlasScientificpH.h index de978e48d..737cd153d 100644 --- a/src/sensors/AtlasScientificpH.h +++ b/src/sensors/AtlasScientificpH.h @@ -51,6 +51,18 @@ /** @ingroup sensor_atlas_ph */ /**@{*/ +/** + * @anchor sensor_atlas_ph_config + * @name Configuration Defines + * Defines to configure and set the address of the Atlas pH sensor + */ +/**@{*/ +#ifndef ATLAS_PH_I2C_ADDR +/// @brief The default I2C address of the Atlas pH sensor is 0x63 (99) +#define ATLAS_PH_I2C_ADDR 0x63 +#endif +/**@}*/ + /** * @anchor sensor_atlas_ph_var_counts * @name Sensor Variable Counts @@ -64,16 +76,6 @@ #define ATLAS_PH_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_atlas_ph_config - * @name Configuration Defines - * Defines to configure and set the address of the Atlas pH sensor - */ -/**@{*/ -/// @brief The default I2C address of the Atlas pH sensor is 0x63 (99) -#define ATLAS_PH_I2C_ADDR 0x63 -/**@}*/ - /** * @anchor sensor_atlas_ph_timing * @name Sensor Timing @@ -113,7 +115,11 @@ * {{ @ref AtlasScientificpH_pH::AtlasScientificpH_pH }} */ /**@{*/ -/// @brief Decimals places in string representation; pH should have 3 - +/// @brief Minimum pH value. +#define ATLAS_PH_MIN 0.001 +/// @brief Maximum pH value. +#define ATLAS_PH_MAX 14.000 +/// @brief Decimal places in string representation; pH should have 3 - /// resolution is 0.001. #define ATLAS_PH_RESOLUTION 3 /// @brief Sensor variable number; pH is stored in sensorValues[0]. @@ -199,11 +205,10 @@ class AtlasScientificpH : public AtlasParent { ATLAS_PH_WARM_UP_TIME_MS, ATLAS_PH_STABILIZATION_TIME_MS, ATLAS_PH_MEASUREMENT_TIME_MS, ATLAS_PH_INC_CALC_VARIABLES) {} - /** * @brief Destroy the Atlas Scientific pH object */ - ~AtlasScientificpH() {} + ~AtlasScientificpH() override = default; }; @@ -233,25 +238,14 @@ class AtlasScientificpH_pH : public Variable { explicit AtlasScientificpH_pH(AtlasScientificpH* parentSense, const char* uuid = "", const char* varCode = ATLAS_PH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ATLAS_PH_VAR_NUM, - (uint8_t)ATLAS_PH_RESOLUTION, ATLAS_PH_VAR_NAME, - ATLAS_PH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new AtlasScientificpH_pH object. - * - * @note This must be tied with a parent AtlasScientificpH before it can be - * used. - */ - AtlasScientificpH_pH() - : Variable((uint8_t)ATLAS_PH_VAR_NUM, (uint8_t)ATLAS_PH_RESOLUTION, - ATLAS_PH_VAR_NAME, ATLAS_PH_UNIT_NAME, - ATLAS_PH_DEFAULT_CODE) {} + : Variable(parentSense, ATLAS_PH_VAR_NUM, ATLAS_PH_RESOLUTION, + ATLAS_PH_VAR_NAME, ATLAS_PH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the AtlasScientificpH_pH object - no action needed. */ - ~AtlasScientificpH_pH() {} + ~AtlasScientificpH_pH() override = default; }; /**@}*/ #endif // SRC_SENSORS_ATLASSCIENTIFICPH_H_ -// cSpell:ignore AtlasScientificpH AtlaspH +// cSpell:words AtlasScientificpH AtlaspH diff --git a/src/sensors/BoschBME280.cpp b/src/sensors/BoschBME280.cpp index fb9de7f9b..51fc03414 100644 --- a/src/sensors/BoschBME280.cpp +++ b/src/sensors/BoschBME280.cpp @@ -16,30 +16,26 @@ BoschBME280::BoschBME280(TwoWire* theI2C, int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) : Sensor("BoschBME280", BME280_NUM_VARIABLES, BME280_WARM_UP_TIME_MS, BME280_STABILIZATION_TIME_MS, BME280_MEASUREMENT_TIME_MS, powerPin, - -1, measurementsToAverage), + -1, measurementsToAverage, BME280_INC_CALC_VARIABLES), _i2cAddressHex(i2cAddressHex), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} +// Delegating constructor BoschBME280::BoschBME280(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) - : Sensor("BoschBME280", BME280_NUM_VARIABLES, BME280_WARM_UP_TIME_MS, - BME280_STABILIZATION_TIME_MS, BME280_MEASUREMENT_TIME_MS, powerPin, - -1, measurementsToAverage, BME280_INC_CALC_VARIABLES), - _i2cAddressHex(i2cAddressHex), - _i2c(&Wire) {} + : BoschBME280(&Wire, powerPin, i2cAddressHex, measurementsToAverage) {} -// Destructor -BoschBME280::~BoschBME280() {} - -String BoschBME280::getSensorLocation(void) { - String address = F("I2C_0x"); +String BoschBME280::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool BoschBME280::setup(void) { +bool BoschBME280::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -59,6 +55,7 @@ bool BoschBME280::setup(void) { ntries++; } if (!success) { + MS_DBG(getSensorNameAndLocation(), F("setup failed after 5 attempts")); // Set the status error bit (bit 7) setStatusBit(ERROR_OCCURRED); // UN-set the set-up bit (bit 0) since setup failed! @@ -66,14 +63,14 @@ bool BoschBME280::setup(void) { } retVal &= success; - // Turn the power back off it it had been turned on + // Turn the power back off if it had been turned on if (!wasOn) { powerDown(); } return retVal; } -bool BoschBME280::wake(void) { +bool BoschBME280::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; @@ -114,61 +111,49 @@ bool BoschBME280::wake(void) { } -bool BoschBME280::addSingleMeasurementResult(void) { - bool success = false; - - // Initialize float variables - float temp = -9999; - float humid = -9999; - float press = -9999; - float alt = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - // Read values - temp = bme_internal.readTemperature(); - if (isnan(temp)) temp = -9999; - humid = bme_internal.readHumidity(); - if (isnan(humid)) humid = -9999; - press = bme_internal.readPressure(); - if (isnan(press)) press = -9999; - alt = bme_internal.readAltitude(SEALEVELPRESSURE_HPA); - if (isnan(alt)) alt = -9999; - - // Assume that if all three are 0, really a failed response - // May also return a very negative temp when receiving a bad response - if ((temp == 0 && press == 0 && humid == 0) || temp < -40) { - MS_DBG(F("All values 0 or bad, assuming sensor non-response!")); - temp = -9999; - press = -9999; - humid = -9999; - alt = -9999; - } else { - success = true; - } - - MS_DBG(F(" Temperature:"), temp, F("°C")); - MS_DBG(F(" Humidity:"), humid, F("%RH")); - MS_DBG(F(" Barometric Pressure:"), press, F("Pa")); - MS_DBG(F(" Calculated Altitude:"), alt, F("m ASL")); +bool BoschBME280::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + bool success = false; + float temp = MS_INVALID_VALUE; + float humid = MS_INVALID_VALUE; + float press = MS_INVALID_VALUE; + float alt = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read values + temp = bme_internal.readTemperature(); + if (isnan(temp)) temp = MS_INVALID_VALUE; + humid = bme_internal.readHumidity(); + if (isnan(humid)) humid = MS_INVALID_VALUE; + press = bme_internal.readPressure(); + if (isnan(press)) press = MS_INVALID_VALUE; + alt = bme_internal.readAltitude(MS_SEA_LEVEL_PRESSURE_HPA); + if (isnan(alt)) alt = MS_INVALID_VALUE; + + MS_DBG(F(" Temperature:"), temp, F("°C")); + MS_DBG(F(" Humidity:"), humid, F("%RH")); + MS_DBG(F(" Barometric Pressure:"), press, F("Pa")); + MS_DBG(F(" Calculated Altitude:"), alt, F("m ASL")); + + bool values_ok = temp != MS_INVALID_VALUE && humid != MS_INVALID_VALUE && + press != MS_INVALID_VALUE && alt != MS_INVALID_VALUE; + // Assume that if temperature, pressure, and humidity are all 0, it's really + // a failed response. A temperature below -40°C (outside sensor range) also + // indicates a bad response. + if (!values_ok || (temp == 0 && press == 0 && humid == 0) || temp < -40) { + MS_DBG(F("Invalid reading (missing, all zeros, or out of range), " + "assuming sensor non-response!")); } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + verifyAndAddMeasurementResult(BME280_TEMP_VAR_NUM, temp); + verifyAndAddMeasurementResult(BME280_HUMIDITY_VAR_NUM, humid); + verifyAndAddMeasurementResult(BME280_PRESSURE_VAR_NUM, press); + verifyAndAddMeasurementResult(BME280_ALTITUDE_VAR_NUM, alt); + success = true; } - verifyAndAddMeasurementResult(BME280_TEMP_VAR_NUM, temp); - verifyAndAddMeasurementResult(BME280_HUMIDITY_VAR_NUM, humid); - verifyAndAddMeasurementResult(BME280_PRESSURE_VAR_NUM, press); - verifyAndAddMeasurementResult(BME280_ALTITUDE_VAR_NUM, alt); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } - -// cSpell:ignore SEALEVELPRESSURE_HPA diff --git a/src/sensors/BoschBME280.h b/src/sensors/BoschBME280.h index c05e28a1a..7142038fd 100644 --- a/src/sensors/BoschBME280.h +++ b/src/sensors/BoschBME280.h @@ -66,11 +66,10 @@ * [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Bosch-BME280-Datasheet.pdf) * * @section sensor_bme280_flags Build flags - * - ```-D SEALEVELPRESSURE_HPA``` + * - ```-D MS_SEA_LEVEL_PRESSURE_HPA``` * - use to adjust the sea level pressure used to calculate altitude from measured barometric pressure * - if not defined, 1013.25 is used - * - The same sea level pressure flag is used for both the BMP3xx and the BME280. - * Whatever you select will be used for both sensors. + * - The same sea level pressure flag is used for BME280, BMP3xx, and MS5837 sensors. * * @section sensor_bme280_ctor Sensor Constructors * {{ @ref BoschBME280::BoschBME280(int8_t, uint8_t, uint8_t) }} @@ -125,19 +124,6 @@ #define BME280_INC_CALC_VARIABLES 1 /**@}*/ -/** - * @anchor sensor_bme280_config - * @name Configuration Defines - * Defines to set the calibration of the calculated base pressure used to - * calculate altitude by the BME280. - */ -/**@{*/ -#if !defined(SEALEVELPRESSURE_HPA) || defined(DOXYGEN) -/// The atmospheric pressure at sea level -#define SEALEVELPRESSURE_HPA (1013.25) -#endif -/**@}*/ - /** * @anchor sensor_bme280_timing * @name Sensor Timing @@ -175,7 +161,11 @@ * {{ @ref BoschBME280_Temp::BoschBME280_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature in degrees Celsius. +#define BME280_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define BME280_TEMP_MAX_C 85.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define BME280_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. @@ -200,7 +190,11 @@ * {{ @ref BoschBME280_Humidity::BoschBME280_Humidity }} */ /**@{*/ -/// @brief Decimals places in string representation; humidity should have 3- +/// @brief Minimum relative humidity in percent. +#define BME280_HUMIDITY_MIN_RH 0.0 +/// @brief Maximum relative humidity in percent. +#define BME280_HUMIDITY_MAX_RH 100.0 +/// @brief Decimal places in string representation; humidity should have 3- /// resolution is 0.008 % RH (16 bit). #define BME280_HUMIDITY_RESOLUTION 3 /// @brief Sensor variable number; humidity is stored in sensorValues[1]. @@ -221,14 +215,18 @@ * @anchor sensor_bme280_pressure * @name Barometric Pressure * The barometric pressure variable from a Bosch BME280 - * - Range is 300 to 1100 hPa + * - Range is 300 to 1100 hPa (30000 to 110000 Pa) * - Absolute accuracy is ±1 hPa * - Relative accuracy is ±0.12 hPa * * {{ @ref BoschBME280_Pressure::BoschBME280_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; barometric pressure should +/// @brief Minimum barometric pressure in pascal. +#define BME280_PRESSURE_MIN_PA 30000.0 +/// @brief Maximum barometric pressure in pascal. +#define BME280_PRESSURE_MAX_PA 110000.0 +/// @brief Decimal places in string representation; barometric pressure should /// have 2. #define BME280_PRESSURE_RESOLUTION 2 /// @brief Sensor variable number; pressure is stored in sensorValues[2]. @@ -253,15 +251,17 @@ * {{ @ref BoschBME280_Altitude::BoschBME280_Altitude }} */ /**@{*/ -/// @brief Decimals places in string representation; altitude should have 0 - +/// @brief Decimal places in string representation; altitude should have 0 - /// resolution is 1m. #define BME280_ALTITUDE_RESOLUTION 0 /// @brief Sensor variable number; altitude is stored in sensorValues[3]. #define BME280_ALTITUDE_VAR_NUM 3 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); -/// "heightAboveSeaFloor" -#define BME280_ALTITUDE_VAR_NAME "heightAboveSeaFloor" +/// "altitude" +/// @remark In library versions 0.37.0 and earlier, this variable was +/// incorrectly named "heightAboveSeaFloor" +#define BME280_ALTITUDE_VAR_NAME "altitude" /// @brief Variable unit name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "meter" #define BME280_ALTITUDE_UNIT_NAME "meter" @@ -318,12 +318,9 @@ class BoschBME280 : public Sensor { /** * @brief Destroy the Bosch BME280 object */ - ~BoschBME280(); + ~BoschBME280() override = default; - /** - * @copydoc Sensor::wake() - */ - bool wake(void) override; + bool wake() override; /** * @brief Do any one-time preparations needed before the sensor will be able * to take readings. @@ -334,21 +331,15 @@ class BoschBME280 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - // bool startSingleMeasurement(void) override; // for forced mode - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** - * @brief Internal reference the the Adafruit BME object + * @brief Internal reference to the Adafruit BME object */ Adafruit_BME280 bme_internal; /** @@ -385,22 +376,13 @@ class BoschBME280_Temp : public Variable { */ explicit BoschBME280_Temp(BoschBME280* parentSense, const char* uuid = "", const char* varCode = BME280_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BME280_TEMP_VAR_NUM, - (uint8_t)BME280_TEMP_RESOLUTION, BME280_TEMP_VAR_NAME, - BME280_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new BoschBME280_Temp object. - * - * @note This must be tied with a parent BoschBME280 before it can be used. - */ - BoschBME280_Temp() - : Variable((uint8_t)BME280_TEMP_VAR_NUM, - (uint8_t)BME280_TEMP_RESOLUTION, BME280_TEMP_VAR_NAME, - BME280_TEMP_UNIT_NAME, BME280_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, BME280_TEMP_VAR_NUM, BME280_TEMP_RESOLUTION, + BME280_TEMP_VAR_NAME, BME280_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the BoschBME280_Temp object - no action needed. */ - ~BoschBME280_Temp() {} + ~BoschBME280_Temp() override = default; }; @@ -428,24 +410,13 @@ class BoschBME280_Humidity : public Variable { explicit BoschBME280_Humidity( BoschBME280* parentSense, const char* uuid = "", const char* varCode = BME280_HUMIDITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BME280_HUMIDITY_VAR_NUM, - (uint8_t)BME280_HUMIDITY_RESOLUTION, - BME280_HUMIDITY_VAR_NAME, BME280_HUMIDITY_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new BoschBME280_Humidity object. - * - * @note This must be tied with a parent BoschBME280 before it can be used. - */ - BoschBME280_Humidity() - : Variable((uint8_t)BME280_HUMIDITY_VAR_NUM, - (uint8_t)BME280_HUMIDITY_RESOLUTION, - BME280_HUMIDITY_VAR_NAME, BME280_HUMIDITY_UNIT_NAME, - BME280_HUMIDITY_DEFAULT_CODE) {} + : Variable(parentSense, BME280_HUMIDITY_VAR_NUM, + BME280_HUMIDITY_RESOLUTION, BME280_HUMIDITY_VAR_NAME, + BME280_HUMIDITY_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the BoschBME280_Humidity object - no action needed. */ - ~BoschBME280_Humidity() {} + ~BoschBME280_Humidity() override = default; }; @@ -473,20 +444,13 @@ class BoschBME280_Pressure : public Variable { explicit BoschBME280_Pressure( BoschBME280* parentSense, const char* uuid = "", const char* varCode = BME280_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BME280_PRESSURE_VAR_NUM, - (uint8_t)BME280_PRESSURE_RESOLUTION, - BME280_PRESSURE_VAR_NAME, BME280_PRESSURE_UNIT_NAME, varCode, - uuid) {} + : Variable(parentSense, BME280_PRESSURE_VAR_NUM, + BME280_PRESSURE_RESOLUTION, BME280_PRESSURE_VAR_NAME, + BME280_PRESSURE_UNIT_NAME, varCode, uuid) {} /** - * @brief Construct a new BoschBME280_Pressure object. - * - * @note This must be tied with a parent BoschBME280 before it can be used. + * @brief Destroy the BoschBME280_Pressure object - no action needed. */ - BoschBME280_Pressure() - : Variable((uint8_t)BME280_PRESSURE_VAR_NUM, - (uint8_t)BME280_PRESSURE_RESOLUTION, - BME280_PRESSURE_VAR_NAME, BME280_PRESSURE_UNIT_NAME, - BME280_PRESSURE_DEFAULT_CODE) {} + ~BoschBME280_Pressure() override = default; }; @@ -514,22 +478,15 @@ class BoschBME280_Altitude : public Variable { explicit BoschBME280_Altitude( BoschBME280* parentSense, const char* uuid = "", const char* varCode = BME280_ALTITUDE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BME280_ALTITUDE_VAR_NUM, - (uint8_t)BME280_ALTITUDE_RESOLUTION, - BME280_ALTITUDE_VAR_NAME, BME280_ALTITUDE_UNIT_NAME, varCode, - uuid) {} + : Variable(parentSense, BME280_ALTITUDE_VAR_NUM, + BME280_ALTITUDE_RESOLUTION, BME280_ALTITUDE_VAR_NAME, + BME280_ALTITUDE_UNIT_NAME, varCode, uuid) {} /** - * @brief Construct a new BoschBME280_Altitude object. - * - * @note This must be tied with a parent BoschBME280 before it can be used. + * @brief Destroy the BoschBME280_Altitude object - no action needed. */ - BoschBME280_Altitude() - : Variable((uint8_t)BME280_ALTITUDE_VAR_NUM, - (uint8_t)BME280_ALTITUDE_RESOLUTION, - BME280_ALTITUDE_VAR_NAME, BME280_ALTITUDE_UNIT_NAME, - BME280_ALTITUDE_DEFAULT_CODE) {} + ~BoschBME280_Altitude() override = default; }; /**@}*/ #endif // SRC_SENSORS_BOSCHBME280_H_ -// cSpell:ignore SEALEVELPRESSURE_HPA +// cSpell:words hectopascals diff --git a/src/sensors/BoschBMP3xx.cpp b/src/sensors/BoschBMP3xx.cpp index e0d8463eb..b1ef18126 100644 --- a/src/sensors/BoschBMP3xx.cpp +++ b/src/sensors/BoschBMP3xx.cpp @@ -25,18 +25,18 @@ BoschBMP3xx::BoschBMP3xx(int8_t powerPin, Mode mode, _filterCoeffEnum(filterCoeff), _standbyEnum(timeStandby), _i2cAddressHex(i2cAddressHex) {} -// Destructor -BoschBMP3xx::~BoschBMP3xx() {} -String BoschBMP3xx::getSensorLocation(void) { - String address = F("I2C_0x"); +String BoschBMP3xx::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool BoschBMP3xx::setup(void) { +bool BoschBMP3xx::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -68,8 +68,8 @@ bool BoschBMP3xx::setup(void) { // The enum values for oversampling match with the values of osr_p and osr_t auto typ_measurementTime_us = static_cast( 234 + - 1 * (392 + (pow(2, static_cast(_pressureOversampleEnum))) * 2020) + - 1 * (163 + (pow(2, static_cast(_tempOversampleEnum))) * 2020)); + 1 * (392 + (1U << static_cast(_pressureOversampleEnum)) * 2020) + + 1 * (163 + (1U << static_cast(_tempOversampleEnum)) * 2020)); float max_measurementTime_us = static_cast(typ_measurementTime_us) * 1.18; // Set the sensor measurement time to the safety-factored max time @@ -105,32 +105,31 @@ bool BoschBMP3xx::setup(void) { "recommended")); } - // convert the standby time enum value into the time between readouts from // the BMP's // ADC NOTE: The ADC will return repeated values if the ADC's ODR (output // data rate) is set faster than the actual measurement time, given // oversampling. - float _timeStandby_ms = 5.0f * pow(2, static_cast(_standbyEnum)); + float timeStandby_ms = 5.0f * (1U << static_cast(_standbyEnum)); // warn if an impossible sampling rate is selected - if ((_timeStandby_ms < max_measurementTime_us / 1000) && + if ((timeStandby_ms < max_measurementTime_us / 1000) && _mode == NORMAL_MODE) { - MS_DBG(F("The selected standby time of"), _timeStandby_ms, + MS_DBG(F("The selected standby time of"), timeStandby_ms, F("between ADC samples is less than the expected max of"), _measurementTime_ms, F("ms needed for temperature and pressure oversampling.")); // bump up the standby time to a possible value - while (5.0f * pow(2, static_cast(_standbyEnum)) < + while (5.0f * (1U << static_cast(_standbyEnum)) < max_measurementTime_us / 1000) { _standbyEnum = static_cast(static_cast(_standbyEnum) + 1); #if defined(MS_DEBUGGING_STD) - _timeStandby_ms = 5.0f * pow(2, static_cast(_standbyEnum)); + timeStandby_ms = 5.0f * (1U << static_cast(_standbyEnum)); #endif - MS_DBG(_standbyEnum, _timeStandby_ms, + MS_DBG(_standbyEnum, timeStandby_ms, static_cast(max_measurementTime_us / 1000)); } - MS_DBG(F("A standby time of"), _timeStandby_ms, + MS_DBG(F("A standby time of"), timeStandby_ms, F("ms between reading will be used.")); } @@ -138,13 +137,13 @@ bool BoschBMP3xx::setup(void) { // the value of the enum is the power of the number of samples if (_filterCoeffEnum != IIR_FILTER_OFF && _mode == NORMAL_MODE) { MS_DBG(F("BMP388/390's IIR filter will only be fully initialized"), - pow(2, static_cast(_filterCoeffEnum)) * _timeStandby_ms, + (1U << static_cast(_filterCoeffEnum)) * timeStandby_ms, F("ms after power on")); } if (_filterCoeffEnum != IIR_FILTER_OFF && _mode == FORCED_MODE) { MS_DBG( F("BMP388/390's IIR filter will only be fully initialized after"), - pow(2, static_cast(_filterCoeffEnum)), F("samples")); + (1U << static_cast(_filterCoeffEnum)), F("samples")); } if (_mode == FORCED_MODE) { @@ -163,33 +162,39 @@ bool BoschBMP3xx::setup(void) { "trim parameters")); success = bmp_internal.begin(_i2cAddressHex); - // Set up oversampling and filter initialization - // Using the filter selection recommended for "Weather monitoring - // (lowest power)" in table 10 of the sensor datasheet - - // Oversampling setting - MS_DBG(F("Sending BMP3xx oversampling settings")); - bmp_internal.setTempOversampling(_tempOversampleEnum); - bmp_internal.setPresOversampling(_pressureOversampleEnum); - - // Coefficient of the filter (in samples) - MS_DBG(F("Sending BMP3xx IIR Filter settings")); - bmp_internal.setIIRFilter(_filterCoeffEnum); - - MS_DBG(F("Setting sea level atmospheric pressure to"), - SEALEVELPRESSURE_HPA); - bmp_internal.setSeaLevelPressure(SEALEVELPRESSURE_HPA); - - // if we plan to operate in normal mode, set that up and begin sampling - // at the specified intervals - // if we're going to operate in forced mode, this isn't needed - if (_mode == NORMAL_MODE) { - // Standby time between samples in normal sampling mode - doesn't - // apply in forced mode - MS_DBG(F( - "Sending BMP3xx stand-by time and starting normal conversion")); - bmp_internal.setTimeStandby(_standbyEnum); - bmp_internal.startNormalConversion(); + if (success) { + // The sea level pressure is used for calculating altitude. This is + // stored as a parameter of the bmp_internal object and only needs + // to be sent once at setup, not repeated at wake, even if we're not + // continuously powered. + MS_DBG(F("Setting sea level atmospheric pressure to"), + MS_SEA_LEVEL_PRESSURE_HPA); + bmp_internal.setSeaLevelPressure(MS_SEA_LEVEL_PRESSURE_HPA); + + // Oversampling settings - these settings are sent to the BMP3xx and + // need to be repeated at wake if we're not continuously powered + MS_DBG(F("Sending BMP3xx oversampling settings")); + bmp_internal.setTempOversampling(_tempOversampleEnum); + bmp_internal.setPresOversampling(_pressureOversampleEnum); + + // if we plan to operate in normal mode, set that up, configure + // standby time and filtering, and begin sampling at the specified + // intervals if we're going to operate in forced mode, this isn't + // needed + if (_mode == NORMAL_MODE) { + // Coefficient of the filter (in samples) + MS_DBG(F("Sending BMP3xx IIR Filter settings")); + bmp_internal.setIIRFilter(_filterCoeffEnum); + // Standby time between samples in normal sampling mode - + // doesn't apply in forced mode + MS_DBG(F("Sending BMP3xx stand-by time and starting normal " + "conversion")); + bmp_internal.setTimeStandby(_standbyEnum); + bmp_internal.startNormalConversion(); + } + } else { + MS_DBG(F("Failed to connect to BMP3xx, attempt"), ntries + 1, + F("of 5")); } ntries++; } @@ -208,14 +213,18 @@ bool BoschBMP3xx::setup(void) { } -bool BoschBMP3xx::wake(void) { +bool BoschBMP3xx::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; - // if the power has gone off, we need to re-read the coefficients, - // we don't need to do anything if always powered. - // NOTE: only forced sampling is supported with switched power + // If the power has gone off, we need to re-read the coefficients and + // reconfigure the oversampling settings, since the sensor will have lost + // its memory of those things. If the power has not gone off, then the + // sensor will still have those things set and we don't need to do anything. + // NOTE: Only forced sampling with the IIR filter disabled is supported + // with switched power. There's no reason to resend the IIR filter and + // standby settings because those only apply to continuous power. if (_powerPin >= 0) { // Run begin fxn because it returns true or false // for success in contact // Make 5 attempts @@ -227,11 +236,8 @@ bool BoschBMP3xx::wake(void) { "trim parameters")); success = bmp_internal.begin(_i2cAddressHex); - // Set up oversampling and filter initialization - // Using the filter selection recommended for "Weather monitoring - // (lowest power)" in table 10 of the sensor datasheet - - // Oversampling setting + // Oversampling settings - these settings are sent to the BMP3xx and + // need to be repeated at wake if we're not continuously powered MS_DBG(F("Sending BMP3xx oversampling settings")); bmp_internal.setTempOversampling(_tempOversampleEnum); bmp_internal.setPresOversampling(_pressureOversampleEnum); @@ -253,7 +259,7 @@ bool BoschBMP3xx::wake(void) { } -bool BoschBMP3xx::startSingleMeasurement(void) { +bool BoschBMP3xx::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -270,51 +276,41 @@ bool BoschBMP3xx::startSingleMeasurement(void) { _millisMeasurementRequested = millis(); } + // NOTE: There's no way of knowing of a failure here so we always return + // true. + // There's no condition where we would need to bump the number of completed + // measurement attempts here. return true; } -bool BoschBMP3xx::addSingleMeasurementResult(void) { - bool success = false; +bool BoschBMP3xx::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Initialize float variables - float temp = -9999; - float press = -9999; - float alt = -9999; + bool success = false; + float temp = MS_INVALID_VALUE; + float press = MS_INVALID_VALUE; + float alt = MS_INVALID_VALUE; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Read values - success = bmp_internal.getMeasurements(temp, press, alt); - - // Assume that if all three are 0, really a failed response - // May also return a very negative temp when receiving a bad response - if (!success) { - temp = -9999; - press = -9999; - alt = -9999; - } + // Read values + success = bmp_internal.getMeasurements(temp, press, alt); + if (success) { + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); MS_DBG(F(" Temperature:"), temp, F("°C")); MS_DBG(F(" Barometric Pressure:"), press, F("Pa")); MS_DBG(F(" Calculated Altitude:"), alt, F("m ASL")); + verifyAndAddMeasurementResult(BMP3XX_TEMP_VAR_NUM, temp); + verifyAndAddMeasurementResult(BMP3XX_PRESSURE_VAR_NUM, press); + verifyAndAddMeasurementResult(BMP3XX_ALTITUDE_VAR_NUM, alt); } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F("Failed to read data from"), getSensorNameAndLocation()); } - verifyAndAddMeasurementResult(BMP3XX_TEMP_VAR_NUM, temp); - verifyAndAddMeasurementResult(BMP3XX_PRESSURE_VAR_NUM, press); - verifyAndAddMeasurementResult(BMP3XX_ALTITUDE_VAR_NUM, alt); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } -// cSpell:ignore oversample SEALEVELPRESSURE +// cSpell:words oversample diff --git a/src/sensors/BoschBMP3xx.h b/src/sensors/BoschBMP3xx.h index a3ec76000..1ac23fbab 100644 --- a/src/sensors/BoschBMP3xx.h +++ b/src/sensors/BoschBMP3xx.h @@ -6,10 +6,9 @@ * @author Sara Geleskie Damiano * * @brief Contains the BoschBMP3xx sensor subclass and the variable subclasses - * BoschBMP3xx_Temp, BoschBMP3xx_Humidity, BoschBMP3xx_Pressure, and - * BoschBMP3xx_Altitude. + * BoschBMP3xx_Temp, BoschBMP3xx_Pressure, and BoschBMP3xx_Altitude. * - * These are used for the Bosch BMP3xx digital pressure and humidity sensor. + * These are used for the Bosch BMP3xx digital pressure and temperature sensor. * * This depends on the [MartinL1's BMP388 * library](https://github.com/MartinL1/BMP388_DEV). @@ -111,11 +110,10 @@ * - [BMP388 Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Bosch-BMP388-Datasheet.pdf) * * @section sensor_bmp3xx_flags Build flags - * - ```-D SEALEVELPRESSURE_HPA``` + * - ```-D MS_SEA_LEVEL_PRESSURE_HPA``` * - use to adjust the sea level pressure used to calculate altitude from measured barometric pressure * - if not defined, 1013.25 is used - * - The same sea level pressure flag is used for both the BMP3xx and the BME280. - * Whatever you select will be used for both sensors. + * - The same sea level pressure flag is used for BMP3xx, BME280, and MS5837 sensors. * * @section sensor_bmp3xx_ctor Sensor Constructors * {{ @ref BoschBMP3xx::BoschBMP3xx(int8_t, Mode, Oversampling, Oversampling, IIRFilter, TimeStandby, uint8_t) }} @@ -164,24 +162,10 @@ /**@{*/ /// @brief Sensor::_numReturnedValues; the BMP3xx can report 3 values. #define BMP3XX_NUM_VARIABLES 3 -/// @brief Sensor::_incCalcValues; altitude is calculated within the Adafruit -/// library. +/// @brief Sensor::_incCalcValues; altitude is calculated from pressure. #define BMP3XX_INC_CALC_VARIABLES 1 /**@}*/ -/** - * @anchor sensor_bme3xx_config - * @name Configuration Defines - * Defines to set the calibration of the calculated base pressure used to - * calculate altitude by the BME3xx. - */ -/**@{*/ -#if !defined(SEALEVELPRESSURE_HPA) || defined(DOXYGEN) -/// The atmospheric pressure at sea level -#define SEALEVELPRESSURE_HPA (1013.25) -#endif -/**@}*/ - /** * @anchor sensor_bmp3xx_timing * @name Sensor Timing @@ -254,8 +238,12 @@ * {{ @ref BoschBMP3xx_Temp::BoschBMP3xx_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 5 - -/// resolution is 0.0.00015°C at the hightest oversampling. See table 7 in the +/// @brief Minimum temperature in degrees Celsius. +#define BMP3XX_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define BMP3XX_TEMP_MAX_C 85.0 +/// @brief Decimal places in string representation; temperature should have 5 - +/// resolution is 0.00015°C at the highest oversampling. See table 7 in the /// [sensor /// datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Bosch-BMP390-Datasheet.pdf) /// for resolution at all bandwidths. @@ -278,20 +266,24 @@ * @anchor sensor_bmp3xx_pressure * @name Barometric Pressure * The barometric pressure variable from a Bosch BMP388 or BMP390 - * - Range for both the BMP388 and BMP390 is 300‒1250 hPa + * - Range for both the BMP388 and BMP390 is 300‒1250 hPa (30000‒125000 Pa) * - Absolute accuracy is typ. ± 50 Pa (±0.50 hPa) * - Relative accuracy is typ. ± 3 Pa (±0.03 hPa), equiv. to ± 0.25 m * * {{ @ref BoschBMP3xx_Pressure::BoschBMP3xx_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; barometric pressure should +/// @brief Minimum barometric pressure in pascals. +#define BMP3XX_PRESSURE_MIN_PA 30000.0 +/// @brief Maximum barometric pressure in pascals. +#define BMP3XX_PRESSURE_MAX_PA 125000.0 +/// @brief Decimal places in string representation; barometric pressure should /// have 3. Resolution of output data in highest resolution mode at lowest /// bandwidth is 0.016 Pa. See table 6 in the [sensor /// datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Bosch-BMP390-Datasheet.pdf) /// for resolution at all bandwidths. #define BMP3XX_PRESSURE_RESOLUTION 3 -/// @brief Sensor variable number; pressure is stored in sensorValues[2]. +/// @brief Sensor variable number; pressure is stored in sensorValues[1]. #define BMP3XX_PRESSURE_VAR_NUM 1 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -313,15 +305,17 @@ * {{ @ref BoschBMP3xx_Altitude::BoschBMP3xx_Altitude }} */ /**@{*/ -/// @brief Decimals places in string representation; altitude should have 0 - +/// @brief Decimal places in string representation; altitude should have 0 - /// resolution is 1m. #define BMP3XX_ALTITUDE_RESOLUTION 0 -/// @brief Sensor variable number; altitude is stored in sensorValues[3]. +/// @brief Sensor variable number; altitude is stored in sensorValues[2]. #define BMP3XX_ALTITUDE_VAR_NUM 2 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); -/// "heightAboveSeaFloor" -#define BMP3XX_ALTITUDE_VAR_NAME "heightAboveSeaFloor" +/// "altitude" +/// @remark In library versions 0.37.0 and earlier, this variable was +/// incorrectly named "heightAboveSeaFloor" +#define BMP3XX_ALTITUDE_VAR_NAME "altitude" /// @brief Variable unit name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "meter" #define BMP3XX_ALTITUDE_UNIT_NAME "meter" @@ -369,11 +363,23 @@ class BoschBMP3xx : public Sensor { * - `OVERSAMPLING_X8` * - `OVERSAMPLING_X16`, * - `OVERSAMPLING_X32` + *
Optional with a default of `OVERSAMPLING_SKIP` (no oversampling). + * This is what is recommended in the datasheet for the lowest power use + * case including environmental and weather monitoring, but you may want to + * use oversampling for better resolution. + * See @ref sensor_bmp3xx_pressure_osr for recommended pressure oversampling + * settings. * * @param tempOversample Temperature oversampling setting *
Possible values are the same as those for pressureOversample. Using * temperature oversampling above X2 is not recommended as it does not * further improve pressure data quality. + *
Optional with a default of `OVERSAMPLING_SKIP` (no oversampling). + * This is what is recommended in the datasheet for the lowest power use + * case including environmental and weather monitoring, but you may want to + * use oversampling for better resolution. + * See @ref sensor_bmp3xx_temp_osr for recommended temperature oversampling + * settings. * * @param filterCoeff Coefficient of the infinite impulse response (IIR) * filter (in samples). @@ -422,6 +428,16 @@ class BoschBMP3xx : public Sensor { * @param i2cAddressHex The I2C address of the BMP3xx; must be either 0x76 * or 0x77. The default value is 0x76. * + * @note **BEHAVIORAL CHANGE**: In previous versions of this library, the + * default oversampling settings were `OVERSAMPLING_X16` for pressure and + * `OVERSAMPLING_X2` for temperature, which provided higher resolution but + * consumed more power. The defaults have been changed to + * `OVERSAMPLING_SKIP` for both parameters to optimize for lowest power + * consumption as recommended in the datasheet. To retain the previous + * higher-resolution behavior, explicitly pass `OVERSAMPLING_X16` for + * pressure and `OVERSAMPLING_X2` for temperature when constructing the + * sensor object. + * * @note For the BoschBMP3xx we do _**NOT**_ provide a * `measurementsToAverage` option. The sensor already provides on-board * averaging by way of oversampling and the IIR filter, so there is no @@ -431,20 +447,17 @@ class BoschBMP3xx : public Sensor { * @ref sensor_bmp3xx_filts_uses for recommended settings */ explicit BoschBMP3xx(int8_t powerPin, Mode mode = FORCED_MODE, - Oversampling pressureOversample = OVERSAMPLING_X16, - Oversampling tempOversample = OVERSAMPLING_X2, + Oversampling pressureOversample = OVERSAMPLING_SKIP, + Oversampling tempOversample = OVERSAMPLING_SKIP, IIRFilter filterCoeff = IIR_FILTER_OFF, TimeStandby timeStandby = TIME_STANDBY_10MS, uint8_t i2cAddressHex = 0x76); /** * @brief Destroy the Bosch BMP3xx object */ - ~BoschBMP3xx(); + ~BoschBMP3xx() override = default; - /** - * @copydoc Sensor::wake() - */ - bool wake(void) override; + bool wake() override; /** * @brief Do any one-time preparations needed before the sensor will be able * to take readings. @@ -455,24 +468,16 @@ class BoschBMP3xx : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::startSingleMeasurement() - */ - bool startSingleMeasurement(void) override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; private: /** - * @brief Internal reference the the BMP388_DEV object + * @brief Internal reference to the BMP388_DEV object */ BMP388_DEV bmp_internal; @@ -606,22 +611,13 @@ class BoschBMP3xx_Temp : public Variable { */ explicit BoschBMP3xx_Temp(BoschBMP3xx* parentSense, const char* uuid = "", const char* varCode = BMP3XX_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BMP3XX_TEMP_VAR_NUM, - (uint8_t)BMP3XX_TEMP_RESOLUTION, BMP3XX_TEMP_VAR_NAME, - BMP3XX_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new BoschBMP3xx_Temp object. - * - * @note This must be tied with a parent BoschBMP3xx before it can be used. - */ - BoschBMP3xx_Temp() - : Variable((uint8_t)BMP3XX_TEMP_VAR_NUM, - (uint8_t)BMP3XX_TEMP_RESOLUTION, BMP3XX_TEMP_VAR_NAME, - BMP3XX_TEMP_UNIT_NAME, BMP3XX_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, BMP3XX_TEMP_VAR_NUM, BMP3XX_TEMP_RESOLUTION, + BMP3XX_TEMP_VAR_NAME, BMP3XX_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the BoschBMP3xx_Temp object - no action needed. */ - ~BoschBMP3xx_Temp() {} + ~BoschBMP3xx_Temp() override = default; }; @@ -649,20 +645,13 @@ class BoschBMP3xx_Pressure : public Variable { explicit BoschBMP3xx_Pressure( BoschBMP3xx* parentSense, const char* uuid = "", const char* varCode = BMP3XX_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BMP3XX_PRESSURE_VAR_NUM, - (uint8_t)BMP3XX_PRESSURE_RESOLUTION, - BMP3XX_PRESSURE_VAR_NAME, BMP3XX_PRESSURE_UNIT_NAME, varCode, - uuid) {} + : Variable(parentSense, BMP3XX_PRESSURE_VAR_NUM, + BMP3XX_PRESSURE_RESOLUTION, BMP3XX_PRESSURE_VAR_NAME, + BMP3XX_PRESSURE_UNIT_NAME, varCode, uuid) {} /** - * @brief Construct a new BoschBMP3xx_Pressure object. - * - * @note This must be tied with a parent BoschBMP3xx before it can be used. + * @brief Destroy the BoschBMP3xx_Pressure object - no action needed. */ - BoschBMP3xx_Pressure() - : Variable((uint8_t)BMP3XX_PRESSURE_VAR_NUM, - (uint8_t)BMP3XX_PRESSURE_RESOLUTION, - BMP3XX_PRESSURE_VAR_NAME, BMP3XX_PRESSURE_UNIT_NAME, - BMP3XX_PRESSURE_DEFAULT_CODE) {} + ~BoschBMP3xx_Pressure() override = default; }; @@ -690,22 +679,15 @@ class BoschBMP3xx_Altitude : public Variable { explicit BoschBMP3xx_Altitude( BoschBMP3xx* parentSense, const char* uuid = "", const char* varCode = BMP3XX_ALTITUDE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BMP3XX_ALTITUDE_VAR_NUM, - (uint8_t)BMP3XX_ALTITUDE_RESOLUTION, - BMP3XX_ALTITUDE_VAR_NAME, BMP3XX_ALTITUDE_UNIT_NAME, varCode, - uuid) {} + : Variable(parentSense, BMP3XX_ALTITUDE_VAR_NUM, + BMP3XX_ALTITUDE_RESOLUTION, BMP3XX_ALTITUDE_VAR_NAME, + BMP3XX_ALTITUDE_UNIT_NAME, varCode, uuid) {} /** - * @brief Construct a new BoschBMP3xx_Altitude object. - * - * @note This must be tied with a parent BoschBMP3xx before it can be used. + * @brief Destroy the BoschBMP3xx_Altitude object - no action needed. */ - BoschBMP3xx_Altitude() - : Variable((uint8_t)BMP3XX_ALTITUDE_VAR_NUM, - (uint8_t)BMP3XX_ALTITUDE_RESOLUTION, - BMP3XX_ALTITUDE_VAR_NAME, BMP3XX_ALTITUDE_UNIT_NAME, - BMP3XX_ALTITUDE_DEFAULT_CODE) {} + ~BoschBMP3xx_Altitude() override = default; }; /**@}*/ #endif // SRC_SENSORS_BOSCHBMP3XX_H_ -// cSpell:ignore oversample SEALEVELPRESSURE osrs_p DDIO bmp3xxtimingTest +// cSpell:words oversample osrs_p DDIO bmp3xxtimingTest hectopascals diff --git a/src/sensors/CampbellClariVUE10.h b/src/sensors/CampbellClariVUE10.h index 5f38ceb57..d6483c0eb 100644 --- a/src/sensors/CampbellClariVUE10.h +++ b/src/sensors/CampbellClariVUE10.h @@ -119,7 +119,11 @@ * {{ @ref CampbellClariVUE10_Turbidity::CampbellClariVUE10_Turbidity }} */ /**@{*/ -/// @brief Decimals places in string representation; turbidity should have 1 +/// @brief Minimum turbidity in formazin nephelometric units. +#define CLARIVUE10_TURBIDITY_MIN_FNU 0.0 +/// @brief Maximum turbidity in formazin nephelometric units. +#define CLARIVUE10_TURBIDITY_MAX_FNU 4000.0 +/// @brief Decimal places in string representation; turbidity should have 1 /// (resolution is 0.2 FNU). #define CLARIVUE10_TURBIDITY_RESOLUTION 1 /// @brief Sensor variable number; turbidity is stored in sensorValues[0] @@ -146,7 +150,11 @@ * {{ @ref CampbellClariVUE10_Temp::CampbellClariVUE10_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature in degrees Celsius. +#define CLARIVUE10_TEMP_MIN_C -2.0 +/// @brief Maximum temperature in degrees Celsius. +#define CLARIVUE10_TEMP_MAX_C 40.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define CLARIVUE10_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[5]. @@ -169,10 +177,12 @@ * The error code variable from a Campbell ClariVUE10 * - Significance of error code values is unknown. * + * @todo Find and define minimum and maximum error code range + * * {{ @ref CampbellClariVUE10_ErrorCode::CampbellClariVUE10_ErrorCode }} */ /**@{*/ -/// @brief Decimals places in string representation; the error code has 0. +/// @brief Decimal places in string representation; the error code has 0. #define CLARIVUE10_ERRORCODE_RESOLUTION 0 /// @brief Sensor variable number; error code is stored in sensorValues[2] #define CLARIVUE10_ERRORCODE_VAR_NUM 6 @@ -251,11 +261,10 @@ class CampbellClariVUE10 : public SDI12Sensors { CLARIVUE10_WARM_UP_TIME_MS, CLARIVUE10_STABILIZATION_TIME_MS, CLARIVUE10_MEASUREMENT_TIME_MS, CLARIVUE10_EXTRA_WAKE_TIME_MS, CLARIVUE10_INC_CALC_VARIABLES) {} - /** * @brief Destroy the Campbell ClariVUE10 object */ - ~CampbellClariVUE10() {} + ~CampbellClariVUE10() override = default; }; @@ -283,27 +292,15 @@ class CampbellClariVUE10_Turbidity : public Variable { explicit CampbellClariVUE10_Turbidity( CampbellClariVUE10* parentSense, const char* uuid = "", const char* varCode = CLARIVUE10_TURBIDITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CLARIVUE10_TURBIDITY_VAR_NUM, - (uint8_t)CLARIVUE10_TURBIDITY_RESOLUTION, + : Variable(parentSense, CLARIVUE10_TURBIDITY_VAR_NUM, + CLARIVUE10_TURBIDITY_RESOLUTION, CLARIVUE10_TURBIDITY_VAR_NAME, CLARIVUE10_TURBIDITY_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellClariVUE10_Turbidity object. - * - * @note This must be tied with a parent CampbellClariVUE10 before it can be - * used. - */ - CampbellClariVUE10_Turbidity() - : Variable((uint8_t)CLARIVUE10_TURBIDITY_VAR_NUM, - (uint8_t)CLARIVUE10_TURBIDITY_RESOLUTION, - CLARIVUE10_TURBIDITY_VAR_NAME, - CLARIVUE10_TURBIDITY_UNIT_NAME, - CLARIVUE10_TURBIDITY_DEFAULT_CODE) {} /** * @brief Destroy the CampbellClariVUE10_Turbidity object - no action * needed. */ - ~CampbellClariVUE10_Turbidity() {} + ~CampbellClariVUE10_Turbidity() override = default; }; @@ -331,25 +328,13 @@ class CampbellClariVUE10_Temp : public Variable { explicit CampbellClariVUE10_Temp( CampbellClariVUE10* parentSense, const char* uuid = "", const char* varCode = CLARIVUE10_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CLARIVUE10_TEMP_VAR_NUM, - (uint8_t)CLARIVUE10_TEMP_RESOLUTION, - CLARIVUE10_TEMP_VAR_NAME, CLARIVUE10_TEMP_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new CampbellClariVUE10_Temp object. - * - * @note This must be tied with a parent CampbellClariVUE10 before it can be - * used. - */ - CampbellClariVUE10_Temp() - : Variable((uint8_t)CLARIVUE10_TEMP_VAR_NUM, - (uint8_t)CLARIVUE10_TEMP_RESOLUTION, - CLARIVUE10_TEMP_VAR_NAME, CLARIVUE10_TEMP_UNIT_NAME, - CLARIVUE10_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, CLARIVUE10_TEMP_VAR_NUM, + CLARIVUE10_TEMP_RESOLUTION, CLARIVUE10_TEMP_VAR_NAME, + CLARIVUE10_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the CampbellClariVUE10_Temp object - no action needed. */ - ~CampbellClariVUE10_Temp() {} + ~CampbellClariVUE10_Temp() override = default; }; @@ -377,27 +362,15 @@ class CampbellClariVUE10_ErrorCode : public Variable { explicit CampbellClariVUE10_ErrorCode( CampbellClariVUE10* parentSense, const char* uuid = "", const char* varCode = CLARIVUE10_ERRORCODE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CLARIVUE10_ERRORCODE_VAR_NUM, - (uint8_t)CLARIVUE10_ERRORCODE_RESOLUTION, + : Variable(parentSense, CLARIVUE10_ERRORCODE_VAR_NUM, + CLARIVUE10_ERRORCODE_RESOLUTION, CLARIVUE10_ERRORCODE_VAR_NAME, CLARIVUE10_ERRORCODE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellClariVUE10_ErrorCode object. - * - * @note This must be tied with a parent CampbellClariVUE10 before it can be - * used. - */ - CampbellClariVUE10_ErrorCode() - : Variable((uint8_t)CLARIVUE10_ERRORCODE_VAR_NUM, - (uint8_t)CLARIVUE10_ERRORCODE_RESOLUTION, - CLARIVUE10_ERRORCODE_VAR_NAME, - CLARIVUE10_ERRORCODE_UNIT_NAME, - CLARIVUE10_ERRORCODE_DEFAULT_CODE) {} /** * @brief Destroy the CampbellClariVUE10_ErrorCode object - no action * needed. */ - ~CampbellClariVUE10_ErrorCode() {} + ~CampbellClariVUE10_ErrorCode() override = default; }; /**@}*/ #endif // SRC_SENSORS_CAMPBELLCLARIVUE10_H_ diff --git a/src/sensors/CampbellOBS3.cpp b/src/sensors/CampbellOBS3.cpp index cec02baf6..238265a41 100644 --- a/src/sensors/CampbellOBS3.cpp +++ b/src/sensors/CampbellOBS3.cpp @@ -10,111 +10,101 @@ #include "CampbellOBS3.h" -#include +#include "TIADS1x15.h" // The constructor - need the power pin, the data pin, and the calibration info -CampbellOBS3::CampbellOBS3(int8_t powerPin, uint8_t adsChannel, +CampbellOBS3::CampbellOBS3(int8_t powerPin, int8_t analogChannel, float x2_coeff_A, float x1_coeff_B, float x0_coeff_C, - uint8_t i2cAddress, uint8_t measurementsToAverage) + uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("CampbellOBS3", OBS3_NUM_VARIABLES, OBS3_WARM_UP_TIME_MS, - OBS3_STABILIZATION_TIME_MS, OBS3_MEASUREMENT_TIME_MS, powerPin, -1, - measurementsToAverage, OBS3_INC_CALC_VARIABLES), - _adsChannel(adsChannel), + OBS3_STABILIZATION_TIME_MS, OBS3_MEASUREMENT_TIME_MS, powerPin, + analogChannel, measurementsToAverage, OBS3_INC_CALC_VARIABLES), _x2_coeff_A(x2_coeff_A), _x1_coeff_B(x1_coeff_B), _x0_coeff_C(x0_coeff_C), - _i2cAddress(i2cAddress) {} + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new TIADS1x15Reader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} + // Destructor -CampbellOBS3::~CampbellOBS3() {} - - -String CampbellOBS3::getSensorLocation(void) { -#ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); -#else - String sensorLocation = F("ADS1015_0x"); -#endif - sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("_Channel"); - sensorLocation += String(_adsChannel); - return sensorLocation; +CampbellOBS3::~CampbellOBS3() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String CampbellOBS3::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + return _analogVoltageReader->getAnalogLocation(_dataPin, -1); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } } -bool CampbellOBS3::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float calibResult = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. -#ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version -#else - Adafruit_ADS1015 ads; // Use this for the 12-bit version -#endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - // Bump the gain up to 1x = +/- 4.096V range - // Sensor return range is 0-2.5V, but the next gain option is 2x which - // only allows up to 2.048V - ads.setGain(GAIN_ONE); - // Begin ADC - ads.begin(_i2cAddress); - - // Print out the calibration curve - MS_DBG(F(" Input calibration Curve:"), _x2_coeff_A, F("x^2 +"), - _x1_coeff_B, F("x +"), _x0_coeff_C); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the ADC raw count - adcCounts = ads.readADC_SingleEnded(_adsChannel); - // Convert ADC raw counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_SingleEnded("), _adsChannel, F("):"), - adcVoltage); - - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - // Skip results out of range - // Apply the unique calibration curve for the given sensor - calibResult = (_x2_coeff_A * sq(adcVoltage)) + - (_x1_coeff_B * adcVoltage) + _x0_coeff_C; - MS_DBG(F(" calibResult:"), calibResult); - } else { // set invalid voltages back to -9999 - adcVoltage = -9999; +bool CampbellOBS3::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); } } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); } - verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult); - verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage); + return sensorSetupSuccess && analogVoltageReaderSuccess; +} + - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); +bool CampbellOBS3::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - return true; + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + + // Print out the calibration curve + MS_DBG(F(" Input calibration Curve:"), _x2_coeff_A, F("x^2 +"), + _x1_coeff_B, F("x +"), _x0_coeff_C); + + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the single-ended analog voltage using the AnalogVoltageReader + // interface. + // NOTE: All implementations of the AnalogVoltageReader class validate both + // the input channel and the resulting voltage, so we can trust that a + // successful read will give us a valid voltage value to work with. + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + adcVoltage); + if (success) { + // Apply the unique calibration curve for the given sensor + float calibResult = (_x2_coeff_A * sq(adcVoltage)) + + (_x1_coeff_B * adcVoltage) + _x0_coeff_C; + MS_DBG(F(" calibResult:"), calibResult); + verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult); + verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage); } else { - return false; + MS_DBG(F(" Failed to get valid voltage from analog reader")); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/CampbellOBS3.h b/src/sensors/CampbellOBS3.h index 50634f782..c2641f8b3 100644 --- a/src/sensors/CampbellOBS3.h +++ b/src/sensors/CampbellOBS3.h @@ -9,8 +9,6 @@ * CampbellOBS3_Turbidity and CampbellOBS3_Voltage. * * These are used for the Campbell Scientific OBS-3+. - * - * This depends on the Adafruit ADS1X15 v2.x library */ /* clang-format off */ /** @@ -50,10 +48,6 @@ * for the calibrated turbidity. To get both high and low range values, create * two sensor objects! * - * @section sensor_obs3_flags Build flags - * - ```-D MS_USE_ADS1015``` - * - switches from the 16-bit ADS1115 to the 12 bit ADS1015 - * * @section sensor_obs3_ctor Sensor Constructor * {{ @ref CampbellOBS3::CampbellOBS3 }} * @@ -89,6 +83,9 @@ #include "VariableBase.h" #include "SensorBase.h" +// Forward declaration +class AnalogVoltageReader; + /** @ingroup sensor_obs3 */ /**@{*/ @@ -112,16 +109,6 @@ #define OBS3_INC_CALC_VARIABLES 1 /**@}*/ -/** - * @anchor sensor_obs3_config - * @name Configuration Defines - * Defines to set the address of the ADD. - */ -/**@{*/ -/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 -/**@}*/ - /** * @anchor sensor_obs3_timing * @name Sensor Timing @@ -168,13 +155,17 @@ * {{ @ref CampbellOBS3_Turbidity::CampbellOBS3_Turbidity }} */ /**@{*/ -/// Variable number; turbidity is stored in sensorValues[0]. +/// @brief Minimum turbidity in nephelometric turbidity units. +#define OBS3_TURB_MIN_NTU 0.0 +/// @brief Maximum turbidity in nephelometric turbidity units. +#define OBS3_TURB_MAX_NTU 4000.0 +/// @brief Variable number; turbidity is stored in sensorValues[0]. #define OBS3_TURB_VAR_NUM 0 #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; turbidity should have 1. +/// @brief Decimal places in string representation; turbidity should have 1. #define OBS3_RESOLUTION 1 #else -/// @brief Decimals places in string representation; turbidity should have 5. +/// @brief Decimal places in string representation; turbidity should have 5. #define OBS3_RESOLUTION 5 #endif /// @brief Variable name in @@ -204,7 +195,11 @@ * {{ @ref CampbellOBS3_Voltage::CampbellOBS3_Voltage }} */ /**@{*/ -/// Variable number; voltage is stored in sensorValues[1]. +/// @brief Minimum voltage in volts. +#define OBS3_VOLTAGE_MIN_V 0.0 +/// @brief Maximum voltage in volts. +#define OBS3_VOLTAGE_MAX_V 2.5 +/// @brief Variable number; voltage is stored in sensorValues[1]. #define OBS3_VOLTAGE_VAR_NUM 1 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -217,12 +212,12 @@ #define OBS3_VOLTAGE_DEFAULT_CODE "OBS3Voltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1. +/// @brief Decimal places in string representation; voltage should have 1. /// - Resolution: /// - 16-bit ADC (ADS1115): 0.125 mV #define OBS3_VOLTAGE_RESOLUTION 1 #else -/// @brief Decimals places in string representation; voltage should have 4. +/// @brief Decimal places in string representation; voltage should have 4. /// - Resolution: /// - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): 2 mV #define OBS3_VOLTAGE_RESOLUTION 4 @@ -244,65 +239,82 @@ /* clang-format on */ class CampbellOBS3 : public Sensor { public: - // The constructor - need the power pin, the ADS1X15 data channel, and the - // calibration info /** * @brief Construct a new Campbell OBS3 object - need the power pin, the - * ADS1X15 data channel, and the calibration info. + * analog data channel, and the calibration info. * - * @note ModularSensors only supports connecting the ADS1x15 to the primary - * hardware I2C instance defined in the Arduino core. Connecting the ADS to - * a secondary hardware or software I2C instance is *not* supported! + * By default, this constructor will internally create a default + * AnalogVoltageReader implementation for voltage readings, but a pointer to + * a custom AnalogVoltageReader object can be passed in if desired. * * @param powerPin The pin on the mcu controlling power to the OBS3+ * Use -1 if it is continuously powered. - * - The ADS1x15 requires an input voltage of 2.0-5.5V, but this library - * assumes the ADS is powered with 3.3V. * - The OBS-3 itself requires a 5-15V power supply, which can be turned off * between measurements. - * @param adsChannel The analog data channel _on the TI ADS1115_ that the - * OBS3 is connected to (0-3). + * @param analogChannel The analog data channel or processor pin for voltage + * measurements. The significance of the channel number depends on the + * specific AnalogVoltageReader implementation used for voltage readings. + * For example, with the default TI ADS1x15, this would be the ADC channel + * (0-3) that the sensor is connected to. Negative or invalid channel + * numbers are not clamped and will cause the reading to fail and emit a + * warning. * @param x2_coeff_A The x2 (A) coefficient for the calibration _in volts_ * @param x1_coeff_B The x (B) coefficient for the calibration _in volts_ * @param x0_coeff_C The x0 (C) coefficient for the calibration _in volts_ - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses a TI ADS1115 or ADS1015. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. + * + * @warning In library versions 0.37.0 and earlier, a different constructor + * was used that the I2C address of the ADS1x15 was an optional input + * parameter which came *before* the optional input parameter for the number + * of measurements to average. The input parameter for the I2C address has + * been *removed* and the input for the number of measurements to average + * has been moved up in the order! Please update your code to prevent a + * compiler error or a silent reading error. */ - CampbellOBS3(int8_t powerPin, uint8_t adsChannel, float x2_coeff_A, + CampbellOBS3(int8_t powerPin, int8_t analogChannel, float x2_coeff_A, float x1_coeff_B, float x0_coeff_C, - uint8_t i2cAddress = ADS1115_ADDRESS, - uint8_t measurementsToAverage = 1); + uint8_t measurementsToAverage = 1, + AnalogVoltageReader* analogVoltageReader = nullptr); /** * @brief Destroy the Campbell OBS3 object */ - ~CampbellOBS3(); + ~CampbellOBS3() override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + CampbellOBS3(const CampbellOBS3&) = delete; + CampbellOBS3& operator=(const CampbellOBS3&) = delete; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete move constructor and move assignment operator + CampbellOBS3(CampbellOBS3&&) = delete; + CampbellOBS3& operator=(CampbellOBS3&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: - /** - * @brief Internal reference to the ADS channel number of the Campbell OBS - * 3+ - */ - uint8_t _adsChannel; - float _x2_coeff_A; ///< Internal reference to the x^2 coefficient - float _x1_coeff_B; ///< Internal reference to the x coefficient - float _x0_coeff_C; ///< Internal reference to the x^0 coefficient - /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 - */ - uint8_t _i2cAddress; + /// @brief The x^2 (A) calibration coefficient + float _x2_coeff_A; + /// @brief The x^1 (B) calibration coefficient + float _x1_coeff_B; + /// @brief The x^0 (C) calibration coefficient + float _x0_coeff_C; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -333,22 +345,12 @@ class CampbellOBS3_Turbidity : public Variable { explicit CampbellOBS3_Turbidity( CampbellOBS3* parentSense, const char* uuid = "", const char* varCode = OBS3_TURB_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)OBS3_TURB_VAR_NUM, - (uint8_t)OBS3_RESOLUTION, OBS3_TURB_VAR_NAME, - OBS3_TURB_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellOBS3_Turbidity object. - * - * @note This must be tied with a parent CampbellOBS3 before it can be used. - */ - CampbellOBS3_Turbidity() - : Variable((uint8_t)OBS3_TURB_VAR_NUM, (uint8_t)OBS3_RESOLUTION, - OBS3_TURB_VAR_NAME, OBS3_TURB_UNIT_NAME, - OBS3_TURB_DEFAULT_CODE) {} + : Variable(parentSense, OBS3_TURB_VAR_NUM, OBS3_RESOLUTION, + OBS3_TURB_VAR_NAME, OBS3_TURB_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Campbell OBS3 Turbidity object */ - ~CampbellOBS3_Turbidity() {} + ~CampbellOBS3_Turbidity() override = default; }; @@ -378,22 +380,13 @@ class CampbellOBS3_Voltage : public Variable { explicit CampbellOBS3_Voltage( CampbellOBS3* parentSense, const char* uuid = "", const char* varCode = OBS3_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)OBS3_VOLTAGE_VAR_NUM, - (uint8_t)OBS3_VOLTAGE_RESOLUTION, OBS3_VOLTAGE_VAR_NAME, - OBS3_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellOBS3_Voltage object. - * - * @note This must be tied with a parent CampbellOBS3 before it can be used. - */ - CampbellOBS3_Voltage() - : Variable((uint8_t)OBS3_VOLTAGE_VAR_NUM, - (uint8_t)OBS3_VOLTAGE_RESOLUTION, OBS3_VOLTAGE_VAR_NAME, - OBS3_VOLTAGE_UNIT_NAME, OBS3_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, OBS3_VOLTAGE_VAR_NUM, OBS3_VOLTAGE_RESOLUTION, + OBS3_VOLTAGE_VAR_NAME, OBS3_VOLTAGE_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the CampbellOBS3_Voltage object - no action needed. */ - ~CampbellOBS3_Voltage() {} + ~CampbellOBS3_Voltage() override = default; }; /**@}*/ #endif // SRC_SENSORS_CAMPBELLOBS3_H_ diff --git a/src/sensors/CampbellRainVUE10.h b/src/sensors/CampbellRainVUE10.h index f6e0b34af..571f19b4d 100644 --- a/src/sensors/CampbellRainVUE10.h +++ b/src/sensors/CampbellRainVUE10.h @@ -122,7 +122,13 @@ * {{ @ref CampbellRainVUE10_Precipitation::CampbellRainVUE10_Precipitation }} */ /**@{*/ -/// @brief Decimals places in string representation; depth should have 2 +/// @brief Minimum precipitation depth in inches. +#define RAINVUE10_PRECIPITATION_MIN_IN 0.0 +/// @brief Maximum precipitation depth in inches. +/// @note There isn't a maximum accumulated depth; this is a very high number to +/// allow for almost a year of rain in PA. +#define RAINVUE10_PRECIPITATION_MAX_IN 50.0 +/// @brief Decimal places in string representation; depth should have 2 /// (resolution is 0.01 inches). #define RAINVUE10_PRECIPITATION_RESOLUTION 2 /// @brief Sensor variable number; precipitation is stored in sensorValues[0] @@ -145,10 +151,13 @@ * Defines for tip count variable from a tipping bucket counter * - Range and accuracy depend on the tipping bucket used. * + * @todo Find and define minimum and maximum tip count range for the Campbell + * RainVUE10. + * * {{ @ref CampbellRainVUE10_Tips::CampbellRainVUE10_Tips }} */ /**@{*/ -/// @brief Decimals places in string representation; the number of tips should +/// @brief Decimal places in string representation; the number of tips should /// have 0 - resolution is 1 tip. #define RAINVUE10_TIPS_RESOLUTION 0 /// @brief Sensor variable number; tips is stored in sensorValues[1]. @@ -183,7 +192,13 @@ * {{ @ref CampbellRainVUE10_RainRateAve::CampbellRainVUE10_RainRateAve }} */ /**@{*/ -/// @brief Decimals places in string representation; the rainfall intensity +/// @brief Minimum rainfall rate in inches per hour. +/// @note The minimum rate when it's raining is 0.01 in/h, but is 0 when not +/// raining. +#define RAINVUE10_RAINRATEAVE_MIN_INPH 0 +/// @brief Maximum rainfall rate in inches per hour. +#define RAINVUE10_RAINRATEAVE_MAX_INPH 39.4 +/// @brief Decimal places in string representation; the rainfall intensity /// has 2. #define RAINVUE10_RAINRATEAVE_RESOLUTION 2 /// @brief Sensor variable number; average intensity is stored in @@ -206,11 +221,17 @@ * @name Rainfall Rate Maximum * The maximum rainfall rate variable from a Campbell RainVUE10, * defined as maximum precipitation intensity since last measurement. - * - Range & Accuracy same as for sensor_rainvue_rainratemax + * - Range is 0.01 to 1000 mm/h (0.0004 to 39.4 in./h) + * - Range & Accuracy same as for sensor_rainvue_rainrateave + * * {{ @ref CampbellRainVUE10_RainRateMax::CampbellRainVUE10_RainRateMax }} */ /**@{*/ -/// @brief Decimals places in string representation; the rainfall intensity +/// @brief Minimum rainfall rate; 0.0004 in./h (0.01 mm/h) +#define RAINVUE10_RAINRATEMAX_MIN_INPH 0.0004 +/// @brief Maximum rainfall rate; 39.4 in./h (1000 mm/h) +#define RAINVUE10_RAINRATEMAX_MAX_INPH 39.4 +/// @brief Decimal places in string representation; the rainfall intensity /// has 2. #define RAINVUE10_RAINRATEMAX_RESOLUTION 2 /// @brief Sensor variable number; average intensity is stored in @@ -224,7 +245,7 @@ /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); /// "inchPerHour" #define RAINVUE10_RAINRATEMAX_UNIT_NAME "inchPerHour" -/// @brief Default variable short code; "RainVUERateAve" +/// @brief Default variable short code; "RainVUERateMax" #define RAINVUE10_RAINRATEMAX_DEFAULT_CODE "RainVUERateMax" /**@}*/ @@ -290,11 +311,10 @@ class CampbellRainVUE10 : public SDI12Sensors { RAINVUE10_WARM_UP_TIME_MS, RAINVUE10_STABILIZATION_TIME_MS, RAINVUE10_MEASUREMENT_TIME_MS, RAINVUE10_EXTRA_WAKE_TIME_MS, RAINVUE10_INC_CALC_VARIABLES) {} - /** * @brief Destroy the Campbell RainVUE10 object */ - ~CampbellRainVUE10() {} + ~CampbellRainVUE10() override = default; }; @@ -322,27 +342,15 @@ class CampbellRainVUE10_Precipitation : public Variable { explicit CampbellRainVUE10_Precipitation( CampbellRainVUE10* parentSense, const char* uuid = "", const char* varCode = RAINVUE10_PRECIPITATION_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)RAINVUE10_PRECIPITATION_VAR_NUM, - (uint8_t)RAINVUE10_PRECIPITATION_RESOLUTION, + : Variable(parentSense, RAINVUE10_PRECIPITATION_VAR_NUM, + RAINVUE10_PRECIPITATION_RESOLUTION, RAINVUE10_PRECIPITATION_VAR_NAME, RAINVUE10_PRECIPITATION_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellRainVUE10_Precipitation object. - * - * @note This must be tied with a parent CampbellRainVUE10 before it can be - * used. - */ - CampbellRainVUE10_Precipitation() - : Variable((uint8_t)RAINVUE10_PRECIPITATION_VAR_NUM, - (uint8_t)RAINVUE10_PRECIPITATION_RESOLUTION, - RAINVUE10_PRECIPITATION_VAR_NAME, - RAINVUE10_PRECIPITATION_UNIT_NAME, - RAINVUE10_PRECIPITATION_DEFAULT_CODE) {} /** * @brief Destroy the CampbellRainVUE10_Precipitation object - no action * needed. */ - ~CampbellRainVUE10_Precipitation() {} + ~CampbellRainVUE10_Precipitation() override = default; }; @@ -370,23 +378,13 @@ class CampbellRainVUE10_Tips : public Variable { explicit CampbellRainVUE10_Tips( CampbellRainVUE10* parentSense, const char* uuid = "", const char* varCode = RAINVUE10_TIPS_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)RAINVUE10_TIPS_VAR_NUM, - (uint8_t)RAINVUE10_TIPS_RESOLUTION, RAINVUE10_TIPS_VAR_NAME, + : Variable(parentSense, RAINVUE10_TIPS_VAR_NUM, + RAINVUE10_TIPS_RESOLUTION, RAINVUE10_TIPS_VAR_NAME, RAINVUE10_TIPS_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellRainVUE10_Tips object. - * - * @note This must be tied with a parent CampbellRainVUE10 before it can be - * used. - */ - CampbellRainVUE10_Tips() - : Variable((uint8_t)RAINVUE10_TIPS_VAR_NUM, - (uint8_t)RAINVUE10_TIPS_RESOLUTION, RAINVUE10_TIPS_VAR_NAME, - RAINVUE10_TIPS_UNIT_NAME, RAINVUE10_TIPS_DEFAULT_CODE) {} /** * @brief Destroy the CampbellRainVUE10_Tips object - no action needed. */ - ~CampbellRainVUE10_Tips() {} + ~CampbellRainVUE10_Tips() override = default; }; @@ -409,32 +407,20 @@ class CampbellRainVUE10_RainRateAve : public Variable { * @param uuid A universally unique identifier (UUID or GUID) for the * variable; optional with the default value of an empty string. * @param varCode A short code to help identify the variable in files; - * optional with a default value of "RainVUEError". + * optional with a default value of "RainVUERateAve". */ explicit CampbellRainVUE10_RainRateAve( CampbellRainVUE10* parentSense, const char* uuid = "", const char* varCode = RAINVUE10_RAINRATEAVE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)RAINVUE10_RAINRATEAVE_VAR_NUM, - (uint8_t)RAINVUE10_RAINRATEAVE_RESOLUTION, + : Variable(parentSense, RAINVUE10_RAINRATEAVE_VAR_NUM, + RAINVUE10_RAINRATEAVE_RESOLUTION, RAINVUE10_RAINRATEAVE_VAR_NAME, RAINVUE10_RAINRATEAVE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellRainVUE10_RainRateAve object. - * - * @note This must be tied with a parent CampbellRainVUE10 before it can be - * used. - */ - CampbellRainVUE10_RainRateAve() - : Variable((uint8_t)RAINVUE10_RAINRATEAVE_VAR_NUM, - (uint8_t)RAINVUE10_RAINRATEAVE_RESOLUTION, - RAINVUE10_RAINRATEAVE_VAR_NAME, - RAINVUE10_RAINRATEAVE_UNIT_NAME, - RAINVUE10_RAINRATEAVE_DEFAULT_CODE) {} /** * @brief Destroy the CampbellRainVUE10_RainRateAve object - no action * needed. */ - ~CampbellRainVUE10_RainRateAve() {} + ~CampbellRainVUE10_RainRateAve() override = default; }; /* clang-format off */ @@ -458,34 +444,22 @@ class CampbellRainVUE10_RainRateMax : public Variable { * @param uuid A universally unique identifier (UUID or GUID) for the * variable; optional with the default value of an empty string. * @param varCode A short code to help identify the variable in files; - * optional with a default value of "RainVUERateAve". + * optional with a default value of "RainVUERateMax". */ explicit CampbellRainVUE10_RainRateMax( CampbellRainVUE10* parentSense, const char* uuid = "", const char* varCode = RAINVUE10_RAINRATEMAX_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)RAINVUE10_RAINRATEMAX_VAR_NUM, - (uint8_t)RAINVUE10_RAINRATEMAX_RESOLUTION, + : Variable(parentSense, RAINVUE10_RAINRATEMAX_VAR_NUM, + RAINVUE10_RAINRATEMAX_RESOLUTION, RAINVUE10_RAINRATEMAX_VAR_NAME, RAINVUE10_RAINRATEMAX_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new CampbellRainVUE10_RainRateMax object. - * - * @note This must be tied with a parent CampbellRainVUE10 before it can be - * used. - */ - CampbellRainVUE10_RainRateMax() - : Variable((uint8_t)RAINVUE10_RAINRATEMAX_VAR_NUM, - (uint8_t)RAINVUE10_RAINRATEMAX_RESOLUTION, - RAINVUE10_RAINRATEMAX_VAR_NAME, - RAINVUE10_RAINRATEMAX_UNIT_NAME, - RAINVUE10_RAINRATEMAX_DEFAULT_CODE) {} /** * @brief Destroy the CampbellRainVUE10_RainRateMax object - no action * needed. */ - ~CampbellRainVUE10_RainRateMax() {} + ~CampbellRainVUE10_RainRateMax() override = default; }; /**@}*/ #endif // SRC_SENSORS_CAMPBELLRAINVUE10_H_ -// cSpell:ignore RAINRATEAVE RAINRATEMAX +// cSpell:words RAINRATEAVE RAINRATEMAX INPH diff --git a/src/sensors/Decagon5TM.cpp b/src/sensors/Decagon5TM.cpp index 150e27962..31cf6edd0 100644 --- a/src/sensors/Decagon5TM.cpp +++ b/src/sensors/Decagon5TM.cpp @@ -19,17 +19,17 @@ bool Decagon5TM::getResults(bool verify_crc) { float temp = sensorValues[TM_TEMP_VAR_NUM]; // Set up the float variables for calculated variable - float VWC = -9999; + float VWC = MS_INVALID_VALUE; // Calculate the VWC from EA using the Topp equation // range check if (ea < 0 || ea > 350) { MS_DBG(F("WARNING: Ea results out of range (0-350)! Cannot calculate " "VWC")); - ea = -9999; + ea = MS_INVALID_VALUE; } // calculate - if (ea != -9999) { + if (ea != MS_INVALID_VALUE) { VWC = (4.3e-6 * (ea * ea * ea)) - (5.5e-4 * (ea * ea)) + (2.92e-2 * ea) - 5.3e-2; VWC *= 100; // Convert to actual percent @@ -46,9 +46,10 @@ bool Decagon5TM::getResults(bool verify_crc) { // VWC = 3.879e-4*raw-0.6956; // equation for mineral soils - // range check on temp; range is - 40°C to + 50°C + // range check on temp; range is - 40°C to + 50°C (with 10°C margin beyond + // spec) if (temp < -50 || temp > 60) { - temp = -9999; + temp = MS_INVALID_VALUE; MS_DBG(F("WARNING: temperature results out of range (-50-60)!")); } @@ -57,5 +58,5 @@ bool Decagon5TM::getResults(bool verify_crc) { verifyAndAddMeasurementResult(TM_EA_VAR_NUM, ea); verifyAndAddMeasurementResult(TM_VWC_VAR_NUM, VWC); - return success && temp != -9999; + return success && temp != MS_INVALID_VALUE; } diff --git a/src/sensors/Decagon5TM.h b/src/sensors/Decagon5TM.h index fe037a4b3..e90c4422e 100644 --- a/src/sensors/Decagon5TM.h +++ b/src/sensors/Decagon5TM.h @@ -124,16 +124,19 @@ /** * @anchor sensor_fivetm_ea * @name EA - * The EA variable from a Meter ECH2O - * - Range is 0 – 1 m3/m3 (0 – 100% VWC) - * - Accuracy for generic calibration equation: ± 0.03 m3/m3 (± 3% VWC) typical - * - Accuracy for medium-specific calibration: ± 0.02 m3/m3 (± 2% VWC) + * The Apparent Dielectric Permittivity (EA) variable from a Meter ECH2O + * Accuracy is ± 1 Ka from 1 – 40 (soil range); + * ± 15% from 40 – 80 Soil. * * {{ @ref Decagon5TM_Ea::Decagon5TM_Ea }} */ /**@{*/ +/// @brief Minimum electrical permittivity in farads per meter. +#define TM_EA_MIN_FPM 1.0 +/// @brief Maximum electrical permittivity in farads per meter. +#define TM_EA_MAX_FPM 80.0 /** - * @brief Decimals places in string representation; EA should have 5 + * @brief Decimal places in string representation; EA should have 5 * * 4 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.0008 m3/m3 (0.08% VWC) @@ -164,8 +167,12 @@ * {{ @ref Decagon5TM_Temp::Decagon5TM_Temp }} */ /**@{*/ +/// @brief Minimum temperature in degrees Celsius. +#define TM_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define TM_TEMP_MAX_C 50.0 /** - * @brief Decimals places in string representation; temperature should have 2 + * @brief Decimal places in string representation; temperature should have 2 * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C. @@ -196,8 +203,12 @@ * {{ @ref Decagon5TM_VWC::Decagon5TM_VWC }} */ /**@{*/ +/// @brief Minimum volumetric water content in percent. +#define TM_VWC_MIN_PCT 0.0 +/// @brief Maximum volumetric water content in percent. +#define TM_VWC_MAX_PCT 100.0 /** - * @brief Decimals places in string representation; VWC should have 3 + * @brief Decimal places in string representation; VWC should have 3 * * 2 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.0008 m3/m3 (0.08% VWC) @@ -283,7 +294,7 @@ class Decagon5TM : public SDI12Sensors { /** * @brief Destroy the Decagon 5TM object */ - ~Decagon5TM() {} + ~Decagon5TM() override = default; /** * @copydoc SDI12Sensors::getResults(bool verify_crc) @@ -315,21 +326,12 @@ class Decagon5TM_Ea : public Variable { */ explicit Decagon5TM_Ea(Decagon5TM* parentSense, const char* uuid = "", const char* varCode = TM_EA_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TM_EA_VAR_NUM, - (uint8_t)TM_EA_RESOLUTION, TM_EA_VAR_NAME, TM_EA_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new Decagon5TM_Ea object. - * - * @note This must be tied with a parent Decagon5TM before it can be used. - */ - Decagon5TM_Ea() - : Variable((uint8_t)TM_EA_VAR_NUM, (uint8_t)TM_EA_RESOLUTION, - TM_EA_VAR_NAME, TM_EA_UNIT_NAME, TM_EA_DEFAULT_CODE) {} + : Variable(parentSense, TM_EA_VAR_NUM, TM_EA_RESOLUTION, TM_EA_VAR_NAME, + TM_EA_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Decagon5TM_Ea object - no action needed. */ - ~Decagon5TM_Ea() {} + ~Decagon5TM_Ea() override = default; }; @@ -355,21 +357,12 @@ class Decagon5TM_Temp : public Variable { */ explicit Decagon5TM_Temp(Decagon5TM* parentSense, const char* uuid = "", const char* varCode = TM_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TM_TEMP_VAR_NUM, - (uint8_t)TM_TEMP_RESOLUTION, TM_TEMP_VAR_NAME, - TM_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new Decagon5TM_Temp object. - * - * @note This must be tied with a parent Decagon5TM before it can be used. - */ - Decagon5TM_Temp() - : Variable((uint8_t)TM_TEMP_VAR_NUM, (uint8_t)TM_TEMP_RESOLUTION, - TM_TEMP_VAR_NAME, TM_TEMP_UNIT_NAME, TM_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, TM_TEMP_VAR_NUM, TM_TEMP_RESOLUTION, + TM_TEMP_VAR_NAME, TM_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Decagon5TM_Temp object - no action needed. */ - ~Decagon5TM_Temp() {} + ~Decagon5TM_Temp() override = default; }; @@ -395,23 +388,14 @@ class Decagon5TM_VWC : public Variable { */ explicit Decagon5TM_VWC(Decagon5TM* parentSense, const char* uuid = "", const char* varCode = TM_VWC_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TM_VWC_VAR_NUM, - (uint8_t)TM_VWC_RESOLUTION, TM_VWC_VAR_NAME, - TM_VWC_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new Decagon5TM_VWC object. - * - * @note This must be tied with a parent Decagon5TM before it can be used. - */ - Decagon5TM_VWC() - : Variable((uint8_t)TM_VWC_VAR_NUM, (uint8_t)TM_VWC_RESOLUTION, - TM_VWC_VAR_NAME, TM_VWC_UNIT_NAME, TM_VWC_DEFAULT_CODE) {} + : Variable(parentSense, TM_VWC_VAR_NUM, TM_VWC_RESOLUTION, + TM_VWC_VAR_NAME, TM_VWC_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Decagon5TM_VWC object - no action needed. */ - ~Decagon5TM_VWC() {} + ~Decagon5TM_VWC() override = default; }; /**@}*/ #endif // SRC_SENSORS_DECAGON5TM_H_ -// cSpell:ignore fivetm matric +// cSpell:words fivetm matric diff --git a/src/sensors/DecagonCTD.h b/src/sensors/DecagonCTD.h index 105cdc05f..e1a2bb223 100644 --- a/src/sensors/DecagonCTD.h +++ b/src/sensors/DecagonCTD.h @@ -109,8 +109,12 @@ * {{ @ref DecagonCTD_Cond::DecagonCTD_Cond }} */ /**@{*/ +/// @brief Minimum specific conductance in microsiemens per centimeter. +#define CTD_COND_MIN_USCM 0.0 +/// @brief Maximum specific conductance in microsiemens per centimeter. +#define CTD_COND_MAX_USCM 120000.0 /** - * @brief Decimals places in string representation; conductivity should have 1. + * @brief Decimal places in string representation; conductivity should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.001 mS/cm = 1 µS/cm @@ -140,8 +144,12 @@ * {{ @ref DecagonCTD_Temp::DecagonCTD_Temp }} */ /**@{*/ +/// @brief Minimum temperature in degrees Celsius. +#define CTD_TEMP_MIN_C -11.0 +/// @brief Maximum temperature in degrees Celsius. +#define CTD_TEMP_MAX_C 49.0 /** - * @brief Decimals places in string representation; temperature should have 2. + * @brief Decimal places in string representation; temperature should have 2. * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C @@ -171,8 +179,12 @@ * {{ @ref DecagonCTD_Depth::DecagonCTD_Depth }} */ /**@{*/ +/// @brief Minimum water depth in millimeters. +#define CTD_DEPTH_MIN_MM 0.0 +/// @brief Maximum water depth in millimeters. +#define CTD_DEPTH_MAX_MM 10000.0 /** - * @brief Decimals places in string representation; depth should have 1. + * @brief Decimal places in string representation; depth should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 2 mm @@ -252,11 +264,10 @@ class DecagonCTD : public SDI12Sensors { "DecagonCTD", CTD_NUM_VARIABLES, CTD_WARM_UP_TIME_MS, CTD_STABILIZATION_TIME_MS, CTD_MEASUREMENT_TIME_MS, CTD_EXTRA_WAKE_TIME_MS, CTD_INC_CALC_VARIABLES) {} - /** * @brief Destroy the Decagon CTD object */ - ~DecagonCTD() {} + ~DecagonCTD() override = default; }; @@ -282,22 +293,12 @@ class DecagonCTD_Cond : public Variable { */ explicit DecagonCTD_Cond(DecagonCTD* parentSense, const char* uuid = "", const char* varCode = CTD_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CTD_COND_VAR_NUM, - (uint8_t)CTD_COND_RESOLUTION, CTD_COND_VAR_NAME, - CTD_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new DecagonCTD_Cond object. - * - * @note This must be tied with a parent DecagonCTD before it can be used. - */ - DecagonCTD_Cond() - : Variable((uint8_t)CTD_COND_VAR_NUM, (uint8_t)CTD_COND_RESOLUTION, - CTD_COND_VAR_NAME, CTD_COND_UNIT_NAME, - CTD_COND_DEFAULT_CODE) {} + : Variable(parentSense, CTD_COND_VAR_NUM, CTD_COND_RESOLUTION, + CTD_COND_VAR_NAME, CTD_COND_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the DecagonCTD_Cond object - no action needed. */ - ~DecagonCTD_Cond() {} + ~DecagonCTD_Cond() override = default; }; @@ -323,22 +324,12 @@ class DecagonCTD_Temp : public Variable { */ explicit DecagonCTD_Temp(DecagonCTD* parentSense, const char* uuid = "", const char* varCode = CTD_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CTD_TEMP_VAR_NUM, - (uint8_t)CTD_TEMP_RESOLUTION, CTD_TEMP_VAR_NAME, - CTD_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new DecagonCTD_Temp object. - * - * @note This must be tied with a parent DecagonCTD before it can be used. - */ - DecagonCTD_Temp() - : Variable((uint8_t)CTD_TEMP_VAR_NUM, (uint8_t)CTD_TEMP_RESOLUTION, - CTD_TEMP_VAR_NAME, CTD_TEMP_UNIT_NAME, - CTD_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, CTD_TEMP_VAR_NUM, CTD_TEMP_RESOLUTION, + CTD_TEMP_VAR_NAME, CTD_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the DecagonCTD_Temp object - no action needed. */ - ~DecagonCTD_Temp() {} + ~DecagonCTD_Temp() override = default; }; @@ -364,22 +355,12 @@ class DecagonCTD_Depth : public Variable { */ explicit DecagonCTD_Depth(DecagonCTD* parentSense, const char* uuid = "", const char* varCode = CTD_DEPTH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CTD_DEPTH_VAR_NUM, - (uint8_t)CTD_DEPTH_RESOLUTION, CTD_DEPTH_VAR_NAME, - CTD_DEPTH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new DecagonCTD_Depth object. - * - * @note This must be tied with a parent DecagonCTD before it can be used. - */ - DecagonCTD_Depth() - : Variable((uint8_t)CTD_DEPTH_VAR_NUM, (uint8_t)CTD_DEPTH_RESOLUTION, - CTD_DEPTH_VAR_NAME, CTD_DEPTH_UNIT_NAME, - CTD_DEPTH_DEFAULT_CODE) {} + : Variable(parentSense, CTD_DEPTH_VAR_NUM, CTD_DEPTH_RESOLUTION, + CTD_DEPTH_VAR_NAME, CTD_DEPTH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the DecagonCTD_Depth object - no action needed. */ - ~DecagonCTD_Depth() {} + ~DecagonCTD_Depth() override = default; }; /**@}*/ #endif // SRC_SENSORS_decagon_ctd_H_ diff --git a/src/sensors/DecagonES2.h b/src/sensors/DecagonES2.h index 96bbd9ec8..b6599a406 100644 --- a/src/sensors/DecagonES2.h +++ b/src/sensors/DecagonES2.h @@ -104,8 +104,12 @@ * {{ @ref DecagonES2_Cond::DecagonES2_Cond }} */ /**@{*/ +/// @brief Minimum specific conductance in microsiemens per centimeter. +#define ES2_COND_MIN_USCM 0.0 +/// @brief Maximum specific conductance in microsiemens per centimeter. +#define ES2_COND_MAX_USCM 120000.0 /** - * @brief Decimals places in string representation; conductivity should have 1. + * @brief Decimal places in string representation; conductivity should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.001 mS/cm = 1 µS/cm @@ -135,8 +139,12 @@ * {{ @ref DecagonES2_Temp::DecagonES2_Temp }} */ /**@{*/ +/// @brief Minimum temperature in degrees Celsius. +#define ES2_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define ES2_TEMP_MAX_C 50.0 /** - * @brief Decimals places in string representation; temperature should have 2. + * @brief Decimal places in string representation; temperature should have 2. * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C @@ -218,7 +226,7 @@ class DecagonES2 : public SDI12Sensors { /** * @brief Destroy the Decagon ES2 object */ - ~DecagonES2() {} + ~DecagonES2() override = default; }; @@ -244,22 +252,12 @@ class DecagonES2_Cond : public Variable { */ explicit DecagonES2_Cond(DecagonES2* parentSense, const char* uuid = "", const char* varCode = ES2_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ES2_COND_VAR_NUM, - (uint8_t)ES2_COND_RESOLUTION, ES2_COND_VAR_NAME, - ES2_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new DecagonES2_Cond object. - * - * @note This must be tied with a parent DecagonES2 before it can be used. - */ - DecagonES2_Cond() - : Variable((uint8_t)ES2_COND_VAR_NUM, (uint8_t)ES2_COND_RESOLUTION, - ES2_COND_VAR_NAME, ES2_COND_UNIT_NAME, - ES2_COND_DEFAULT_CODE) {} + : Variable(parentSense, ES2_COND_VAR_NUM, ES2_COND_RESOLUTION, + ES2_COND_VAR_NAME, ES2_COND_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the DecagonES2_Cond object - no action needed. */ - ~DecagonES2_Cond() {} + ~DecagonES2_Cond() override = default; }; /* clang-format off */ @@ -284,22 +282,12 @@ class DecagonES2_Temp : public Variable { */ explicit DecagonES2_Temp(DecagonES2* parentSense, const char* uuid = "", const char* varCode = ES2_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ES2_TEMP_VAR_NUM, - (uint8_t)ES2_TEMP_RESOLUTION, ES2_TEMP_VAR_NAME, - ES2_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new DecagonES2_Temp object. - * - * @note This must be tied with a parent DecagonES2 before it can be used. - */ - DecagonES2_Temp() - : Variable((uint8_t)ES2_TEMP_VAR_NUM, (uint8_t)ES2_TEMP_RESOLUTION, - ES2_TEMP_VAR_NAME, ES2_TEMP_UNIT_NAME, - ES2_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, ES2_TEMP_VAR_NUM, ES2_TEMP_RESOLUTION, + ES2_TEMP_VAR_NAME, ES2_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the DecagonES2_Temp object - no action needed. */ - ~DecagonES2_Temp() {} + ~DecagonES2_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_DECAGONES2_H_ diff --git a/src/sensors/EverlightALSPT19.cpp b/src/sensors/EverlightALSPT19.cpp index 996ff612c..9cc32bff3 100644 --- a/src/sensors/EverlightALSPT19.cpp +++ b/src/sensors/EverlightALSPT19.cpp @@ -9,80 +9,132 @@ */ #include "EverlightALSPT19.h" +#include "ProcessorAnalog.h" -// The constructor - because this is I2C, only need the power pin -// This sensor has a set I2C address of 0XB8 EverlightALSPT19::EverlightALSPT19(int8_t powerPin, int8_t dataPin, - float supplyVoltage, float loadResistor, - uint8_t measurementsToAverage) + float alsSupplyVoltage, float loadResistor, + uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("Everlight ALS-PT19", ALSPT19_NUM_VARIABLES, ALSPT19_WARM_UP_TIME_MS, ALSPT19_STABILIZATION_TIME_MS, ALSPT19_MEASUREMENT_TIME_MS, powerPin, dataPin, - measurementsToAverage), - _supplyVoltage(supplyVoltage), - _loadResistor(loadResistor) {} -EverlightALSPT19::EverlightALSPT19(uint8_t measurementsToAverage) - : Sensor("Everlight ALS-PT19", ALSPT19_NUM_VARIABLES, - ALSPT19_WARM_UP_TIME_MS, ALSPT19_STABILIZATION_TIME_MS, - ALSPT19_MEASUREMENT_TIME_MS, MAYFLY_ALS_POWER_PIN, - MAYFLY_ALS_DATA_PIN, measurementsToAverage, - ALSPT19_INC_CALC_VARIABLES), - _supplyVoltage(MAYFLY_ALS_SUPPLY_VOLTAGE), - _loadResistor(MAYFLY_ALS_LOADING_RESISTANCE) {} -EverlightALSPT19::~EverlightALSPT19() {} - - -bool EverlightALSPT19::addSingleMeasurementResult(void) { - // Initialize float variables - float volt_val = -9999; - float current_val = -9999; - float lux_val = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - // First measure the analog voltage. - // The return value from analogRead() is IN BITS NOT IN VOLTS!! - // Take a priming reading. - // First reading will be low - discard - analogRead(_dataPin); - // Take the reading we'll keep - uint32_t sensor_adc = analogRead(_dataPin); - MS_DEEP_DBG(" ADC Bits:", sensor_adc); - - if (0 == sensor_adc) { - // Prevent underflow, can never be outside of PROCESSOR_ADC_RANGE - sensor_adc = 1; - } - // convert bits to volts - volt_val = (_supplyVoltage / static_cast(PROCESSOR_ADC_MAX)) * - static_cast(sensor_adc); - // convert volts to current - // resistance is entered in kΩ and we want µA - current_val = (volt_val / (_loadResistor * 1000)) * 1e6; - // convert current to illuminance - // from sensor datasheet, typical 200µA current for 1000 Lux - lux_val = current_val * (1000. / 200.); + measurementsToAverage, ALSPT19_INC_CALC_VARIABLES), + _alsSupplyVoltage((alsSupplyVoltage > 0.0f) ? alsSupplyVoltage + : OPERATING_VOLTAGE), + // If no analog voltage reader was provided, create a default one + _loadResistor(loadResistor), + _analogVoltageReader(analogVoltageReader == nullptr + ? new ProcessorAnalogReader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} +#if (defined(BUILT_IN_ALS_POWER_PIN) && defined(BUILT_IN_ALS_DATA_PIN) && \ + defined(BUILT_IN_ALS_SUPPLY_VOLTAGE) && \ + defined(BUILT_IN_ALS_LOADING_RESISTANCE)) || \ + defined(DOXYGEN) +EverlightALSPT19::EverlightALSPT19(uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) + : EverlightALSPT19(BUILT_IN_ALS_POWER_PIN, BUILT_IN_ALS_DATA_PIN, + BUILT_IN_ALS_SUPPLY_VOLTAGE, + BUILT_IN_ALS_LOADING_RESISTANCE, measurementsToAverage, + analogVoltageReader) {} +#endif - MS_DBG(F(" Voltage:"), volt_val, F("V")); - MS_DBG(F(" Current:"), current_val, F("µA")); - MS_DBG(F(" Illuminance:"), lux_val, F("lux")); +// Destructor +EverlightALSPT19::~EverlightALSPT19() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String EverlightALSPT19::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + return _analogVoltageReader->getAnalogLocation(_dataPin, -1); } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + return String(F("Unknown_AnalogVoltageReader")); } +} - verifyAndAddMeasurementResult(ALSPT19_VOLTAGE_VAR_NUM, volt_val); - verifyAndAddMeasurementResult(ALSPT19_CURRENT_VAR_NUM, current_val); - verifyAndAddMeasurementResult(ALSPT19_ILLUMINANCE_VAR_NUM, lux_val); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); +bool EverlightALSPT19::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); + } + } else { + MS_DBG(getSensorNameAndLocation(), + F("Unexpected: analog voltage reader is null")); + } + + return sensorSetupSuccess && analogVoltageReaderSuccess; +} - return true; + +bool EverlightALSPT19::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + // Check if we have a valid load resistor + if (_loadResistor <= 0) { + MS_DBG(getSensorNameAndLocation(), F("Invalid load resistor value")); + return finalizeMeasurementAttempt(false); + } + + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the single-ended analog voltage using the AnalogVoltageReader + // interface. + // NOTE: All implementations of the AnalogVoltageReader class validate both + // the input channel and the resulting voltage, so we can trust that a + // successful read will give us a valid voltage value to work with. + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + adcVoltage); + + if (success) { + verifyAndAddMeasurementResult(ALSPT19_VOLTAGE_VAR_NUM, adcVoltage); + + // From the datasheet: + // The output voltage V(out) is the product of photocurrent I(PH) and + // loading resistor R(L): + // - V(out) = I(PH) * R(L) + // At saturation: + // - V(out) = Vcc-0.4V + if (adcVoltage > _alsSupplyVoltage - 0.4f) { + MS_DBG(getSensorNameAndLocation(), + F("Light sensor has reached saturation! Clamping current " + "and illumination values!")); + adcVoltage = max(0.0f, _alsSupplyVoltage - 0.4f); + } + // convert volts to current + // resistance is entered in kΩ and we want µA + float current_val = (adcVoltage / (_loadResistor * 1000.0f)) * 1e6f; + MS_DBG(F(" Current:"), current_val, F("µA")); + verifyAndAddMeasurementResult(ALSPT19_CURRENT_VAR_NUM, current_val); + + // convert current to illuminance + // from sensor datasheet, typical 200µA current for 1000 Lux + float calibResult = current_val * (1000.0f / ALSPT19_UA_PER_1000LUX); + MS_DBG(F(" Illuminance:"), calibResult, F("lux")); + verifyAndAddMeasurementResult(ALSPT19_ILLUMINANCE_VAR_NUM, calibResult); + } else { + MS_DBG(F(" Failed to get valid voltage from analog reader")); + } + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/EverlightALSPT19.h b/src/sensors/EverlightALSPT19.h index 9e8a1b8c4..e45d92cd7 100644 --- a/src/sensors/EverlightALSPT19.h +++ b/src/sensors/EverlightALSPT19.h @@ -28,9 +28,14 @@ * @section sensor_alspt19_datasheet Sensor Datasheet * [Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Everlight-ALS-PT19.pdf) * + * @section sensor_alspt19_config_flags Build flags + * - `-D ALSPT19_UA_PER_1000LUX=##` + * - used to set current equivalent to 1000lux, which is used to calculate + * lux from the sensor + * * @section sensor_alspt19_ctor Sensor Constructors - * {{ @ref EverlightALSPT19::EverlightALSPT19(uint8_t) }} - * {{ @ref EverlightALSPT19::EverlightALSPT19(int8_t, int8_t, float, float, uint8_t) }} + * {{ @ref EverlightALSPT19::EverlightALSPT19(uint8_t, AnalogVoltageReader*) }} + * {{ @ref EverlightALSPT19::EverlightALSPT19(int8_t, int8_t, float, float, uint8_t, AnalogVoltageReader*) }} * * @section sensor_alspt19_examples Example Code * @@ -71,6 +76,34 @@ /** @ingroup sensor_alspt19 */ /**@{*/ +/** + * @anchor sensor_alspt19_config + * @name Configuration Defines + * Define for the ALS calibration between current and lux. + */ +/**@{*/ +#if !defined(ALSPT19_UA_PER_1000LUX) || defined(DOXYGEN) +/** + * @brief The default current (in µA) that is equivalent to 1000 lux of + * illuminance. + * + * Extrapolating from plots in the sensor datasheet + * - Incandescent light: ~5,000µA for 10,000 lux (500 for 1000) + * - Fluorescent light: ~1,500µA for 10,000 lux (150 for 1000) + * - 6500K white LED: 150µA for 1000 Lux + * + * @attention The default of 200.0f has been used by this library, but I'm + * unclear the origin of this number. + * + * @todo Find the source of the calibration of typical 200µA current for 1000 + * Lux, which doesn't appear to align with the datasheet. + */ +#define ALSPT19_UA_PER_1000LUX 200.0f +#endif +static_assert(ALSPT19_UA_PER_1000LUX > 0, + "Current-to-lux calibration factor must be positive"); +/**@}*/ + /** * @anchor sensor_alspt19_var_counts * @name Sensor Variable Counts @@ -85,23 +118,6 @@ #define ALSPT19_INC_CALC_VARIABLES 2 /**@}*/ -/** - * @anchor sensor_alspt19_mayfly - * @name Pin Definitions for the Mayfly - * Specific pin definitions for the ALS-PT19 built in to the EnviroDIY Mayfly - * v1.x - */ -/**@{*/ -/// @brief The power pin for the ALS on the EnviroDIY Mayfly v1.x -#define MAYFLY_ALS_POWER_PIN -1 -/// @brief The data pin for the ALS on the EnviroDIY Mayfly v1.x -#define MAYFLY_ALS_DATA_PIN A4 -/// @brief The supply voltage for the ALS on the EnviroDIY Mayfly v1.x -#define MAYFLY_ALS_SUPPLY_VOLTAGE 3.3 -/// @brief The loading resistance for the ALS on the EnviroDIY Mayfly v1.x -#define MAYFLY_ALS_LOADING_RESISTANCE 10 -/**@}*/ - /** * @anchor sensor_alspt19_timing * @name Sensor Timing @@ -129,8 +145,12 @@ * {{ @ref EverlightALSPT19_Voltage::EverlightALSPT19_Voltage }} */ /**@{*/ +/// @brief Minimum voltage in volts. +#define ALSPT19_VOLTAGE_MIN_V 0.0 +/// @brief Maximum voltage in volts. +#define ALSPT19_VOLTAGE_MAX_V 4.6 /** - * @brief Decimals places in string representation; voltage should have 0 + * @brief Decimal places in string representation; voltage should have 0 * * The true resolution depends on the ADC, the supply voltage, and the loading * resistor, but for simplicity we will use 3, which is an appropriate value for @@ -160,8 +180,12 @@ * {{ @ref EverlightALSPT19_Current::EverlightALSPT19_Current }} */ /**@{*/ +/// @brief Minimum electric current in microamperes. +#define ALSPT19_CURRENT_MIN_UA 5.0 +/// @brief Maximum electric current in microamperes. +#define ALSPT19_CURRENT_MAX_UA 520.0 /** - * @brief Decimals places in string representation; voltage should have 0 + * @brief Decimal places in string representation; voltage should have 0 * * The true resolution depends on the ADC, the supply voltage, and the loading * resistor, but for simplicity we will use 0 as this is not a high precision @@ -191,8 +215,12 @@ * {{ @ref EverlightALSPT19_Illuminance::EverlightALSPT19_Illuminance }} */ /**@{*/ +/// @brief Minimum illuminance in lux. +#define ALSPT19_ILLUMINANCE_MIN_LUX 0.0 +/// @brief Maximum illuminance in lux. +#define ALSPT19_ILLUMINANCE_MAX_LUX 10000.0 /** - * @brief Decimals places in string representation; illuminance should have 0 + * @brief Decimal places in string representation; illuminance should have 0 * * The true resolution depends on the ADC, the supply voltage, the loading * resistor, and the light source, but for simplicity we will use 0 as this is @@ -213,6 +241,8 @@ #define ALSPT19_ILLUMINANCE_DEFAULT_CODE "ALSPT19Lux" /**@}*/ +// Forward declaration +class AnalogVoltageReader; /* clang-format off */ /** @@ -231,18 +261,33 @@ class EverlightALSPT19 : public Sensor { * @param dataPin The processor ADC port pin to read the voltage from the EC * probe. Not all processor pins can be used as analog pins. Those usable * as analog pins generally are numbered with an "A" in front of the number - * - ie, A1. - * @param supplyVoltage The power supply voltage (in volts) of the ALS-PT19. - * @param loadResistor The size of the loading resistor, in kilaohms (kΩ). + * - i.e., A1. + * @param alsSupplyVoltage The power supply voltage (in volts) of the + * ALS-PT19. This does not have to be the same as the board operating + * voltage or the supply voltage of the AnalogVoltageReader reader. + * This is used to clamp the light values when the sensor is over-saturated. + * @param loadResistor The size of the loading resistor, in kiloohms (kΩ). * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 10. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses the processor's internal ADC. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. */ - EverlightALSPT19(int8_t powerPin, int8_t dataPin, float supplyVoltage, - float loadResistor, uint8_t measurementsToAverage = 10); + EverlightALSPT19(int8_t powerPin, int8_t dataPin, float alsSupplyVoltage, + float loadResistor, uint8_t measurementsToAverage = 10, + AnalogVoltageReader* analogVoltageReader = nullptr); + +#if (defined(BUILT_IN_ALS_POWER_PIN) && defined(BUILT_IN_ALS_DATA_PIN) && \ + defined(BUILT_IN_ALS_SUPPLY_VOLTAGE) && \ + defined(BUILT_IN_ALS_LOADING_RESISTANCE)) || \ + defined(DOXYGEN) /** * @brief Construct a new EverlightALSPT19 object with pins and resistors - * for the EnviroDIY Mayfly 1.x. + * for boards with a built-in ALS-PT19 configured in KnownProcessors.h. * * This is a short-cut constructor to help users of our own board so they * can change the number of readings without changing other arguments or @@ -251,27 +296,50 @@ class EverlightALSPT19 : public Sensor { * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 10. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses the processor's internal ADC. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. */ - explicit EverlightALSPT19(uint8_t measurementsToAverage = 10); + explicit EverlightALSPT19( + uint8_t measurementsToAverage = 10, + AnalogVoltageReader* analogVoltageReader = nullptr); +#endif /** - * @brief Destroy the EverlightALSPT19 object - no action needed. + * @brief Destroy the EverlightALSPT19 object. + * + * Conditionally deletes the _analogVoltageReader member if the ownership + * flag _ownsAnalogVoltageReader is true, otherwise leaves it unmodified. */ - ~EverlightALSPT19(); + ~EverlightALSPT19() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + EverlightALSPT19(const EverlightALSPT19&) = delete; + EverlightALSPT19& operator=(const EverlightALSPT19&) = delete; + + // Delete move constructor and move assignment operator + EverlightALSPT19(EverlightALSPT19&&) = delete; + EverlightALSPT19& operator=(EverlightALSPT19&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: - /** - * @brief The power supply voltage - */ - float _supplyVoltage; - /** - * @brief The loading resistance - */ - float _loadResistor; + /// @brief The PT-19 power supply voltage + float _alsSupplyVoltage = 0.0f; + /// @brief The loading resistance + float _loadResistor = 0.0f; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -297,25 +365,13 @@ class EverlightALSPT19_Voltage : public Variable { explicit EverlightALSPT19_Voltage( EverlightALSPT19* parentSense, const char* uuid = "", const char* varCode = ALSPT19_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ALSPT19_VOLTAGE_VAR_NUM, - (uint8_t)ALSPT19_VOLTAGE_RESOLUTION, - ALSPT19_VOLTAGE_VAR_NAME, ALSPT19_VOLTAGE_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new EverlightALSPT19_Voltage object. - * - * @note This must be tied with a parent EverlightALSPT19 before it can be - * used. - */ - EverlightALSPT19_Voltage() - : Variable((uint8_t)ALSPT19_VOLTAGE_VAR_NUM, - (uint8_t)ALSPT19_VOLTAGE_RESOLUTION, - ALSPT19_VOLTAGE_VAR_NAME, ALSPT19_VOLTAGE_UNIT_NAME, - ALSPT19_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, ALSPT19_VOLTAGE_VAR_NUM, + ALSPT19_VOLTAGE_RESOLUTION, ALSPT19_VOLTAGE_VAR_NAME, + ALSPT19_VOLTAGE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the EverlightALSPT19_Voltage object - no action needed. */ - ~EverlightALSPT19_Voltage() {} + ~EverlightALSPT19_Voltage() override = default; }; @@ -341,25 +397,13 @@ class EverlightALSPT19_Current : public Variable { explicit EverlightALSPT19_Current( EverlightALSPT19* parentSense, const char* uuid = "", const char* varCode = ALSPT19_CURRENT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ALSPT19_CURRENT_VAR_NUM, - (uint8_t)ALSPT19_CURRENT_RESOLUTION, - ALSPT19_CURRENT_VAR_NAME, ALSPT19_CURRENT_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new EverlightALSPT19_Current object. - * - * @note This must be tied with a parent EverlightALSPT19 before it can be - * used. - */ - EverlightALSPT19_Current() - : Variable((uint8_t)ALSPT19_CURRENT_VAR_NUM, - (uint8_t)ALSPT19_CURRENT_RESOLUTION, - ALSPT19_CURRENT_VAR_NAME, ALSPT19_CURRENT_UNIT_NAME, - ALSPT19_CURRENT_DEFAULT_CODE) {} + : Variable(parentSense, ALSPT19_CURRENT_VAR_NUM, + ALSPT19_CURRENT_RESOLUTION, ALSPT19_CURRENT_VAR_NAME, + ALSPT19_CURRENT_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the EverlightALSPT19_Current object - no action needed. */ - ~EverlightALSPT19_Current() {} + ~EverlightALSPT19_Current() override = default; }; @@ -385,26 +429,16 @@ class EverlightALSPT19_Illuminance : public Variable { explicit EverlightALSPT19_Illuminance( EverlightALSPT19* parentSense, const char* uuid = "", const char* varCode = ALSPT19_ILLUMINANCE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ALSPT19_ILLUMINANCE_VAR_NUM, - (uint8_t)ALSPT19_ILLUMINANCE_RESOLUTION, - ALSPT19_ILLUMINANCE_VAR_NAME, ALSPT19_ILLUMINANCE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new EverlightALSPT19_Illuminance object. - * - * @note This must be tied with a parent EverlightALSPT19 before it can be - * used. - */ - EverlightALSPT19_Illuminance() - : Variable((uint8_t)ALSPT19_ILLUMINANCE_VAR_NUM, - (uint8_t)ALSPT19_ILLUMINANCE_RESOLUTION, - ALSPT19_ILLUMINANCE_VAR_NAME, ALSPT19_ILLUMINANCE_UNIT_NAME, - ALSPT19_ILLUMINANCE_DEFAULT_CODE) {} + : Variable(parentSense, ALSPT19_ILLUMINANCE_VAR_NUM, + ALSPT19_ILLUMINANCE_RESOLUTION, ALSPT19_ILLUMINANCE_VAR_NAME, + ALSPT19_ILLUMINANCE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the EverlightALSPT19_Illuminance object - no action * needed. */ - ~EverlightALSPT19_Illuminance() {} + ~EverlightALSPT19_Illuminance() override = default; }; /**@}*/ #endif // SRC_SENSORS_EVERLIGHTALSPT19_H_ + +// cSpell:words microamperes diff --git a/src/sensors/FreescaleMPL115A2.cpp b/src/sensors/FreescaleMPL115A2.cpp index 79d33ed03..d231dbb39 100644 --- a/src/sensors/FreescaleMPL115A2.cpp +++ b/src/sensors/FreescaleMPL115A2.cpp @@ -16,27 +16,23 @@ // This sensor has a set I2C address of 0x60. FreescaleMPL115A2::FreescaleMPL115A2(TwoWire* theI2C, int8_t powerPin, uint8_t measurementsToAverage) - : Sensor("FreescaleMPL115A2", MPL115A2_NUM_VARIABLES, - MPL115A2_WARM_UP_TIME_MS, MPL115A2_STABILIZATION_TIME_MS, - MPL115A2_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage), - _i2c(theI2C) {} -FreescaleMPL115A2::FreescaleMPL115A2(int8_t powerPin, - uint8_t measurementsToAverage) : Sensor("FreescaleMPL115A2", MPL115A2_NUM_VARIABLES, MPL115A2_WARM_UP_TIME_MS, MPL115A2_STABILIZATION_TIME_MS, MPL115A2_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage, MPL115A2_INC_CALC_VARIABLES), - _i2c(&Wire) {} -// Destructor -FreescaleMPL115A2::~FreescaleMPL115A2() {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} +// Delegating constructor +FreescaleMPL115A2::FreescaleMPL115A2(int8_t powerPin, + uint8_t measurementsToAverage) + : FreescaleMPL115A2(&Wire, powerPin, measurementsToAverage) {} -String FreescaleMPL115A2::getSensorLocation(void) { +String FreescaleMPL115A2::getSensorLocation() { return F("I2C_0x60"); } -bool FreescaleMPL115A2::setup(void) { +bool FreescaleMPL115A2::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -59,41 +55,32 @@ bool FreescaleMPL115A2::setup(void) { } -bool FreescaleMPL115A2::addSingleMeasurementResult(void) { - // Initialize float variables - float temp = -9999; - float press = -9999; +bool FreescaleMPL115A2::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + bool success = false; + float temp = MS_INVALID_VALUE; + float press = MS_INVALID_VALUE; - // Read values - mpl115a2_internal.getPT(&press, &temp); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - if (isnan(temp)) temp = -9999; - if (isnan(press)) press = -9999; + // Read values + mpl115a2_internal.getPT(&press, &temp); - if (press > 115.0 || temp < -40.0) { - temp = -9999; - press = -9999; - } + MS_DBG(F(" Temperature:"), temp); + MS_DBG(F(" Pressure:"), press); - MS_DBG(F(" Temperature:"), temp); - MS_DBG(F(" Pressure:"), press); + if (!isnan(temp) && !isnan(press) && press >= MPL115A2_PRESSURE_MIN_KPA && + press <= MPL115A2_PRESSURE_MAX_KPA && temp >= MPL115A2_TEMP_MIN_C && + temp <= MPL115A2_TEMP_MAX_C) { + verifyAndAddMeasurementResult(MPL115A2_TEMP_VAR_NUM, temp); + verifyAndAddMeasurementResult(MPL115A2_PRESSURE_VAR_NUM, press); + success = true; } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F(" Values outside expected range or invalid")); } - verifyAndAddMeasurementResult(MPL115A2_TEMP_VAR_NUM, temp); - verifyAndAddMeasurementResult(MPL115A2_PRESSURE_VAR_NUM, press); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // no way of knowing if successful, just return true - return true; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/FreescaleMPL115A2.h b/src/sensors/FreescaleMPL115A2.h index 56384e4c1..bfea42fa9 100644 --- a/src/sensors/FreescaleMPL115A2.h +++ b/src/sensors/FreescaleMPL115A2.h @@ -35,7 +35,7 @@ * is managed by the * [Adafruit MPL115A2 library](https://github.com/adafruit/Adafruit_MPL115A2). * - * @note Software I2C is *not* supported for the AM2315. + * @note Software I2C is *not* supported for the MPL115A2. * A secondary hardware I2C on a SAMD board is supported. * * @section sensor_mpl115a2_datasheet Sensor Datasheet @@ -125,7 +125,11 @@ * {{ @ref FreescaleMPL115A2_Temp::FreescaleMPL115A2_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum valid temperature in degrees Celsius. +#define MPL115A2_TEMP_MIN_C -20.0 +/// @brief Maximum valid temperature in degrees Celsius. +#define MPL115A2_TEMP_MAX_C 85.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define MPL115A2_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. @@ -152,7 +156,11 @@ * {{ @ref FreescaleMPL115A2_Pressure::FreescaleMPL115A2_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; pressure should have 2 - +/// @brief Minimum valid pressure in kilopascals. +#define MPL115A2_PRESSURE_MIN_KPA 50.0 +/// @brief Maximum valid pressure in kilopascals. +#define MPL115A2_PRESSURE_MAX_KPA 115.0 +/// @brief Decimal places in string representation; pressure should have 2 - /// resolution is 1.5 hPa. #define MPL115A2_PRESSURE_RESOLUTION 2 /// @brief Sensor variable number; pressure is stored in sensorValues[1]. @@ -220,7 +228,7 @@ class FreescaleMPL115A2 : public Sensor { /** * @brief Destroy the FreescaleMPL115A2 object */ - ~FreescaleMPL115A2(); + ~FreescaleMPL115A2() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -233,16 +241,11 @@ class FreescaleMPL115A2 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** @@ -288,23 +291,13 @@ class FreescaleMPL115A2_Temp : public Variable { explicit FreescaleMPL115A2_Temp( FreescaleMPL115A2* parentSense, const char* uuid = "", const char* varCode = MPL115A2_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)MPL115A2_TEMP_VAR_NUM, - (uint8_t)MPL115A2_TEMP_RESOLUTION, MPL115A2_TEMP_VAR_NAME, - MPL115A2_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new FreescaleMPL115A2_Temp object. - * - * @note This must be tied with a parent FreescaleMPL115A2 before it can be - * used. - */ - FreescaleMPL115A2_Temp() - : Variable((uint8_t)MPL115A2_TEMP_VAR_NUM, - (uint8_t)MPL115A2_TEMP_RESOLUTION, MPL115A2_TEMP_VAR_NAME, - MPL115A2_TEMP_UNIT_NAME, MPL115A2_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, MPL115A2_TEMP_VAR_NUM, MPL115A2_TEMP_RESOLUTION, + MPL115A2_TEMP_VAR_NAME, MPL115A2_TEMP_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the FreescaleMPL115A2_Temp object - no action needed. */ - ~FreescaleMPL115A2_Temp() {} + ~FreescaleMPL115A2_Temp() override = default; }; /** @@ -340,25 +333,13 @@ class FreescaleMPL115A2_Pressure : public Variable { explicit FreescaleMPL115A2_Pressure( FreescaleMPL115A2* parentSense, const char* uuid = "", const char* varCode = MPL115A2_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)MPL115A2_PRESSURE_VAR_NUM, - (uint8_t)MPL115A2_PRESSURE_RESOLUTION, - MPL115A2_PRESSURE_VAR_NAME, MPL115A2_PRESSURE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new FreescaleMPL115A2_Pressure object. - * - * @note This must be tied with a parent FreescaleMPL115A2 before it can be - * used. - */ - FreescaleMPL115A2_Pressure() - : Variable((uint8_t)MPL115A2_PRESSURE_VAR_NUM, - (uint8_t)MPL115A2_PRESSURE_RESOLUTION, - MPL115A2_PRESSURE_VAR_NAME, MPL115A2_PRESSURE_UNIT_NAME, - MPL115A2_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, MPL115A2_PRESSURE_VAR_NUM, + MPL115A2_PRESSURE_RESOLUTION, MPL115A2_PRESSURE_VAR_NAME, + MPL115A2_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the FreescaleMPL115A2_Pressure object - no action needed. */ - ~FreescaleMPL115A2_Pressure() {} + ~FreescaleMPL115A2_Pressure() override = default; }; /** diff --git a/src/sensors/GeoluxHydroCam.cpp b/src/sensors/GeoluxHydroCam.cpp index a1ebf235c..37eea7049 100644 --- a/src/sensors/GeoluxHydroCam.cpp +++ b/src/sensors/GeoluxHydroCam.cpp @@ -18,13 +18,14 @@ GeoluxHydroCam::GeoluxHydroCam(Stream* stream, int8_t powerPin, : Sensor("GeoluxHydroCam", HYDROCAM_NUM_VARIABLES, HYDROCAM_WARM_UP_TIME_MS, HYDROCAM_STABILIZATION_TIME_MS, HYDROCAM_MEASUREMENT_TIME_MS, powerPin, -1, 1, HYDROCAM_INC_CALC_VARIABLES), - _powerPin2(powerPin2), _imageResolution(imageResolution), _filePrefix(filePrefix), _alwaysAutoFocus(alwaysAutoFocus), _baseLogger(&baseLogger), _stream(stream), - _camera(stream) {} + _camera(stream) { + setSecondaryPowerPin(powerPin2); +} GeoluxHydroCam::GeoluxHydroCam(Stream& stream, int8_t powerPin, @@ -34,28 +35,27 @@ GeoluxHydroCam::GeoluxHydroCam(Stream& stream, int8_t powerPin, : Sensor("GeoluxHydroCam", HYDROCAM_NUM_VARIABLES, HYDROCAM_WARM_UP_TIME_MS, HYDROCAM_STABILIZATION_TIME_MS, HYDROCAM_MEASUREMENT_TIME_MS, powerPin, -1, 1, HYDROCAM_INC_CALC_VARIABLES), - _powerPin2(powerPin2), _imageResolution(imageResolution), _filePrefix(filePrefix), _alwaysAutoFocus(alwaysAutoFocus), _baseLogger(&baseLogger), _stream(&stream), - _camera(&stream) {} + _camera(&stream) { + setSecondaryPowerPin(powerPin2); +} -// Destructor -GeoluxHydroCam::~GeoluxHydroCam() {} String GeoluxHydroCam::getLastSavedImageName() { return _filename; } -String GeoluxHydroCam::getSensorLocation(void) { +String GeoluxHydroCam::getSensorLocation() { return F("cameraSerial"); } -bool GeoluxHydroCam::setup(void) { +bool GeoluxHydroCam::setup() { MS_DEEP_DBG(F("Setting up Geolux HydroCam sensor...")); bool success = Sensor::setup(); // this will set pin modes and the setup status bit @@ -112,7 +112,7 @@ bool GeoluxHydroCam::setup(void) { } -bool GeoluxHydroCam::wake(void) { +bool GeoluxHydroCam::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; @@ -135,14 +135,14 @@ bool GeoluxHydroCam::wake(void) { // The function to put the sensor to sleep // Different from the standard in that empties and flushes the stream. -bool GeoluxHydroCam::sleep(void) { +bool GeoluxHydroCam::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); return Sensor::sleep(); -}; +} -bool GeoluxHydroCam::startSingleMeasurement(void) { +bool GeoluxHydroCam::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -180,152 +180,83 @@ bool GeoluxHydroCam::startSingleMeasurement(void) { clearStatusBit(MEASUREMENT_SUCCESSFUL); } - return true; + return success; } -bool GeoluxHydroCam::addSingleMeasurementResult(void) { - // Initialize values +bool GeoluxHydroCam::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + bool success = false; - int32_t bytes_transferred = -9999; - int32_t byte_error = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - // set a new filename based on the current RTC time - String filename = _baseLogger->generateFileName( - true, HYDROCAM_FILE_EXTENSION, _filePrefix); - MS_DBG(F("Attempting to create the file: "), filename); - - // Initialise the SD card - // skip everything else if there's no SD card, otherwise it might hang - if (!_baseLogger->initializeSDCard()) return false; - - // Create and then open the file in write mode - if (imgFile.open(filename.c_str(), O_CREAT | O_WRITE | O_AT_END)) { - MS_DBG(F("Created new file:"), filename); - success = true; - } else { - MS_DBG(F("Failed to create the image file"), filename); - success = false; - } + int32_t bytes_transferred = MS_INVALID_VALUE; + int32_t byte_error = MS_INVALID_VALUE; + + int32_t image_size = _camera.getImageSize(); + MS_DBG(F("Completed image is"), image_size, F("bytes.")); + if (image_size <= 0) { + MS_DBG(F("Camera returned an image size <= 0, which means the snapshot " + "failed!")); + return finalizeMeasurementAttempt(false); + } - int32_t image_size = _camera.getImageSize(); - MS_DBG(F("Completed image is"), image_size, F("bytes.")); - success &= image_size != 0; - - if (success) { - // dump anything in the camera stream, just in case - _camera.streamDump(); - - // Disable the watch-dog timer to reduce interrupts during transfer - MS_DBG(F("Disabling the watchdog during file transfer")); - extendedWatchDog::disableWatchDog(); - - // transfer the image from the camera to a file on the SD card - MS_START_DEBUG_TIMER; - bytes_transferred = _camera.transferImage(imgFile, image_size); - byte_error = abs(bytes_transferred - image_size); - // Close the image file - imgFile.close(); - - // See how long it took us - MS_DBG(F("Wrote"), bytes_transferred, F("of expected"), image_size, - F("bytes to the SD card - a difference of"), byte_error, - F("bytes")); - MS_DBG(F("Total read/write time was"), MS_PRINT_DEBUG_TIMER, - F("ms")); - - // Re-enable the watchdog - MS_DBG(F("Re-enabling the watchdog after file transfer")); - extendedWatchDog::enableWatchDog(); - - // Store the last image name - _filename = filename; - - success = bytes_transferred == image_size; - MS_DBG(F("Image transfer was a"), - success ? F("success") : F("failure")); - } + // set a new filename based on the current RTC time + String filename = _baseLogger->generateFileName( + true, HYDROCAM_FILE_EXTENSION, _filePrefix); + MS_DBG(F("Attempting to create the file: "), filename); + + // Initialise the SD card + // skip everything else if there's no SD card, otherwise it might hang + if (!_baseLogger->initializeSDCard()) { + MS_DBG(F("Failed initialize SD card, aborting!")); + return finalizeMeasurementAttempt(false); + } + + // Open the file in write mode - creating a new file if it doesn't exist or + // overwriting an existing one + if (imgFile.open(filename.c_str(), O_CREAT | O_WRITE | O_TRUNC)) { + MS_DBG(F("Created new file:"), filename); } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F("Failed to create the image file, aborting!")); + return finalizeMeasurementAttempt(false); } - verifyAndAddMeasurementResult(HYDROCAM_SIZE_VAR_NUM, bytes_transferred); - verifyAndAddMeasurementResult(HYDROCAM_ERROR_VAR_NUM, byte_error); + // dump anything in the camera stream, just in case + _camera.streamDump(); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + // Disable the watch-dog timer to reduce interrupts during transfer + MS_DBG(F("Disabling the watchdog during file transfer")); + extendedWatchDog::disableWatchDog(); - // Return values shows if we got a not-obviously-bad reading - return success; -} + // transfer the image from the camera to a file on the SD card + MS_START_DEBUG_TIMER; + bytes_transferred = _camera.transferImage(imgFile, image_size); + byte_error = (bytes_transferred >= 0) ? labs(bytes_transferred - image_size) + : MS_INVALID_VALUE; + // Close the image file after transfer + imgFile.close(); -// This turns on sensor power -void GeoluxHydroCam::powerUp(void) { - if (_powerPin >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); - } - if (_powerPin2 >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin2, OUTPUT); - MS_DBG(F("Applying secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, HIGH); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - } else { - // Mark the time that the sensor was powered - _millisPowerOn = millis(); - } - // Set the status bit for sensor power attempt (bit 1) and success (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); -} + // See how long it took us + MS_DBG(F("Wrote"), bytes_transferred, F("of expected"), image_size, + F("bytes to the SD card - a difference of"), byte_error, F("bytes")); + MS_DBG(F("Total read/write time was"), MS_PRINT_DEBUG_TIMER, F("ms")); + // Re-enable the watchdog + MS_DBG(F("Re-enabling the watchdog after file transfer")); + extendedWatchDog::enableWatchDog(); -// This turns off sensor power -void GeoluxHydroCam::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); - // Unset the power-on time - _millisPowerOn = 0; - // Unset the activation time - _millisSensorActivated = 0; - // Unset the measurement request time - _millisMeasurementRequested = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - } - if (_powerPin2 >= 0) { - MS_DBG(F("Turning off secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, LOW); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Do NOT unset any status bits or timestamps if we didn't really power - // down! - } + // Store the last image name - even if the transfer was incomplete + _filename = filename; + + success = bytes_transferred == image_size; + MS_DBG(F("Image transfer was a"), success ? F("success") : F("failure")); + + verifyAndAddMeasurementResult(HYDROCAM_SIZE_VAR_NUM, bytes_transferred); + verifyAndAddMeasurementResult(HYDROCAM_ERROR_VAR_NUM, byte_error); + + // Return success value when finished + return finalizeMeasurementAttempt(success); } // check if the camera is ready @@ -414,12 +345,12 @@ bool GeoluxHydroCam::isStable(bool debug) { } uint32_t elapsed_since_wake_up = millis() - _millisSensorActivated; - uint32_t minTime = _stabilizationTime_ms + _alwaysAutoFocus - ? HYDROCAM_AUTOFOCUS_TIME_MS - : 0L; - uint32_t maxTime = HYDROCAM_STABILIZATION_TIME_MAX + _alwaysAutoFocus - ? HYDROCAM_AUTOFOCUS_TIME_MAX - : 0L; + uint32_t minTime = _alwaysAutoFocus + ? HYDROCAM_AUTOFOCUS_TIME_MS + _stabilizationTime_ms + : _stabilizationTime_ms; + uint32_t maxTime = _alwaysAutoFocus + ? HYDROCAM_AUTOFOCUS_TIME_MAX + HYDROCAM_STABILIZATION_TIME_MAX + : HYDROCAM_STABILIZATION_TIME_MAX; // If the sensor has been activated and enough time has elapsed, it's stable if (elapsed_since_wake_up > maxTime) { MS_DBG(F("It's been"), elapsed_since_wake_up, F("ms, and"), diff --git a/src/sensors/GeoluxHydroCam.h b/src/sensors/GeoluxHydroCam.h index 4e5f508b0..d30d30f06 100644 --- a/src/sensors/GeoluxHydroCam.h +++ b/src/sensors/GeoluxHydroCam.h @@ -154,7 +154,7 @@ /// @brief Sensor::_measurementTime_ms; the HydroCam imaging time is variable /// depending on the image size, but the typical minimum I've seen for the /// smallest image (160x120) is ~3.8s on firmware >2.0.1. -/// The largest image takes over 16s on firmwares <2.0.1. +/// The largest image (2592x1944) takes over 16s on firmwares <2.0.1. #define HYDROCAM_MEASUREMENT_TIME_MS 3800L /// @brief The maximum time to wait for an image. #define HYDROCAM_MEASUREMENT_TIME_MAX 18000L @@ -169,10 +169,20 @@ * SD card, not necessarily (but hopefully) the size of the image * as reported by the camera * + * @todo Figure out the largest image size the camera can produce and set the + * max accordingly. The manual doesn't specify, but there is a setting to limit + * the file size which has a maximum of 3000KB (3MB), so it seems like the + * camera should be able to produce images at least that large. For now, we'll + * set the max to a conservative 10MB (10485760 bytes). + * * {{ @ref GeoluxHydroCam_ImageSize::GeoluxHydroCam_ImageSize }} */ /**@{*/ -/// @brief Decimals places in string representation; image size should have 0 - +/// @brief Minimum image size in bytes. +#define HYDROCAM_SIZE_MIN_BYTES 0 +/// @brief Maximum image size in bytes. +#define HYDROCAM_SIZE_MAX_BYTES 10485760 +/// @brief Decimal places in string representation; image size should have 0 - /// resolution is 1 byte. #define HYDROCAM_SIZE_RESOLUTION 0 /// @brief Sensor variable number; image size is stored in sensorValues[0]. @@ -198,7 +208,12 @@ * {{ @ref GeoluxHydroCam_ByteError::GeoluxHydroCam_ByteError }} */ /**@{*/ -/// @brief Decimals places in string representation; byte error should have +/// @brief Minimum flash memory error count. +#define HYDROCAM_ERROR_MIN_COUNT 0 +/// @brief Maximum flash memory error count - can be off by as much as the max +/// image size. +#define HYDROCAM_ERROR_MAX_COUNT HYDROCAM_SIZE_MAX_BYTES +/// @brief Decimal places in string representation; byte error should have /// 0 - resolution is 1 byte. #define HYDROCAM_ERROR_RESOLUTION 0 /// @brief Sensor variable number; byte error is stored in sensorValues[1]. @@ -268,20 +283,22 @@ class GeoluxHydroCam : public Sensor { * because the autofocus takes about 30s. Default false. */ GeoluxHydroCam(Stream* stream, int8_t powerPin, Logger& baseLogger, - int8_t powerPin2, const char* imageResolution = "1600x1200", + int8_t powerPin2 = -1, + const char* imageResolution = "1600x1200", const char* filePrefix = nullptr, bool alwaysAutoFocus = false); /** * @copydoc GeoluxHydroCam::GeoluxHydroCam */ GeoluxHydroCam(Stream& stream, int8_t powerPin, Logger& baseLogger, - int8_t powerPin2, const char* imageResolution = "1600x1200", + int8_t powerPin2 = -1, + const char* imageResolution = "1600x1200", const char* filePrefix = nullptr, bool alwaysAutoFocus = false); /** * @brief Destroy the Geolux HydroCam object */ - ~GeoluxHydroCam(); + ~GeoluxHydroCam() override = default; /** * @brief Extra unique function to retrieve the name of the last saved image @@ -290,10 +307,7 @@ class GeoluxHydroCam : public Sensor { */ String getLastSavedImageName(); - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -307,7 +321,7 @@ class GeoluxHydroCam : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; /** * @brief Wake the sensor up, if necessary. Do whatever it takes to get a * sensor in the proper state to begin a measurement. @@ -321,23 +335,11 @@ class GeoluxHydroCam : public Sensor { * * @return True if the wake function completed successfully. */ - bool wake(void) override; - bool sleep(void) override; - - - /** - * @copydoc Sensor::startSingleMeasurement() - */ - bool startSingleMeasurement(void) override; - - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool wake() override; + bool sleep() override; - // Override these to use two power pins - void powerUp(void) override; - void powerDown(void) override; + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; /** * @copydoc Sensor::isWarmedUp(bool debug) @@ -374,10 +376,6 @@ class GeoluxHydroCam : public Sensor { bool isMeasurementComplete(bool debug = false) override; private: - /** - * @brief Private reference to the power pin fro the RS-485 adapter. - */ - int8_t _powerPin2; const char* _imageResolution; ///< The image resolution from the Geolux HydroCam const char* @@ -460,23 +458,13 @@ class GeoluxHydroCam_ImageSize : public Variable { explicit GeoluxHydroCam_ImageSize( GeoluxHydroCam* parentSense, const char* uuid = "", const char* varCode = HYDROCAM_SIZE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HYDROCAM_SIZE_VAR_NUM, - (uint8_t)HYDROCAM_SIZE_RESOLUTION, HYDROCAM_SIZE_VAR_NAME, - HYDROCAM_SIZE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new GeoluxHydroCam_ImageSize object. - * - * @note This must be tied with a parent GeoluxHydroCam before it can be - * used. - */ - GeoluxHydroCam_ImageSize() - : Variable((uint8_t)HYDROCAM_SIZE_VAR_NUM, - (uint8_t)HYDROCAM_SIZE_RESOLUTION, HYDROCAM_SIZE_VAR_NAME, - HYDROCAM_SIZE_UNIT_NAME, HYDROCAM_SIZE_DEFAULT_CODE) {} + : Variable(parentSense, HYDROCAM_SIZE_VAR_NUM, HYDROCAM_SIZE_RESOLUTION, + HYDROCAM_SIZE_VAR_NAME, HYDROCAM_SIZE_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the GeoluxHydroCam_ImageSize object - no action needed. */ - ~GeoluxHydroCam_ImageSize() {} + ~GeoluxHydroCam_ImageSize() override = default; }; @@ -504,26 +492,16 @@ class GeoluxHydroCam_ByteError : public Variable { explicit GeoluxHydroCam_ByteError( GeoluxHydroCam* parentSense, const char* uuid = "", const char* varCode = HYDROCAM_ERROR_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HYDROCAM_ERROR_VAR_NUM, - (uint8_t)HYDROCAM_ERROR_RESOLUTION, HYDROCAM_ERROR_VAR_NAME, + : Variable(parentSense, HYDROCAM_ERROR_VAR_NUM, + HYDROCAM_ERROR_RESOLUTION, HYDROCAM_ERROR_VAR_NAME, HYDROCAM_ERROR_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new GeoluxHydroCam_ByteError object. - * - * @note This must be tied with a parent GeoluxHydroCam before it can be - * used. - */ - GeoluxHydroCam_ByteError() - : Variable((uint8_t)HYDROCAM_ERROR_VAR_NUM, - (uint8_t)HYDROCAM_ERROR_RESOLUTION, HYDROCAM_ERROR_VAR_NAME, - HYDROCAM_ERROR_UNIT_NAME, HYDROCAM_ERROR_DEFAULT_CODE) {} /** * @brief Destroy the GeoluxHydroCam_ByteError object - no action * needed. */ - ~GeoluxHydroCam_ByteError() {} + ~GeoluxHydroCam_ByteError() override = default; }; /**@}*/ #endif // SRC_SENSORS_GEOLUXHYDROCAM_H_ -// cSpell:ignore dataloggers QQVGA QVGA QXGA UXGA autofocusing +// cSpell:words dataloggers QQVGA QVGA QXGA UXGA autofocusing diff --git a/src/sensors/GroPointGPLP8.h b/src/sensors/GroPointGPLP8.h index 7a0ea8d07..1538a66ed 100644 --- a/src/sensors/GroPointGPLP8.h +++ b/src/sensors/GroPointGPLP8.h @@ -101,7 +101,7 @@ * {{ @ref GroPointGPLP8_Moist::GroPointGPLP8_Moist }} */ /**@{*/ -/// @brief Decimals places in string representation; soil moisture should have 1 +/// @brief Decimal places in string representation; soil moisture should have 1 /// - resolution is 0.1 %. #define GPLP8_MOIST_RESOLUTION 1 /// @brief Variable name in @@ -124,7 +124,7 @@ * {{ @ref GroPointGPLP8_Temp::GroPointGPLP8_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define GPLP8_TEMP_RESOLUTION 1 /// @brief Variable name in @@ -193,7 +193,7 @@ class GroPointGPLP8 : public GroPointParent { /** * @brief Destroy the GroPoint GPLP8 object */ - ~GroPointGPLP8() {} + ~GroPointGPLP8() override = default; }; @@ -226,28 +226,13 @@ class GroPointGPLP8_Moist : public Variable { const uint8_t sensorVarNum, const char* uuid = "", const char* varCode = GPLP8_MOIST_DEFAULT_CODE) - : Variable(parentSense, sensorVarNum, (uint8_t)GPLP8_MOIST_RESOLUTION, + : Variable(parentSense, sensorVarNum, GPLP8_MOIST_RESOLUTION, GPLP8_MOIST_VAR_NAME, GPLP8_MOIST_UNIT_NAME, varCode, uuid) { } - /** - * @brief Construct a new GroPointGPLP8_Moist object. - * - * @param sensorVarNum The position the variable result holds in the - * variable result array. The GroPoint GPLP8 can have up to 8 soil moisture - * results. When creating the variable for soil moisture, you must specify - * the output number from the sensor. - * - * @note This must be tied with a parent GroPointGPLP8 before it can be - * used. - */ - GroPointGPLP8_Moist(const uint8_t sensorVarNum) - : Variable(sensorVarNum, (uint8_t)GPLP8_MOIST_RESOLUTION, - GPLP8_MOIST_VAR_NAME, GPLP8_MOIST_UNIT_NAME, - GPLP8_MOIST_DEFAULT_CODE) {} /** * @brief Destroy the GroPointGPLP8_Moist object - no action needed. */ - ~GroPointGPLP8_Moist() {} + ~GroPointGPLP8_Moist() override = default; }; /* clang-format off */ @@ -279,27 +264,12 @@ class GroPointGPLP8_Temp : public Variable { const uint8_t sensorVarNum, const char* uuid = "", const char* varCode = GPLP8_TEMP_DEFAULT_CODE) - : Variable(parentSense, sensorVarNum, (uint8_t)GPLP8_TEMP_RESOLUTION, + : Variable(parentSense, sensorVarNum, GPLP8_TEMP_RESOLUTION, GPLP8_TEMP_VAR_NAME, GPLP8_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new GroPointGPLP8_Temp object. - * - * @param sensorVarNum The position the variable result holds in the - * variable result array. The GroPoint GPLP8 can have up to 8 temperature - * results. When creating the variable for temperature, you must specify the - * output number from the sensor. - * - * @note This must be tied with a parent GroPointGPLP8 before it can be - * used. - */ - GroPointGPLP8_Temp(const uint8_t sensorVarNum) - : Variable(sensorVarNum, (uint8_t)GPLP8_TEMP_RESOLUTION, - GPLP8_TEMP_VAR_NAME, GPLP8_TEMP_UNIT_NAME, - GPLP8_TEMP_DEFAULT_CODE) {} /** * @brief Destroy the GroPointGPLP8_Temp object - no action needed. */ - ~GroPointGPLP8_Temp() {} + ~GroPointGPLP8_Temp() override = default; }; /**@}*/ diff --git a/src/sensors/GroPointParent.cpp b/src/sensors/GroPointParent.cpp index 96d941703..68e2e193a 100644 --- a/src/sensors/GroPointParent.cpp +++ b/src/sensors/GroPointParent.cpp @@ -26,8 +26,10 @@ GroPointParent::GroPointParent(byte modbusAddress, Stream* stream, _model(model), _modbusAddress(modbusAddress), _stream(stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} + _RS485EnablePin(enablePin) { + setSecondaryPowerPin(powerPin2); +} +// Delegating constructor GroPointParent::GroPointParent(byte modbusAddress, Stream& stream, int8_t powerPin, int8_t powerPin2, int8_t enablePin, uint8_t measurementsToAverage, @@ -36,32 +38,27 @@ GroPointParent::GroPointParent(byte modbusAddress, Stream& stream, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms, uint8_t incCalcValues) - : Sensor(sensName, numVariables, warmUpTime_ms, stabilizationTime_ms, - measurementTime_ms, powerPin, -1, measurementsToAverage, - incCalcValues), - _model(model), - _modbusAddress(modbusAddress), - _stream(&stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} -// Destructor -GroPointParent::~GroPointParent() {} + : GroPointParent(modbusAddress, &stream, powerPin, powerPin2, enablePin, + measurementsToAverage, model, sensName, numVariables, + warmUpTime_ms, stabilizationTime_ms, measurementTime_ms, + incCalcValues) {} // The sensor installation location on the Mayfly -String GroPointParent::getSensorLocation(void) { - String sensorLocation = F("modbus_0x"); +String GroPointParent::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve(12); // Reserve for "modbus_0x" + 2 hex chars + sensorLocation = F("modbus_0x"); if (_modbusAddress < 16) sensorLocation += "0"; sensorLocation += String(_modbusAddress, HEX); return sensorLocation; } -bool GroPointParent::setup(void) { +bool GroPointParent::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - if (_powerPin2 >= 0) { pinMode(_powerPin2, OUTPUT); } #ifdef MS_GROPOINTPARENT_DEBUG_DEEP _gsensor.setDebugStream(&MS_SERIAL_OUTPUT); #endif @@ -78,11 +75,14 @@ bool GroPointParent::setup(void) { // The function to wake up a sensor // Different from the standard in that it waits for warm up and starts // measurements -bool GroPointParent::wake(void) { +bool GroPointParent::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; + // Reset enable pin because pins are set to tri-state on sleep + if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } + // Send the command to begin taking readings, trying up to 5 times bool success = false; uint8_t ntries = 0; @@ -114,7 +114,7 @@ bool GroPointParent::wake(void) { // The function to put the sensor to sleep // Different from the standard in that it stops measurements and empties and // flushes the stream. -bool GroPointParent::sleep(void) { +bool GroPointParent::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); @@ -158,173 +158,83 @@ bool GroPointParent::sleep(void) { } -// This turns on sensor power -void GroPointParent::powerUp(void) { - if (_powerPin >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); - } - if (_powerPin2 >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin2, OUTPUT); - MS_DBG(F("Applying secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, HIGH); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - } else { - // Mark the time that the sensor was powered - _millisPowerOn = millis(); - } - // Reset enable pin because pins are set to tri-state on sleep - if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - // Set the status bit for sensor power attempt (bit 1) and success (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); -} - - -// This turns off sensor power -void GroPointParent::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); - // Unset the power-on time - _millisPowerOn = 0; - // Unset the activation time - _millisSensorActivated = 0; - // Unset the measurement request time - _millisMeasurementRequested = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - } - if (_powerPin2 >= 0) { - MS_DBG(F("Turning off secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, LOW); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Do NOT unset any status bits or timestamps if we didn't really power - // down! - } -} - +bool GroPointParent::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } -bool GroPointParent::addSingleMeasurementResult(void) { bool success = false; bool successT = false; // Initialize moisture variables for each probe segment - float M1, M2, M3, M4, M5, M6, M7, M8 = -9999; + float M1 = MS_INVALID_VALUE, M2 = MS_INVALID_VALUE, M3 = MS_INVALID_VALUE, + M4 = MS_INVALID_VALUE, M5 = MS_INVALID_VALUE, M6 = MS_INVALID_VALUE, + M7 = MS_INVALID_VALUE, M8 = MS_INVALID_VALUE; // Initialize temperature variables for each probe sensor - float T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - switch (_model) { - case GPLP8: { - // Get Moisture Values - MS_DBG(F("Get Values from"), getSensorNameAndLocation()); - success = _gsensor.getValues(M1, M2, M3, M4, M5, M6, M7, M8); - - // Fix not-a-number values - if (!success || isnan(M1)) M1 = -9999; - if (!success || isnan(M2)) M2 = -9999; - if (!success || isnan(M3)) M3 = -9999; - if (!success || isnan(M4)) M4 = -9999; - if (!success || isnan(M5)) M5 = -9999; - if (!success || isnan(M6)) M6 = -9999; - if (!success || isnan(M7)) M7 = -9999; - if (!success || isnan(M8)) M8 = -9999; - - MS_DBG(F(" "), _gsensor.getParameter()); - MS_DBG(F(" "), _gsensor.getUnits()); - MS_DBG(F(" "), M1, ',', M2, ',', M3, ',', M4, ',', M5, ',', - M6, ',', M7, ',', M8); - - // Get Temperature Values - successT = _gsensor.getTemperatureValues( - T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); - - // Fix not-a-number values - if (!successT || isnan(T1)) T1 = -9999; - if (!successT || isnan(T2)) T2 = -9999; - if (!successT || isnan(T3)) T3 = -9999; - if (!successT || isnan(T4)) T4 = -9999; - if (!successT || isnan(T5)) T5 = -9999; - if (!successT || isnan(T6)) T6 = -9999; - if (!successT || isnan(T7)) T7 = -9999; - if (!successT || isnan(T8)) T8 = -9999; - if (!successT || isnan(T9)) T9 = -9999; - if (!successT || isnan(T10)) T10 = -9999; - if (!successT || isnan(T11)) T11 = -9999; - if (!successT || isnan(T12)) T12 = -9999; - if (!successT || isnan(T13)) T13 = -9999; - - MS_DBG(F(" "), _gsensor.getParameter1()); - MS_DBG(F(" "), _gsensor.getUnits1()); - MS_DBG(F(" "), T1, ',', T2, ',', T3, ',', T4, ',', T5, ',', - T6, ',', T7, ',', T8, ',', T9, ',', T10, ',', T11, ',', - T12, ',', T13); - - - // Put values into the array - verifyAndAddMeasurementResult(0, M1); - verifyAndAddMeasurementResult(1, M2); - verifyAndAddMeasurementResult(2, M3); - verifyAndAddMeasurementResult(3, M4); - verifyAndAddMeasurementResult(4, M5); - verifyAndAddMeasurementResult(5, M6); - verifyAndAddMeasurementResult(6, M7); - verifyAndAddMeasurementResult(7, M8); - - verifyAndAddMeasurementResult(8, T1); - verifyAndAddMeasurementResult(9, T2); - verifyAndAddMeasurementResult(10, T3); - verifyAndAddMeasurementResult(11, T4); - verifyAndAddMeasurementResult(12, T5); - verifyAndAddMeasurementResult(13, T6); - verifyAndAddMeasurementResult(14, T7); - verifyAndAddMeasurementResult(15, T8); - verifyAndAddMeasurementResult(16, T9); - verifyAndAddMeasurementResult(17, T10); - verifyAndAddMeasurementResult(18, T11); - verifyAndAddMeasurementResult(19, T12); - verifyAndAddMeasurementResult(20, T13); - - - break; - } - default: { - // Get Values - MS_DBG(F("Other GroPoint models not yet implemented.")); - } + float T1 = MS_INVALID_VALUE, T2 = MS_INVALID_VALUE, T3 = MS_INVALID_VALUE, + T4 = MS_INVALID_VALUE, T5 = MS_INVALID_VALUE, T6 = MS_INVALID_VALUE, + T7 = MS_INVALID_VALUE, T8 = MS_INVALID_VALUE, T9 = MS_INVALID_VALUE, + T10 = MS_INVALID_VALUE, T11 = MS_INVALID_VALUE, + T12 = MS_INVALID_VALUE, T13 = MS_INVALID_VALUE; + + switch (_model) { + case GPLP8: { + // Get Moisture Values + MS_DBG(F("Get Values from"), getSensorNameAndLocation()); + success = _gsensor.getValues(M1, M2, M3, M4, M5, M6, M7, M8); + + MS_DBG(F(" "), _gsensor.getParameter()); + MS_DBG(F(" "), _gsensor.getUnits()); + MS_DBG(F(" "), M1, ',', M2, ',', M3, ',', M4, ',', M5, ',', M6, + ',', M7, ',', M8); + + // Get Temperature Values + successT = _gsensor.getTemperatureValues( + T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + + MS_DBG(F(" "), _gsensor.getParameter1()); + MS_DBG(F(" "), _gsensor.getUnits1()); + MS_DBG(F(" "), T1, ',', T2, ',', T3, ',', T4, ',', T5, ',', T6, + ',', T7, ',', T8, ',', T9, ',', T10, ',', T11, ',', T12, ',', + T13); + + break; + } + default: { + // Get Values + MS_DBG(F("Other GroPoint models not yet implemented.")); + break; } - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + if (success) { + // Put values into the array + verifyAndAddMeasurementResult(0, M1); + verifyAndAddMeasurementResult(1, M2); + verifyAndAddMeasurementResult(2, M3); + verifyAndAddMeasurementResult(3, M4); + verifyAndAddMeasurementResult(4, M5); + verifyAndAddMeasurementResult(5, M6); + verifyAndAddMeasurementResult(6, M7); + verifyAndAddMeasurementResult(7, M8); + } + if (successT) { + verifyAndAddMeasurementResult(8, T1); + verifyAndAddMeasurementResult(9, T2); + verifyAndAddMeasurementResult(10, T3); + verifyAndAddMeasurementResult(11, T4); + verifyAndAddMeasurementResult(12, T5); + verifyAndAddMeasurementResult(13, T6); + verifyAndAddMeasurementResult(14, T7); + verifyAndAddMeasurementResult(15, T8); + verifyAndAddMeasurementResult(16, T9); + verifyAndAddMeasurementResult(17, T10); + verifyAndAddMeasurementResult(18, T11); + verifyAndAddMeasurementResult(19, T12); + verifyAndAddMeasurementResult(20, T13); + } - // Return true when finished - return success && successT; + // Return success value when finished. Success requires both the moisture + // and temperature values to be successfully retrieved + return finalizeMeasurementAttempt((success && successT)); } // cSpell:ignore gsensor diff --git a/src/sensors/GroPointParent.h b/src/sensors/GroPointParent.h index 4a4695c3c..48ef9f5e0 100644 --- a/src/sensors/GroPointParent.h +++ b/src/sensors/GroPointParent.h @@ -1,5 +1,5 @@ /** - * @file GroPointParent.cpp + * @file GroPointParent.h * @copyright Stroud Water Research Center * Part of the EnviroDIY ModularSensors library for Arduino. * This library is published under the BSD-3 license. @@ -35,7 +35,7 @@ * They communicate via [Modbus RTU](https://en.wikipedia.org/wiki/Modbus) over [RS-485](https://en.wikipedia.org/wiki/RS-485). * To interface with them, you will need an RS485-to-TTL adapter. * - * The sensor constructor requires as input: the sensor modbus address, a stream instance for data (ie, ```Serial```), and one or two power pins. + * The sensor constructor requires as input: the sensor modbus address, a stream instance for data (i.e., ```Serial```), and one or two power pins. * The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. * (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) * Please see the section "[Notes on Arduino Streams and Software Serial](@ref page_arduino_streams)" @@ -44,9 +44,6 @@ * AltSoftSerial and HardwareSerial work fine. * Up to two power pins are provided so that the RS485 adapter, the sensor and/or an external power relay can be controlled separately. * If the power to everything is controlled by the same pin, use -1 for the second power pin or omit the argument. - * If they are controlled by different pins _and no other sensors are dependent on power from either pin_ then the order of the pins doesn't matter. - * If the RS485 adapter, sensor, or relay are controlled by different pins _and any other sensors are controlled by the same pins_ you should put the shared pin first and the un-shared pin second. - * Both pins _cannot_ be shared pins. * * By default, this library cuts power to the sensors between readings, causing them to lose track of their brushing interval. * The library manually activates the brushes as part of the "wake" command. @@ -154,12 +151,9 @@ class GroPointParent : public Sensor { /** * @brief Destroy the GroPoint Parent object - no action taken */ - virtual ~GroPointParent(); + ~GroPointParent() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -172,29 +166,18 @@ class GroPointParent : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::wake() - */ - bool wake(void) override; + bool setup() override; + /** - * @brief Puts the sensor to sleep, if necessary. - * - * This also un-sets the #_millisSensorActivated timestamp (sets it to 0). - * This does NOT power down the sensor! + * @brief Wake up the sensor and manually activate the brushes. * - * @return True if the sleep function completed successfully. + * @return True if the sensor wake and brush activation succeeded, false + * if activation failed after retries. */ - bool sleep(void) override; + bool wake() override; + bool sleep() override; - // Override these to use two power pins - void powerUp(void) override; - void powerDown(void) override; - - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; private: /** @@ -220,12 +203,8 @@ class GroPointParent : public Sensor { * pin. */ int8_t _RS485EnablePin; - /** - * @brief Private reference to the power pin fro the RS-485 adapter. - */ - int8_t _powerPin2; }; #endif // SRC_SENSORS_GROPOINTPARENT_H_ -// cSpell:ignore GPLPX gsensor +// cSpell:words GPLPX gsensor diff --git a/src/sensors/InSituRDO.h b/src/sensors/InSituRDO.h index b3f5eaf6d..c1aa81789 100644 --- a/src/sensors/InSituRDO.h +++ b/src/sensors/InSituRDO.h @@ -70,7 +70,7 @@ * 1. Disable caching: * - By default, the RDO PRO-X "caches" readings for 5000ms (5s) and will * not take a new measurement until the 5s cache expires. If you want to take - * measurements at faster than 5s intervals (ie, to average multiple + * measurements at faster than 5s intervals (i.e., to average multiple * measurements), I strongly recommend setting the cache value to 0ms using the * Win-Situ software. The cache value can be changed in the "Diagnostics" menu * found on the "Device Setup" tab of Win-Situ. @@ -212,11 +212,11 @@ * @anchor sensor_insitu_rdo_domgl * @name Dissolved Oxygen Concentration * The DO concentration variable from an In-Situ RDO PRO-X - * - Range is 0 to 50 mg/L concentration + * - Range is 0 to 60 mg/L concentration * - Accuracy: * - ± 0.1 mg/L from 0 to 8 mg/L * - ± 0.2 mg/L of reading from 8-20 mg/L - * - ± 10% of reading from 20-50 mg/L + * - ± 10% of reading from 20-60 mg/L * * @note To achieve maximum accuracy, the sensor must be calibrated using either * a one or two point calibration. @@ -224,8 +224,12 @@ * {{ @ref InSituRDO_DOmgL::InSituRDO_DOmgL }} */ /**@{*/ +/// @brief Minimum dissolved oxygen concentration in milligrams per liter. +#define INSITU_RDO_DOMGL_MIN_MGPL 0.0 +/// @brief Maximum dissolved oxygen concentration in milligrams per liter. +#define INSITU_RDO_DOMGL_MAX_MGPL 60.0 /** - * @brief Decimals places in string representation; dissolved oxygen + * @brief Decimal places in string representation; dissolved oxygen * concentration should have 2 - resolution is 0.01 mg/L. * * Contrary to the spec sheet, the actual resolution returned by the sensor in @@ -260,11 +264,18 @@ * @note To achieve maximum accuracy, the sensor must be calibrated using either * a one or two point calibration. * + * @todo Find the maximum value for percent saturation. The spec sheet only + * lists the maximum DO concentration. I've set it here to 500%. + * * {{ @ref InSituRDO_DOpct::InSituRDO_DOpct }} */ /**@{*/ +/// @brief Minimum dissolved oxygen percent saturation. +#define INSITU_RDO_DOPCT_MIN_PCT 0.0 +/// @brief Maximum dissolved oxygen percent saturation. +#define INSITU_RDO_DOPCT_MAX_PCT 500.0 /** - * @brief Decimals places in string representation; dissolved oxygen percent + * @brief Decimal places in string representation; dissolved oxygen percent * saturation should have 1. * * The actual resolution returned by the sensor in SDI-12 mode is 0.00001 %. @@ -297,9 +308,12 @@ * {{ @ref InSituRDO_Temp::InSituRDO_Temp }} */ /**@{*/ -/// @brief +/// @brief Minimum temperature in degrees Celsius. +#define INSITU_RDO_TEMP_MIN_C 0.0 +/// @brief Maximum temperature in degrees Celsius. +#define INSITU_RDO_TEMP_MAX_C 50.0 /** - * @brief Decimals places in string representation; temperature should have 2 - + * @brief Decimal places in string representation; temperature should have 2 - * resolution is 0.01°C. * * The spec sheet lists 2 decimal resolution, but the returned value has 5. @@ -329,10 +343,13 @@ * @note The oxygen partial pressure output must be manually enabled in SDI-12 * mode using the Win-Situ software. * + * @todo Find and define minimum and maximum oxygen partial pressure measurement + * range + * * {{ @ref InSituRDO_Pressure::InSituRDO_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; pressure should have 3 +/// @brief Decimal places in string representation; pressure should have 2 #define INSITU_RDO_PRESSURE_RESOLUTION 2 /// @brief Variable number; temperature is stored in sensorValues[3]. #define INSITU_RDO_PRESSURE_VAR_NUM 3 @@ -413,7 +430,7 @@ class InSituRDO : public SDI12Sensors { /** * @brief Destroy the In-Situ RDO object */ - ~InSituRDO() {} + ~InSituRDO() override = default; }; @@ -441,25 +458,13 @@ class InSituRDO_DOmgL : public Variable { explicit InSituRDO_DOmgL( InSituRDO* parentSense, const char* uuid = "", const char* varCode = INSITU_RDO_DOMGL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INSITU_RDO_DOMGL_VAR_NUM, - (uint8_t)INSITU_RDO_DOMGL_RESOLUTION, - INSITU_RDO_DOMGL_VAR_NAME, INSITU_RDO_DOMGL_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new InSituRDO_DOmgL object. - * - * @note This must be tied with a parent InSituRDO before it can be - * used. - */ - InSituRDO_DOmgL() - : Variable((uint8_t)INSITU_RDO_DOMGL_VAR_NUM, - (uint8_t)INSITU_RDO_DOMGL_RESOLUTION, - INSITU_RDO_DOMGL_VAR_NAME, INSITU_RDO_DOMGL_UNIT_NAME, - INSITU_RDO_DOMGL_DEFAULT_CODE) {} + : Variable(parentSense, INSITU_RDO_DOMGL_VAR_NUM, + INSITU_RDO_DOMGL_RESOLUTION, INSITU_RDO_DOMGL_VAR_NAME, + INSITU_RDO_DOMGL_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the InSituRDO_DOmgL object - no action needed. */ - ~InSituRDO_DOmgL() {} + ~InSituRDO_DOmgL() override = default; }; @@ -487,25 +492,13 @@ class InSituRDO_DOpct : public Variable { explicit InSituRDO_DOpct( InSituRDO* parentSense, const char* uuid = "", const char* varCode = INSITU_RDO_DOPCT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INSITU_RDO_DOPCT_VAR_NUM, - (uint8_t)INSITU_RDO_DOPCT_RESOLUTION, - INSITU_RDO_DOPCT_VAR_NAME, INSITU_RDO_DOPCT_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new InSituRDO_DOpct object. - * - * @note This must be tied with a parent InSituRDO before it can be - * used. - */ - InSituRDO_DOpct() - : Variable((uint8_t)INSITU_RDO_DOPCT_VAR_NUM, - (uint8_t)INSITU_RDO_DOPCT_RESOLUTION, - INSITU_RDO_DOPCT_VAR_NAME, INSITU_RDO_DOPCT_UNIT_NAME, - INSITU_RDO_DOPCT_DEFAULT_CODE) {} + : Variable(parentSense, INSITU_RDO_DOPCT_VAR_NUM, + INSITU_RDO_DOPCT_RESOLUTION, INSITU_RDO_DOPCT_VAR_NAME, + INSITU_RDO_DOPCT_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the InSituRDO_DOpct object - no action needed. */ - ~InSituRDO_DOpct() {} + ~InSituRDO_DOpct() override = default; }; @@ -532,25 +525,13 @@ class InSituRDO_Temp : public Variable { */ explicit InSituRDO_Temp(InSituRDO* parentSense, const char* uuid = "", const char* varCode = INSITU_RDO_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INSITU_RDO_TEMP_VAR_NUM, - (uint8_t)INSITU_RDO_TEMP_RESOLUTION, - INSITU_RDO_TEMP_VAR_NAME, INSITU_RDO_TEMP_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new InSituRDO_Temp object. - * - * @note This must be tied with a parent InSituRDO before it can be - * used. - */ - InSituRDO_Temp() - : Variable((uint8_t)INSITU_RDO_TEMP_VAR_NUM, - (uint8_t)INSITU_RDO_TEMP_RESOLUTION, - INSITU_RDO_TEMP_VAR_NAME, INSITU_RDO_TEMP_UNIT_NAME, - INSITU_RDO_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, INSITU_RDO_TEMP_VAR_NUM, + INSITU_RDO_TEMP_RESOLUTION, INSITU_RDO_TEMP_VAR_NAME, + INSITU_RDO_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the InSituRDO_Temp object - no action needed. */ - ~InSituRDO_Temp() {} + ~InSituRDO_Temp() override = default; }; @@ -578,25 +559,13 @@ class InSituRDO_Pressure : public Variable { explicit InSituRDO_Pressure( InSituRDO* parentSense, const char* uuid = "", const char* varCode = INSITU_RDO_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INSITU_RDO_PRESSURE_VAR_NUM, - (uint8_t)INSITU_RDO_PRESSURE_RESOLUTION, - INSITU_RDO_PRESSURE_VAR_NAME, INSITU_RDO_PRESSURE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new InSituRDO_Pressure object. - * - * @note This must be tied with a parent InSituRDO before it can be - * used. - */ - InSituRDO_Pressure() - : Variable((uint8_t)INSITU_RDO_PRESSURE_VAR_NUM, - (uint8_t)INSITU_RDO_PRESSURE_RESOLUTION, - INSITU_RDO_PRESSURE_VAR_NAME, INSITU_RDO_PRESSURE_UNIT_NAME, - INSITU_RDO_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, INSITU_RDO_PRESSURE_VAR_NUM, + INSITU_RDO_PRESSURE_RESOLUTION, INSITU_RDO_PRESSURE_VAR_NAME, + INSITU_RDO_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the InSituRDO_Pressure object - no action needed. */ - ~InSituRDO_Pressure() {} + ~InSituRDO_Pressure() override = default; }; /**@}*/ #endif // SRC_SENSORS_INSITURDO_H_ diff --git a/src/sensors/InSituTrollSdi12a.h b/src/sensors/InSituTrollSdi12a.h index fc2ace370..ad88b585f 100644 --- a/src/sensors/InSituTrollSdi12a.h +++ b/src/sensors/InSituTrollSdi12a.h @@ -47,7 +47,7 @@ * Parameters are very flexible and need to be aligned used WinSitu with this module. * * The depth sensor third parameter may need to be created. The expected - * parameters and order are Pressure (PSI), Temperature (C), Depth (ft). + * parameters and order are Pressure (PSI), Temperature (C), Depth (ft). * * Tested with Level TROLL 500. * @@ -117,18 +117,17 @@ * @anchor sensor_insitu_troll_pressure * @name Pressure * The pressure variable from a In-Situ TROLL - * - Range is 0 – x (depends on range eg 5psig) + * - Range depends on the model; the highest range models go up to 500 psig. * * {{ @ref InSituTrollSdi12a_Pressure::InSituTrollSdi12a_Pressure }} */ /**@{*/ -/** - * @brief Decimals places in string representation; conductivity should have 1. - * - * 0 are reported, adding extra digit to resolution to allow the proper number - * of significant figures for averaging - resolution is 0.001 mS/cm = 1 µS/cm - */ +/// @brief Minimum pressure; 0 psi +#define ITROLLA_PRESSURE_MIN_PSI 0 +/// @brief Maximum pressure; 500 psi (depends on model range) +#define ITROLLA_PRESSURE_MAX_PSI 500 +/// @brief Decimal places in string representation; pressure should have 5. #define ITROLLA_PRESSURE_RESOLUTION 5 /// @brief Sensor variable number; pressure is stored in sensorValues[0]. #define ITROLLA_PRESSURE_VAR_NUM 0 @@ -154,8 +153,12 @@ * {{ @ref InSituTrollSdi12a_Temp::InSituTrollSdi12a_Temp }} */ /**@{*/ +/// @brief Minimum temperature; -11°C +#define ITROLLA_TEMP_MIN_C -11 +/// @brief Maximum temperature; 49°C +#define ITROLLA_TEMP_MAX_C 49 /** - * @brief Decimals places in string representation; temperature should have 2. + * @brief Decimal places in string representation; temperature should have 2. * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C @@ -179,14 +182,18 @@ * @anchor sensor_insitu_troll_depth * @name Water Depth * The water depth variable from a In-Situ TROLL - * - Range is 0 to 3.5m to 350m depending on model + * - Range is 0 to 3.5m to 350m (up to 1150ft) depending on model * - Accuracy is ±0.05% of full scale * * {{ @ref InSituTrollSdi12a_Depth::InSituTrollSdi12a_Depth }} */ /**@{*/ +/// @brief Minimum depth; 0 feet +#define ITROLLA_DEPTH_MIN_FT 0 +/// @brief Maximum depth; 1150 feet (depending on model) +#define ITROLLA_DEPTH_MAX_FT 1150 /** - * @brief Decimals places in string representation; depth should have 1. + * @brief Decimal places in string representation; depth should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 2 mm @@ -270,7 +277,7 @@ class InSituTrollSdi12a : public SDI12Sensors { /** * @brief Destroy the ITROLL object */ - ~InSituTrollSdi12a() {} + ~InSituTrollSdi12a() override = default; }; @@ -297,25 +304,13 @@ class InSituTrollSdi12a_Pressure : public Variable { InSituTrollSdi12a_Pressure( Sensor* parentSense, const char* uuid = "", const char* varCode = ITROLLA_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ITROLLA_PRESSURE_VAR_NUM, - (uint8_t)ITROLLA_PRESSURE_RESOLUTION, - ITROLLA_PRESSURE_VAR_NAME, ITROLLA_PRESSURE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new InSituTrollSdi12a_Pressure object. - * - * @note This must be tied with a parent InSituTrollSdi12a before it can be - * used. - */ - InSituTrollSdi12a_Pressure() - : Variable((uint8_t)ITROLLA_PRESSURE_VAR_NUM, - (uint8_t)ITROLLA_PRESSURE_RESOLUTION, - ITROLLA_PRESSURE_VAR_NAME, ITROLLA_PRESSURE_UNIT_NAME, - ITROLLA_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, ITROLLA_PRESSURE_VAR_NUM, + ITROLLA_PRESSURE_RESOLUTION, ITROLLA_PRESSURE_VAR_NAME, + ITROLLA_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the InSituTrollSdi12a_Pressure object - no action needed. */ - ~InSituTrollSdi12a_Pressure() {} + ~InSituTrollSdi12a_Pressure() override = default; }; @@ -341,24 +336,13 @@ class InSituTrollSdi12a_Temp : public Variable { */ InSituTrollSdi12a_Temp(Sensor* parentSense, const char* uuid = "", const char* varCode = ITROLLA_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ITROLLA_TEMP_VAR_NUM, - (uint8_t)ITROLLA_TEMP_RESOLUTION, ITROLLA_TEMP_TEMP_VAR_NAME, - ITROLLA_TEMP_TEMP_UNIT_NAME, varCode, uuid) {} - - /** - * @brief Construct a new InSituTrollSdi12a_Temp object. - * - * @note This must be tied with a parent InSituTrollSdi12a before it can be - * used. - */ - InSituTrollSdi12a_Temp() - : Variable((uint8_t)ITROLLA_TEMP_VAR_NUM, - (uint8_t)ITROLLA_TEMP_RESOLUTION, ITROLLA_TEMP_TEMP_VAR_NAME, - ITROLLA_TEMP_TEMP_UNIT_NAME, ITROLLA_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, ITROLLA_TEMP_VAR_NUM, ITROLLA_TEMP_RESOLUTION, + ITROLLA_TEMP_TEMP_VAR_NAME, ITROLLA_TEMP_TEMP_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the InSituTrollSdi12a_Temp object - no action needed. */ - ~InSituTrollSdi12a_Temp() {} + ~InSituTrollSdi12a_Temp() override = default; }; @@ -384,23 +368,13 @@ class InSituTrollSdi12a_Depth : public Variable { */ InSituTrollSdi12a_Depth(Sensor* parentSense, const char* uuid = "", const char* varCode = ITROLLA_DEPTH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)ITROLLA_DEPTH_VAR_NUM, - (uint8_t)ITROLLA_DEPTH_RESOLUTION, ITROLLA_DEPTH_VAR_NAME, - ITROLLA_DEPTH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new InSituTrollSdi12a_Depth object. - * - * @note This must be tied with a parent InSituTrollSdi12a before it can be - * used. - */ - InSituTrollSdi12a_Depth() - : Variable((uint8_t)ITROLLA_DEPTH_VAR_NUM, - (uint8_t)ITROLLA_DEPTH_RESOLUTION, ITROLLA_DEPTH_VAR_NAME, - ITROLLA_DEPTH_UNIT_NAME, ITROLLA_DEPTH_DEFAULT_CODE) {} + : Variable(parentSense, ITROLLA_DEPTH_VAR_NUM, ITROLLA_DEPTH_RESOLUTION, + ITROLLA_DEPTH_VAR_NAME, ITROLLA_DEPTH_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the InSituTrollSdi12a_Depth object - no action needed. */ - ~InSituTrollSdi12a_Depth() {} + ~InSituTrollSdi12a_Depth() override = default; }; /**@}*/ @@ -412,4 +386,4 @@ typedef InSituTrollSdi12a_Depth InsituTrollSdi12a_Depth; #endif // SRC_SENSORS_INSITUTROLLSDI12_H_ -// cSpell:ignore ITROLL ITROLLA +// cSpell:words ITROLL ITROLLA diff --git a/src/sensors/KellerAcculevel.h b/src/sensors/KellerAcculevel.h index fabf79f6e..f3e990702 100644 --- a/src/sensors/KellerAcculevel.h +++ b/src/sensors/KellerAcculevel.h @@ -83,7 +83,11 @@ * {{ @ref KellerAcculevel_Pressure::KellerAcculevel_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; pressure should have 5 - +/// @brief Minimum pressure; 0 bar +#define ACCULEVEL_PRESSURE_MIN_BAR 0 +/// @brief Maximum pressure; 11 bar +#define ACCULEVEL_PRESSURE_MAX_BAR 11 +/// @brief Decimal places in string representation; pressure should have 5 - /// resolution is 0.002%. #define ACCULEVEL_PRESSURE_RESOLUTION 5 /// @brief Default variable short code; "kellerAccuPress" @@ -100,7 +104,11 @@ * {{ @ref KellerAcculevel_Temp::KellerAcculevel_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature; -10°C +#define ACCULEVEL_TEMP_MIN_C -10 +/// @brief Maximum temperature; 60°C +#define ACCULEVEL_TEMP_MAX_C 60 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define ACCULEVEL_TEMP_RESOLUTION 2 /// @brief Default variable short code; "kellerAccuTemp" @@ -111,13 +119,17 @@ * @anchor sensor_acculevel_height * @name Height * The height variable from a Keller Acculevel - * - Range is 0 to 900 feet + * - Range is 0 to 900 feet (0 to 275 m) * - Accuracy is Standard ±0.1% FS, Optional ±0.05% FS * * {{ @ref KellerAcculevel_Height::KellerAcculevel_Height }} */ /**@{*/ -/// @brief Decimals places in string representation; height should have 4 - +/// @brief Minimum height; 0 feet (0 m) +#define ACCULEVEL_HEIGHT_MIN_M 0 +/// @brief Maximum height; 900 feet (275 m) +#define ACCULEVEL_HEIGHT_MAX_M 275 +/// @brief Decimal places in string representation; height should have 4 - /// resolution is 0.002%. #define ACCULEVEL_HEIGHT_RESOLUTION 4 /// @brief Default variable short code; "kellerAccuHeight" @@ -179,7 +191,7 @@ class KellerAcculevel : public KellerParent { /** * @brief Destroy the Keller Acculevel object */ - ~KellerAcculevel() {} + ~KellerAcculevel() override = default; }; @@ -207,25 +219,13 @@ class KellerAcculevel_Pressure : public Variable { explicit KellerAcculevel_Pressure( KellerAcculevel* parentSense, const char* uuid = "", const char* varCode = ACCULEVEL_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_PRESSURE_VAR_NUM, - (uint8_t)ACCULEVEL_PRESSURE_RESOLUTION, - KELLER_PRESSURE_VAR_NAME, KELLER_PRESSURE_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new KellerAcculevel_Pressure object. - * - * @note This must be tied with a parent KellerAcculevel before it can be - * used. - */ - KellerAcculevel_Pressure() - : Variable((uint8_t)KELLER_PRESSURE_VAR_NUM, - (uint8_t)ACCULEVEL_PRESSURE_RESOLUTION, - KELLER_PRESSURE_VAR_NAME, KELLER_PRESSURE_UNIT_NAME, - ACCULEVEL_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, KELLER_PRESSURE_VAR_NUM, + ACCULEVEL_PRESSURE_RESOLUTION, KELLER_PRESSURE_VAR_NAME, + KELLER_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the KellerAcculevel_Pressure object - no action needed. */ - ~KellerAcculevel_Pressure() {} + ~KellerAcculevel_Pressure() override = default; }; @@ -253,23 +253,13 @@ class KellerAcculevel_Temp : public Variable { explicit KellerAcculevel_Temp( KellerAcculevel* parentSense, const char* uuid = "", const char* varCode = ACCULEVEL_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_TEMP_VAR_NUM, - (uint8_t)ACCULEVEL_TEMP_RESOLUTION, KELLER_TEMP_VAR_NAME, - KELLER_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new KellerAcculevel_Temp object. - * - * @note This must be tied with a parent KellerAcculevel before it can be - * used. - */ - KellerAcculevel_Temp() - : Variable((uint8_t)KELLER_TEMP_VAR_NUM, - (uint8_t)ACCULEVEL_TEMP_RESOLUTION, KELLER_TEMP_VAR_NAME, - KELLER_TEMP_UNIT_NAME, ACCULEVEL_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, KELLER_TEMP_VAR_NUM, ACCULEVEL_TEMP_RESOLUTION, + KELLER_TEMP_VAR_NAME, KELLER_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the KellerAcculevel_Temp object - no action needed. */ - ~KellerAcculevel_Temp() {} + ~KellerAcculevel_Temp() override = default; }; @@ -297,25 +287,15 @@ class KellerAcculevel_Height : public Variable { explicit KellerAcculevel_Height( KellerAcculevel* parentSense, const char* uuid = "", const char* varCode = ACCULEVEL_HEIGHT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_HEIGHT_VAR_NUM, - (uint8_t)ACCULEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, + : Variable(parentSense, KELLER_HEIGHT_VAR_NUM, + ACCULEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, KELLER_HEIGHT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new KellerAcculevel_Height object. - * - * @note This must be tied with a parent KellerAcculevel before it can be - * used. - */ - KellerAcculevel_Height() - : Variable((uint8_t)KELLER_HEIGHT_VAR_NUM, - (uint8_t)ACCULEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, - KELLER_HEIGHT_UNIT_NAME, ACCULEVEL_HEIGHT_DEFAULT_CODE) {} /** * @brief Destroy the KellerAcculevel_Height object - no action needed. */ - ~KellerAcculevel_Height() {} + ~KellerAcculevel_Height() override = default; }; /**@}*/ #endif // SRC_SENSORS_KELLERACCULEVEL_H_ -// cSpell:ignore kellerAccuPress kellerAccuTemp kellerAccuHeight +// cSpell:words kellerAccuPress kellerAccuTemp kellerAccuHeight diff --git a/src/sensors/KellerNanolevel.h b/src/sensors/KellerNanolevel.h index 94f3b81c7..66667c443 100644 --- a/src/sensors/KellerNanolevel.h +++ b/src/sensors/KellerNanolevel.h @@ -75,7 +75,11 @@ * {{ @ref KellerNanolevel_Pressure::KellerNanolevel_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; pressure should have 5 - +/// @brief Minimum pressure; 0 mbar +#define NANOLEVEL_PRESSURE_MIN_MBAR 0 +/// @brief Maximum pressure; 300 mbar +#define NANOLEVEL_PRESSURE_MAX_MBAR 300 +/// @brief Decimal places in string representation; pressure should have 5 - /// resolution is 0.002%. #define NANOLEVEL_PRESSURE_RESOLUTION 5 /// @brief Default variable short code; "kellerNanoPress" @@ -92,7 +96,11 @@ * {{ @ref KellerNanolevel_Temp::KellerNanolevel_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature; 10°C +#define NANOLEVEL_TEMP_MIN_C 10 +/// @brief Maximum temperature; 50°C +#define NANOLEVEL_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define NANOLEVEL_TEMP_RESOLUTION 2 /// @brief Default variable short code; "kellerNanoTemp" @@ -103,13 +111,17 @@ * @anchor sensor_nanolevel_height * @name Height * The height variable from a Keller Nanolevel - * - Range is 0 to 120 inches + * - Range is 0 to 120 inches (0 to 3.048 m) * - Accuracy is Standard ±0.1% FS, Optional ±0.05% FS * * {{ @ref KellerNanolevel_Height::KellerNanolevel_Height }} */ /**@{*/ -/// @brief Decimals places in string representation; height should have 4 - +/// @brief Minimum height; 0 inches (0 m) +#define NANOLEVEL_HEIGHT_MIN_M 0 +/// @brief Maximum height; 120 inches (3.048 m) +#define NANOLEVEL_HEIGHT_MAX_M 3.048 +/// @brief Decimal places in string representation; height should have 4 - /// resolution is 0.002%. #define NANOLEVEL_HEIGHT_RESOLUTION 4 /// @brief Default variable short code; "kellerNanoHeight" @@ -171,7 +183,7 @@ class KellerNanolevel : public KellerParent { /** * @brief Destroy the Keller Nanolevel object */ - ~KellerNanolevel() {} + ~KellerNanolevel() override = default; }; @@ -199,25 +211,13 @@ class KellerNanolevel_Pressure : public Variable { explicit KellerNanolevel_Pressure( KellerNanolevel* parentSense, const char* uuid = "", const char* varCode = NANOLEVEL_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_PRESSURE_VAR_NUM, - (uint8_t)NANOLEVEL_PRESSURE_RESOLUTION, - KELLER_PRESSURE_VAR_NAME, KELLER_PRESSURE_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new KellerNanolevel_Pressure object. - * - * @note This must be tied with a parent KellerNanolevel before it can be - * used. - */ - KellerNanolevel_Pressure() - : Variable((uint8_t)KELLER_PRESSURE_VAR_NUM, - (uint8_t)NANOLEVEL_PRESSURE_RESOLUTION, - KELLER_PRESSURE_VAR_NAME, KELLER_PRESSURE_UNIT_NAME, - NANOLEVEL_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, KELLER_PRESSURE_VAR_NUM, + NANOLEVEL_PRESSURE_RESOLUTION, KELLER_PRESSURE_VAR_NAME, + KELLER_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the KellerNanolevel_Pressure object - no action needed. */ - ~KellerNanolevel_Pressure() {} + ~KellerNanolevel_Pressure() override = default; }; @@ -245,23 +245,13 @@ class KellerNanolevel_Temp : public Variable { explicit KellerNanolevel_Temp( KellerNanolevel* parentSense, const char* uuid = "", const char* varCode = NANOLEVEL_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_TEMP_VAR_NUM, - (uint8_t)NANOLEVEL_TEMP_RESOLUTION, KELLER_TEMP_VAR_NAME, - KELLER_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new KellerNanolevel_Temp object. - * - * @note This must be tied with a parent KellerNanolevel before it can be - * used. - */ - KellerNanolevel_Temp() - : Variable((uint8_t)KELLER_TEMP_VAR_NUM, - (uint8_t)NANOLEVEL_TEMP_RESOLUTION, KELLER_TEMP_VAR_NAME, - KELLER_TEMP_UNIT_NAME, NANOLEVEL_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, KELLER_TEMP_VAR_NUM, NANOLEVEL_TEMP_RESOLUTION, + KELLER_TEMP_VAR_NAME, KELLER_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the KellerNanolevel_Temp object - no action needed. */ - ~KellerNanolevel_Temp() {} + ~KellerNanolevel_Temp() override = default; }; @@ -289,23 +279,13 @@ class KellerNanolevel_Height : public Variable { explicit KellerNanolevel_Height( KellerNanolevel* parentSense, const char* uuid = "", const char* varCode = NANOLEVEL_HEIGHT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)KELLER_HEIGHT_VAR_NUM, - (uint8_t)NANOLEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, + : Variable(parentSense, KELLER_HEIGHT_VAR_NUM, + NANOLEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, KELLER_HEIGHT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new KellerNanolevel_Height object. - * - * @note This must be tied with a parent KellerNanolevel before it can be - * used. - */ - KellerNanolevel_Height() - : Variable((uint8_t)KELLER_HEIGHT_VAR_NUM, - (uint8_t)NANOLEVEL_HEIGHT_RESOLUTION, KELLER_HEIGHT_VAR_NAME, - KELLER_HEIGHT_UNIT_NAME, NANOLEVEL_HEIGHT_DEFAULT_CODE) {} /** * @brief Destroy the KellerNanolevel_Height object - no action needed. */ - ~KellerNanolevel_Height() {} + ~KellerNanolevel_Height() override = default; }; /**@}*/ #endif // SRC_SENSORS_KELLERNANOLEVEL_H_ diff --git a/src/sensors/KellerParent.cpp b/src/sensors/KellerParent.cpp index e8a657441..73379a3d6 100644 --- a/src/sensors/KellerParent.cpp +++ b/src/sensors/KellerParent.cpp @@ -26,8 +26,10 @@ KellerParent::KellerParent(byte modbusAddress, Stream* stream, int8_t powerPin, _model(model), _modbusAddress(modbusAddress), _stream(stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} + _RS485EnablePin(enablePin) { + setSecondaryPowerPin(powerPin2); +} +// Delegating constructor KellerParent::KellerParent(byte modbusAddress, Stream& stream, int8_t powerPin, int8_t powerPin2, int8_t enablePin, uint8_t measurementsToAverage, kellerModel model, @@ -35,32 +37,26 @@ KellerParent::KellerParent(byte modbusAddress, Stream& stream, int8_t powerPin, uint32_t warmUpTime_ms, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms) - : Sensor(sensName, numVariables, warmUpTime_ms, stabilizationTime_ms, - measurementTime_ms, powerPin, -1, measurementsToAverage, - KELLER_INC_CALC_VARIABLES), - _model(model), - _modbusAddress(modbusAddress), - _stream(&stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} -// Destructor -KellerParent::~KellerParent() {} + : KellerParent(modbusAddress, &stream, powerPin, powerPin2, enablePin, + measurementsToAverage, model, sensName, numVariables, + warmUpTime_ms, stabilizationTime_ms, measurementTime_ms) {} // The sensor installation location on the Mayfly -String KellerParent::getSensorLocation(void) { - String sensorLocation = F("modbus_0x"); +String KellerParent::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve(12); // Reserve for "modbus_0x" + 2 hex chars + sensorLocation = F("modbus_0x"); if (_modbusAddress < 16) sensorLocation += "0"; sensorLocation += String(_modbusAddress, HEX); return sensorLocation; } -bool KellerParent::setup(void) { +bool KellerParent::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - if (_powerPin2 >= 0) { pinMode(_powerPin2, OUTPUT); } #ifdef MS_KELLERPARENT_DEBUG_DEEP _ksensor.setDebugStream(&MS_SERIAL_OUTPUT); @@ -77,129 +73,62 @@ bool KellerParent::setup(void) { // The function to put the sensor to sleep // Different from the standard in that empties and flushes the stream. -bool KellerParent::sleep(void) { +bool KellerParent::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); return Sensor::sleep(); -}; - - -// This turns on sensor power -void KellerParent::powerUp(void) { - if (_powerPin >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); - } - if (_powerPin2 >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin2, OUTPUT); - MS_DBG(F("Applying secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, HIGH); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - } else { - // Mark the time that the sensor was powered - _millisPowerOn = millis(); - } - // Reset enable pin because pins are set to tri-state on sleep - if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - // Set the status bit for sensor power attempt (bit 1) and success (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); } -// This turns off sensor power -void KellerParent::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); - // Unset the power-on time - _millisPowerOn = 0; - // Unset the activation time - _millisSensorActivated = 0; - // Unset the measurement request time - _millisMeasurementRequested = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - } - if (_powerPin2 >= 0) { - MS_DBG(F("Turning off secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, LOW); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Do NOT unset any status bits or timestamps if we didn't really power - // down! - } -} - +bool KellerParent::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } -bool KellerParent::addSingleMeasurementResult(void) { - bool success = false; + bool success = false; + float waterPressureBar = MS_INVALID_VALUE; + float waterTemperatureC = MS_INVALID_VALUE; + float waterDepthM = MS_INVALID_VALUE; + float waterPressure_mBar = MS_INVALID_VALUE; - // Initialize float variables - float waterPressureBar = -9999; - float waterTemperatureC = -9999; - float waterDepthM = -9999; - float waterPressure_mBar = -9999; + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - // Get Values - success = _ksensor.getValues(waterPressureBar, waterTemperatureC); - waterDepthM = _ksensor.calcWaterDepthM( - waterPressureBar, - waterTemperatureC); // float calcWaterDepthM(float - // waterPressureBar, float waterTemperatureC) - - // Fix not-a-number values - if (!success || isnan(waterPressureBar)) waterPressureBar = -9999; - if (!success || isnan(waterTemperatureC)) waterTemperatureC = -9999; - if (!success || isnan(waterDepthM)) waterDepthM = -9999; + // NOTE: As of KellerModbus library version 0.2.7, the getValues function + // will *always* return true. We set the success with it here for + // future-readiness. + success = _ksensor.getValues(waterPressureBar, waterTemperatureC); + // Since we can't depend on the return value, we check for success by + // checking that the value variables are not MS_INVALID_VALUE or NaN. + success &= + (!isnan(waterPressureBar) && waterPressureBar != MS_INVALID_VALUE && + !isnan(waterTemperatureC) && waterTemperatureC != MS_INVALID_VALUE); + if (success) { // For waterPressureBar, convert bar to millibar - if (waterPressureBar != -9999) - waterPressure_mBar = 1000 * waterPressureBar; - + waterPressure_mBar = 1000 * waterPressureBar; + // display values for debugging MS_DBG(F(" Pressure_mbar:"), waterPressure_mBar); MS_DBG(F(" Temp_C:"), waterTemperatureC); + // Put pressure and temperature into the array + verifyAndAddMeasurementResult(KELLER_PRESSURE_VAR_NUM, + waterPressure_mBar); + verifyAndAddMeasurementResult(KELLER_TEMP_VAR_NUM, waterTemperatureC); + + // calculate depth from pressure and temperature + waterDepthM = _ksensor.calcWaterDepthM(waterPressureBar, + waterTemperatureC); + // display value for debugging MS_DBG(F(" Height_m:"), waterDepthM); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); } - // Put values into the array - verifyAndAddMeasurementResult(KELLER_PRESSURE_VAR_NUM, waterPressure_mBar); - verifyAndAddMeasurementResult(KELLER_TEMP_VAR_NUM, waterTemperatureC); - verifyAndAddMeasurementResult(KELLER_HEIGHT_VAR_NUM, waterDepthM); + success &= (!isnan(waterDepthM) && waterDepthM != MS_INVALID_VALUE); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + if (success) { + verifyAndAddMeasurementResult(KELLER_HEIGHT_VAR_NUM, waterDepthM); + } - // Return true when finished - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } // cSpell:ignore ksensor diff --git a/src/sensors/KellerParent.h b/src/sensors/KellerParent.h index 4a4148670..d3970ca29 100644 --- a/src/sensors/KellerParent.h +++ b/src/sensors/KellerParent.h @@ -50,8 +50,8 @@ * Digital communication with Keller sensors configured for SDI12 communication * protocols are not supported by this library. * - * The sensor constructors require as input: the sensor modbus address, a - * stream instance for data (ie, ```Serial```), and one or two power pins. The + * The sensor constructors require as input: the sensor modbus address, a + * stream instance for data (i.e., ```Serial```), and one or two power pins. The * Arduino pin controlling the receive and data enable on your RS485-to-TTL * adapter and the number of readings to average are optional. (Use -1 for the * second power pin and -1 for the enable pin if these don't apply and you want @@ -231,12 +231,9 @@ class KellerParent : public Sensor { /** * @brief Destroy the Keller Parent object - no action taken */ - virtual ~KellerParent(); + ~KellerParent() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -249,19 +246,16 @@ class KellerParent : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - - // override to empty and flush the stream - bool sleep(void) override; - - // Override these to use two power pins - void powerUp(void) override; - void powerDown(void) override; + bool setup() override; /** - * @copydoc Sensor::addSingleMeasurementResult() + * @brief Empty and flush the stream before sleeping. + * + * @return True if sleep was successful. */ - bool addSingleMeasurementResult(void) override; + bool sleep() override; + + bool addSingleMeasurementResult() override; private: /** @@ -287,12 +281,8 @@ class KellerParent : public Sensor { * pin. */ int8_t _RS485EnablePin; - /** - * @brief Private reference to the power pin fro the RS-485 adapter. - */ - int8_t _powerPin2; }; /**@}*/ #endif // SRC_SENSORS_KELLERPARENT_H_ -// cSpell:ignore ksensor +// cSpell:words ksensor diff --git a/src/sensors/MaxBotixSonar.cpp b/src/sensors/MaxBotixSonar.cpp index 92e430330..52401c1ea 100644 --- a/src/sensors/MaxBotixSonar.cpp +++ b/src/sensors/MaxBotixSonar.cpp @@ -16,37 +16,34 @@ MaxBotixSonar::MaxBotixSonar(Stream* stream, int8_t powerPin, int8_t triggerPin, bool convertCm) : Sensor("MaxBotixMaxSonar", HRXL_NUM_VARIABLES, HRXL_WARM_UP_TIME_MS, HRXL_STABILIZATION_TIME_MS, HRXL_MEASUREMENT_TIME_MS, powerPin, -1, - measurementsToAverage), + measurementsToAverage, HRXL_INC_CALC_VARIABLES), _maxRange(maxRange), _triggerPin(triggerPin), _convertCm(convertCm), - _stream(stream) {} - + _stream(stream) { + setMaxRetries(MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES); +} +// Delegating constructor MaxBotixSonar::MaxBotixSonar(Stream& stream, int8_t powerPin, int8_t triggerPin, int16_t maxRange, uint8_t measurementsToAverage, bool convertCm) - : Sensor("MaxBotixMaxSonar", HRXL_NUM_VARIABLES, HRXL_WARM_UP_TIME_MS, - HRXL_STABILIZATION_TIME_MS, HRXL_MEASUREMENT_TIME_MS, powerPin, -1, - measurementsToAverage, HRXL_INC_CALC_VARIABLES), - _maxRange(maxRange), - _triggerPin(triggerPin), - _convertCm(convertCm), - _stream(&stream) {} - -// Destructor -MaxBotixSonar::~MaxBotixSonar() {} + : MaxBotixSonar(&stream, powerPin, triggerPin, maxRange, + measurementsToAverage, convertCm) {} // unfortunately, we really cannot know where the stream is attached. -String MaxBotixSonar::getSensorLocation(void) { +String MaxBotixSonar::getSensorLocation() { // attach the trigger pin to the stream number - String loc = "sonarStream_trigger" + String(_triggerPin); + String loc; + loc.reserve(25); // Reserve for "sonarStream_trigger" + pin number + loc = F("sonarStream_trigger"); + loc += _triggerPin; return loc; } -bool MaxBotixSonar::setup(void) { +bool MaxBotixSonar::setup() { // Set up the trigger, if applicable if (_triggerPin >= 0) { pinMode(_triggerPin, OUTPUT); @@ -62,7 +59,7 @@ bool MaxBotixSonar::setup(void) { // Parsing and tossing the header lines in the wake-up -bool MaxBotixSonar::wake(void) { +bool MaxBotixSonar::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; @@ -93,46 +90,90 @@ bool MaxBotixSonar::wake(void) { MS_DBG(i, '-', headerLine); } // Clear anything else out of the stream buffer - auto junkChars = static_cast(_stream->available()); - if (junkChars) { - MS_DBG(F("Dumping"), junkChars, - F("characters from MaxBotix stream buffer")); - for (uint8_t i = 0; i < junkChars; i++) { -#ifdef MS_MAXBOTIXSONAR_DEBUG - MS_SERIAL_OUTPUT.print(_stream->read()); -#else - _stream->read(); -#endif - } -#ifdef MS_MAXBOTIXSONAR_DEBUG - PRINTOUT(" "); -#endif - } + dumpBuffer(); return true; } // The function to put the sensor to sleep // Different from the standard in that empties and flushes the stream. -bool MaxBotixSonar::sleep(void) { +bool MaxBotixSonar::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); return Sensor::sleep(); -}; +} + + +bool MaxBotixSonar::startSingleMeasurement() { + // Sensor::startSingleMeasurement() checks that if it's awake/active and + // sets the timestamp and status bits. If it returns false, there's no + // reason to go on. + if (!Sensor::startSingleMeasurement()) return false; + dumpBuffer(); // dump anything stuck in the stream buffer + + // If the sonar is running on a trigger, activate it + if (_triggerPin >= 0) { + MS_DBG(F(" Triggering Sonar with"), _triggerPin); + digitalWrite(_triggerPin, HIGH); + delayMicroseconds(30); // Trigger must be held high for >20 µs + digitalWrite(_triggerPin, LOW); + + // Update the time that a measurement was requested + // For MaxBotix, we're actively triggering so mark time as now + _millisMeasurementRequested = millis(); + } + + return true; +} + + +bool MaxBotixSonar::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } -bool MaxBotixSonar::addSingleMeasurementResult(void) { // Initialize values bool success = false; - int16_t result = -9999; + int16_t result = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Ask for a result and let the stream timeout be our "wait" for the + // measurement + result = static_cast(_stream->parseInt()); + _stream->read(); // Throw away the carriage return + MS_DBG(F(" Sonar Range:"), result); + + // Check if result is valid + // If it cannot obtain a result, the sonar sends a value just above its max + // range. If the result becomes garbled or the sonar is disconnected, + // parseInt returns 0. These sensors cannot read 0, so we know 0 is bad. + if (result <= 0 || result >= _maxRange) { + MS_DBG(F(" Bad or Suspicious Result, Retry #"), _currentRetries + 1); + result = MS_INVALID_VALUE; + } else { + MS_DBG(F(" Good result found")); + // convert result from cm to mm if convertCm is set to true + if (_convertCm) { result *= 10; } + success = true; + verifyAndAddMeasurementResult(HRXL_VAR_NUM, result); + } - // Clear anything out of the stream buffer - auto junkChars = static_cast(_stream->available()); - if (junkChars) { + // dump anything left in the stream buffer + dumpBuffer(); + + // Return success value when finished + return finalizeMeasurementAttempt(success); +} + + +void MaxBotixSonar::dumpBuffer() { + auto junkChars = _stream->available(); + if (junkChars > 0) { MS_DBG(F("Dumping"), junkChars, F("characters from MaxBotix stream buffer:")); - for (uint8_t i = 0; i < junkChars; i++) { + while (_stream->available()) { #ifdef MS_MAXBOTIXSONAR_DEBUG MS_SERIAL_OUTPUT.print(_stream->read()); #else @@ -143,61 +184,4 @@ bool MaxBotixSonar::addSingleMeasurementResult(void) { PRINTOUT(" "); #endif } - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - uint8_t rangeAttempts = 0; - while (success == false && rangeAttempts < 25) { - // If the sonar is running on a trigger, activating the trigger - // should in theory happen within the startSingleMeasurement - // function. Because we're really taking up to 25 measurements - // for each "single measurement" until a valid value is returned - // and the measurement time is <166ms, we'll actually activate - // the trigger here. - if (_triggerPin >= 0) { - MS_DBG(F(" Triggering Sonar with"), _triggerPin); - digitalWrite(_triggerPin, HIGH); - delayMicroseconds(30); // Trigger must be held high for >20 µs - digitalWrite(_triggerPin, LOW); - } - - // Immediately ask for a result and let the stream timeout be our - // "wait" for the measurement. - result = static_cast(_stream->parseInt()); - _stream->read(); // To throw away the carriage return - MS_DBG(F(" Sonar Range:"), result); - rangeAttempts++; - - // If it cannot obtain a result, the sonar is supposed to send a - // value just above its max range. If the result becomes garbled or - // the sonar is disconnected, the parseInt function returns 0. - // Luckily, these sensors are not capable of reading 0, so we also - // know the 0 value is bad. - if (result <= 0 || result >= _maxRange) { - MS_DBG(F(" Bad or Suspicious Result, Retry Attempt #"), - rangeAttempts); - result = -9999; - } else { - MS_DBG(F(" Good result found")); - // convert result from cm to mm if convertCm is set to true - if (_convertCm == true) { result *= 10; } - success = true; - } - } - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); - } - - verifyAndAddMeasurementResult(HRXL_VAR_NUM, result); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // Return values shows if we got a not-obviously-bad reading - return success; } diff --git a/src/sensors/MaxBotixSonar.h b/src/sensors/MaxBotixSonar.h index f9c66d394..1bae9d487 100644 --- a/src/sensors/MaxBotixSonar.h +++ b/src/sensors/MaxBotixSonar.h @@ -85,6 +85,11 @@ * - [MaxTemp Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Maxbotix-HR-MaxTemp-Datasheet.pdf) * - [Wiring Guide](https://github.com/EnviroDIY/ModularSensors/wiki/Sensor-Datasheets/Maxbotix-MaxSonar-MB7954-Datasheet-ConnectWire.pdf) * + * @section sensor_maxbotix_flags Build flags + * - ```-D MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES=25``` + * - Changes the default number of measurement retries when a measurement + * fails. The default value is 25. This sensor fails frequently. + * * @section sensor_maxbotix_ctor Sensor Constructor * {{ @ref MaxBotixSonar::MaxBotixSonar }} * @@ -123,6 +128,34 @@ /** @ingroup sensor_maxbotix */ /**@{*/ +/** + * @anchor sensor_maxbotix_config + * @name Sensor Configuration + * Build-time configuration for the MaxBotix HRXL MaxSonar + */ +/**@{*/ +#if !defined(MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES) || defined(DOXYGEN) +/** + * @brief Default number of measurement retries for MaxBotix sensors + * + * The default number of times to retry a measurement when it fails. The + * MaxBotix sensors can sometimes return invalid readings (0 or values above + * the maximum range), especially in challenging environmental conditions. + * Higher values provide better reliability but may increase total measurement + * time on sensor failures. This can be set at runtime for individual sensors + * and the default can be overridden at compile time with `-D + * MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES=value` + */ +#define MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES 25 +#endif + +// Static assert to validate measurement retries is reasonable +static_assert(MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES >= 0 && + MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES <= 50, + "MAXBOTIX_DEFAULT_MEASUREMENT_RETRIES must be between 0 and 50 " + "(reasonable measurement retry range)"); +/**@}*/ + /** * @anchor sensor_maxbotix_var_counts * @name Sensor Variable Counts @@ -147,7 +180,9 @@ /// up (0ms stabilization). #define HRXL_STABILIZATION_TIME_MS 0 /// @brief Sensor::_measurementTime_ms; the HRXL takes 166ms to complete a -/// measurement. +/// measurement. It outputs results at least every 166ms. +/// @note Because the default number of measurement retries is 25, the actual +/// time to get a measurement result may be much longer than 166ms. #define HRXL_MEASUREMENT_TIME_MS 250 /**@}*/ @@ -155,13 +190,17 @@ * @anchor sensor_maxbotix_range * @name Range * The range variable from a Maxbotix HRXL ultrasonic range finder - * - Range depends on the exact model + * - Range depends on the exact model, up to 10m for the longest range model. * - Accuracy is ±1% * * {{ @ref MaxBotixSonar_Range::MaxBotixSonar_Range }} */ /**@{*/ -/// @brief Decimals places in string representation; range should have 0 - +/// @brief Minimum range in millimeters. +#define HRXL_MIN_MM 0 +/// @brief Maximum range in millimeters. +#define HRXL_MAX_MM 10000 +/// @brief Decimal places in string representation; range should have 0 - /// resolution is 1mm (except for models which have range 10mm). #define HRXL_RESOLUTION 0 /// @brief Sensor variable number; range is stored in sensorValues[0]. @@ -220,12 +259,9 @@ class MaxBotixSonar : public Sensor { /** * @brief Destroy the MaxBotix Sonar object */ - ~MaxBotixSonar(); + ~MaxBotixSonar() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -237,7 +273,7 @@ class MaxBotixSonar : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; /** * @brief Wake the sensor up, if necessary. Do whatever it takes to get a * sensor in the proper state to begin a measurement. @@ -252,14 +288,17 @@ class MaxBotixSonar : public Sensor { * * @return True if the wake function completed successfully. */ - bool wake(void) override; - // override to empty and flush the stream - bool sleep(void) override; - + bool wake() override; /** - * @copydoc Sensor::addSingleMeasurementResult() + * @brief Puts the MaxBotixSonar sensor to sleep after emptying and flushing + * the stream. + * + * @return True if the sleep function completed successfully. */ - bool addSingleMeasurementResult(void) override; + bool sleep() override; + + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; private: int16_t _maxRange; ///< The maximum range of the Maxbotix sonar @@ -274,6 +313,16 @@ class MaxBotixSonar : public Sensor { * Maxbotix sensor. */ Stream* _stream; + + /** + * @brief Helper function to dump any available characters from the stream + * buffer + * + * Reads and discards all available characters from the stream to clear the + * buffer. Optionally prints debugging output showing the discarded + * characters. + */ + void dumpBuffer(); }; @@ -301,23 +350,14 @@ class MaxBotixSonar_Range : public Variable { explicit MaxBotixSonar_Range(MaxBotixSonar* parentSense, const char* uuid = "", const char* varCode = HRXL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HRXL_VAR_NUM, (uint8_t)HRXL_RESOLUTION, - HRXL_VAR_NAME, HRXL_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MaxBotixSonar_Range object. - * - * @note This must be tied with a parent MaxBotixSonar before it can be - * used. - */ - MaxBotixSonar_Range() - : Variable((uint8_t)HRXL_VAR_NUM, (uint8_t)HRXL_RESOLUTION, - HRXL_VAR_NAME, HRXL_UNIT_NAME, HRXL_DEFAULT_CODE) {} + : Variable(parentSense, HRXL_VAR_NUM, HRXL_RESOLUTION, HRXL_VAR_NAME, + HRXL_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the MaxBotixSonar_Range object - no action needed. */ - ~MaxBotixSonar_Range() {} + ~MaxBotixSonar_Range() override = default; }; /**@}*/ #endif // SRC_SENSORS_MAXBOTIXSONAR_H_ -// cSpell:ignore max_botix +// cSpell:words max_botix diff --git a/src/sensors/MaximDS18.cpp b/src/sensors/MaximDS18.cpp index 4d37d51b7..ca710e2a1 100644 --- a/src/sensors/MaximDS18.cpp +++ b/src/sensors/MaximDS18.cpp @@ -34,17 +34,18 @@ MaximDS18::MaximDS18(int8_t powerPin, int8_t dataPin, _addressKnown(false), _internalOneWire(dataPin), _internalDallasTemp(&_internalOneWire) {} -// Destructor -MaximDS18::~MaximDS18() {} // Turns the address into a printable string String MaximDS18::makeAddressString(DeviceAddress owAddr) { - String addrStr = F("Pin"); + String addrStr; + addrStr.reserve( + 50); // Reserve for "Pin" + pin# + "{0x##,0x##...}" (8 addresses) + addrStr = F("Pin"); addrStr += (_dataPin); addrStr += '{'; for (uint8_t i = 0; i < 8; i++) { - addrStr += "0x"; + addrStr += F("0x"); if (owAddr[i] < 0x10) addrStr += "0"; addrStr += String(owAddr[i], HEX); if (i < 7) addrStr += ","; @@ -55,15 +56,15 @@ String MaximDS18::makeAddressString(DeviceAddress owAddr) { } -// This gets the place the sensor is installed ON THE MAYFLY (ie, pin number) -String MaximDS18::getSensorLocation(void) { +// This gets the place the sensor is installed ON THE MAYFLY (i.e., pin number) +String MaximDS18::getSensorLocation() { return makeAddressString(_OneWireAddress); } // The function to set up connection to a sensor. // By default, sets pin modes and returns ready -bool MaximDS18::setup(void) { +bool MaximDS18::setup() { uint8_t ntries = 0; bool retVal = @@ -130,6 +131,8 @@ bool MaximDS18::setup(void) { // Tell the sensor that we do NOT want to wait for conversions to finish // That is, we're in ASYNC mode and will get values when we're ready + // NOTE: This is a setting of the library, not the sensor itself; it is not + // changed by the sensor powering down. _internalDallasTemp.setWaitForConversion(false); // Turn the power back off it it had been turned on @@ -149,7 +152,7 @@ bool MaximDS18::setup(void) { // Sending the device a request to start temp conversion. // Because we put ourselves in ASYNC mode in setup, we don't have to wait for // finish -bool MaximDS18::startSingleMeasurement(void) { +bool MaximDS18::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -178,38 +181,29 @@ bool MaximDS18::startSingleMeasurement(void) { } -bool MaximDS18::addSingleMeasurementResult(void) { - bool success = false; +bool MaximDS18::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Initialize float variable - float result = -9999; + bool success = false; + float result = MS_INVALID_VALUE; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - result = _internalDallasTemp.getTempC(_OneWireAddress); - MS_DBG(F(" Received"), result, F("°C")); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + result = _internalDallasTemp.getTempC(_OneWireAddress); + MS_DBG(F(" Received"), result, F("°C")); - // If a DS18 cannot get a good measurement, it returns 85 - // If the sensor is not properly connected, it returns -127 - if (result == 85 || result == -127) { - result = -9999; - } else { - success = true; - } - MS_DBG(F(" Temperature:"), result, F("°C")); + // If a DS18 cannot get a good measurement, it returns 85 + // If the sensor is not properly connected, it returns -127 + if (result != DS18_BAD_MEASUREMENT_VALUE && + result != DS18_DISCONNECTED_VALUE) { + // Put value into the array + verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result); + success = true; } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F(" Invalid measurement received from"), + getSensorNameAndLocation()); } - // Put value into the array - verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/MaximDS18.h b/src/sensors/MaximDS18.h index 5d5e17b14..0fc4beeed 100644 --- a/src/sensors/MaximDS18.h +++ b/src/sensors/MaximDS18.h @@ -153,7 +153,11 @@ * {{ @ref MaximDS18_Temp::MaximDS18_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 4. +/// @brief Minimum temperature in degrees Celsius. +#define DS18_TEMP_MIN_C -55.0 +/// @brief Maximum temperature in degrees Celsius. +#define DS18_TEMP_MAX_C 125.0 +/// @brief Decimal places in string representation; temperature should have 4. #define DS18_TEMP_RESOLUTION 4 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. #define DS18_TEMP_VAR_NUM 0 @@ -167,6 +171,10 @@ #define DS18_TEMP_UNIT_NAME "degreeCelsius" /// @brief Default variable short code; "DS18Temp" #define DS18_TEMP_DEFAULT_CODE "DS18Temp" +/// @brief Value returned when DS18 cannot get a good measurement +#define DS18_BAD_MEASUREMENT_VALUE 85.0f +/// @brief Value returned when DS18 sensor is not properly connected +#define DS18_DISCONNECTED_VALUE -127.0f /**@}*/ /* clang-format off */ @@ -225,7 +233,7 @@ class MaximDS18 : public Sensor { /** * @brief Destroy the Maxim DS18 object */ - ~MaximDS18(); + ~MaximDS18() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -238,28 +246,12 @@ class MaximDS18 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @brief Tell the sensor to start a single measurement, if needed. - * - * This also sets the #_millisMeasurementRequested timestamp. - * - * @note This function does NOT include any waiting for the sensor to be - * warmed up or stable! - * - * @return True if the start measurement function completed - * successfully. successfully. - */ - bool startSingleMeasurement(void) override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; private: /** @@ -314,22 +306,12 @@ class MaximDS18_Temp : public Variable { */ explicit MaximDS18_Temp(MaximDS18* parentSense, const char* uuid = "", const char* varCode = DS18_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DS18_TEMP_VAR_NUM, - (uint8_t)DS18_TEMP_RESOLUTION, DS18_TEMP_VAR_NAME, - DS18_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MaximDS18_Temp object. - * - * @note This must be tied with a parent MaximDS18 before it can be used. - */ - MaximDS18_Temp() - : Variable((uint8_t)DS18_TEMP_VAR_NUM, (uint8_t)DS18_TEMP_RESOLUTION, - DS18_TEMP_VAR_NAME, DS18_TEMP_UNIT_NAME, - DS18_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, DS18_TEMP_VAR_NUM, DS18_TEMP_RESOLUTION, + DS18_TEMP_VAR_NAME, DS18_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the MaximDS18_Temp object - no action needed. */ - ~MaximDS18_Temp() {} + ~MaximDS18_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_MAXIMDS18_H_ diff --git a/src/sensors/MaximDS3231.cpp b/src/sensors/MaximDS3231.cpp index 32debf783..498ad7c37 100644 --- a/src/sensors/MaximDS3231.cpp +++ b/src/sensors/MaximDS3231.cpp @@ -16,15 +16,14 @@ MaximDS3231::MaximDS3231(uint8_t measurementsToAverage) : Sensor("MaximDS3231", DS3231_NUM_VARIABLES, DS3231_WARM_UP_TIME_MS, DS3231_STABILIZATION_TIME_MS, DS3231_MEASUREMENT_TIME_MS, -1, -1, measurementsToAverage, DS3231_INC_CALC_VARIABLES) {} -// Destructor -MaximDS3231::~MaximDS3231() {} -String MaximDS3231::getSensorLocation(void) { + +String MaximDS3231::getSensorLocation() { return F("I2C_0x68"); } -bool MaximDS3231::setup(void) { +bool MaximDS3231::setup() { rtc.begin(); // NOTE: This also turns off interrupts on the RTC! return Sensor::setup(); // this will set pin modes and the setup status bit // The clock should be continuously powered, so we never need to worry about @@ -33,7 +32,7 @@ bool MaximDS3231::setup(void) { // Sending the device a request to start temp conversion. -bool MaximDS3231::startSingleMeasurement(void) { +bool MaximDS3231::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -46,23 +45,31 @@ bool MaximDS3231::startSingleMeasurement(void) { MS_DBG(F("Forcing new temperature reading by DS3231")); rtc.convertTemperature(false); + // NOTE: There's no way of knowing if there's a failure here so we always + // return true. + // There's no condition where we would need to bump the number of completed + // measurement attempts here. return true; } -bool MaximDS3231::addSingleMeasurementResult(void) { - // get the temperature value +bool MaximDS3231::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // NOTE: If this the start measurement or get temperature fails we have much + // bigger problems than just a lost temperature value. That is, if I2C + // communication with the clock fails, the system is too broken to even ask + // for this temperature. MS_DBG(getSensorNameAndLocation(), F("is reporting:")); float tempVal = rtc.getTemperature(); MS_DBG(F(" Temp:"), tempVal, F("°C")); verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + // Check if temperature is valid before marking success + bool success = !isnan(tempVal); - // Return true when finished - return true; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/MaximDS3231.h b/src/sensors/MaximDS3231.h index 9a65ffee4..6fce591ef 100644 --- a/src/sensors/MaximDS3231.h +++ b/src/sensors/MaximDS3231.h @@ -127,7 +127,11 @@ * {{ @ref MaximDS3231_Temp::MaximDS3231_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature in degrees Celsius. +#define DS3231_TEMP_MIN_C -55.0 +/// @brief Maximum temperature in degrees Celsius. +#define DS3231_TEMP_MAX_C 125.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is -0.25°C (10 bit). #define DS3231_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. @@ -166,12 +170,9 @@ class MaximDS3231 : public Sensor { /** * @brief Destroy the Maxim DS3231 object */ - ~MaximDS3231(); + ~MaximDS3231() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -184,24 +185,10 @@ class MaximDS3231 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; - /** - * @brief Tell the sensor to start a single measurement, if needed. - * - * This also sets the #_millisMeasurementRequested timestamp. - * - * @note This function does NOT include any waiting for the sensor to be - * warmed up or stable! - * - * @return True if the start measurement function completed - * successfully. successfully. - */ - bool startSingleMeasurement(void) override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool startSingleMeasurement() override; + bool addSingleMeasurementResult() override; }; @@ -227,24 +214,15 @@ class MaximDS3231_Temp : public Variable { */ explicit MaximDS3231_Temp(MaximDS3231* parentSense, const char* uuid = "", const char* varCode = DS3231_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DS3231_TEMP_VAR_NUM, - (uint8_t)DS3231_TEMP_RESOLUTION, DS3231_TEMP_VAR_NAME, - DS3231_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MaximDS3231_Temp object. - * - * @note This must be tied with a parent MaximDS3231 before it can be used. - */ - MaximDS3231_Temp() - : Variable((uint8_t)DS3231_TEMP_VAR_NUM, - (uint8_t)DS3231_TEMP_RESOLUTION, DS3231_TEMP_VAR_NAME, - DS3231_TEMP_UNIT_NAME, DS3231_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, DS3231_TEMP_VAR_NUM, DS3231_TEMP_RESOLUTION, + DS3231_TEMP_VAR_NAME, DS3231_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the MaximDS3231_Temp object - no action needed. */ - ~MaximDS3231_Temp() {} + ~MaximDS3231_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_MAXIMDS3231_H_ -// cSpell:ignore temperatureDatalogger +// cSpell:words temperatureDatalogger diff --git a/src/sensors/MeaSpecMS5803.cpp b/src/sensors/MeaSpecMS5803.cpp index eb7898093..682d10920 100644 --- a/src/sensors/MeaSpecMS5803.cpp +++ b/src/sensors/MeaSpecMS5803.cpp @@ -21,18 +21,18 @@ MeaSpecMS5803::MeaSpecMS5803(int8_t powerPin, uint8_t i2cAddressHex, -1, measurementsToAverage, MS5803_INC_CALC_VARIABLES), _i2cAddressHex(i2cAddressHex), _maxPressure(maxPressure) {} -// Destructor -MeaSpecMS5803::~MeaSpecMS5803() {} -String MeaSpecMS5803::getSensorLocation(void) { - String address = F("I2C_0x"); +String MeaSpecMS5803::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool MeaSpecMS5803::setup(void) { +bool MeaSpecMS5803::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -54,49 +54,38 @@ bool MeaSpecMS5803::setup(void) { } -bool MeaSpecMS5803::addSingleMeasurementResult(void) { - bool success = false; - - // Initialize float variables - float temp = -9999; - float press = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Read values - // NOTE: These functions actually include the request to begin - // a measurement and the wait for said measurement to finish. - // It's pretty fast (max of 11 ms) so we'll just wait. - temp = MS5803_internal.getTemperature(CELSIUS, ADC_512); - press = MS5803_internal.getPressure(ADC_4096); - - if (isnan(temp)) temp = -9999; - if (isnan(press)) press = -9999; - if (temp < -50 || temp > 95) { // Range is -40°C to +85°C - temp = -9999; - press = -9999; - } - if (press == 0) { // Returns 0 when disconnected, which is highly - // unlikely to be a real value. - temp = -9999; - press = -9999; - } - - MS_DBG(F(" Temperature:"), temp); - MS_DBG(F(" Pressure:"), press); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); +bool MeaSpecMS5803::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + bool success = false; + float temp = MS_INVALID_VALUE; + float press = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + // Read values + // NOTE: These functions actually include the request to begin + // a measurement and the wait for said measurement to finish. + // It's pretty fast (max of 11 ms) so we'll just wait. + temp = MS5803_internal.getTemperature(CELSIUS, ADC_512); + press = MS5803_internal.getPressure(ADC_4096); + + MS_DBG(F(" Temperature:"), temp); + MS_DBG(F(" Pressure:"), press); + + if (!isnan(temp) && !isnan(press) && temp >= MS5803_TEMP_MIN_C && + temp <= MS5803_TEMP_MAX_C && press > 0.0 && + press <= (_maxPressure * 1000.0)) { + // Temperature Range is -40°C to +85°C + // Pressure returns 0 when disconnected, which is highly unlikely to be + // a real value. + // Pressure range depends on the model; validation uses _maxPressure + // (bar) * 1000 (mbar/bar) + verifyAndAddMeasurementResult(MS5803_TEMP_VAR_NUM, temp); + verifyAndAddMeasurementResult(MS5803_PRESSURE_VAR_NUM, press); + success = true; } - verifyAndAddMeasurementResult(MS5803_TEMP_VAR_NUM, temp); - verifyAndAddMeasurementResult(MS5803_PRESSURE_VAR_NUM, press); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/MeaSpecMS5803.h b/src/sensors/MeaSpecMS5803.h index 605ca46d0..cbda9b41d 100644 --- a/src/sensors/MeaSpecMS5803.h +++ b/src/sensors/MeaSpecMS5803.h @@ -144,7 +144,11 @@ * {{ @ref MeaSpecMS5803_Temp::MeaSpecMS5803_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature in degrees Celsius. +#define MS5803_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define MS5803_TEMP_MAX_C 85.0 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is <0.01°C. #define MS5803_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. @@ -165,7 +169,7 @@ * @anchor sensor_ms5803_pressure * @name Pressure * The pressure variable from a Measurement Specialties MS5803 - * - Range is 0 to 14 bar + * - Range is 0 to 14 bar (0 to 14000 mbar) * - Accuracy between 0 and +40°C is: * - 14ba: ±20mbar * - 2ba: ±1.5mbar @@ -183,7 +187,11 @@ * {{ @ref MeaSpecMS5803_Pressure::MeaSpecMS5803_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; pressure should have 3. +/// @brief Minimum pressure in millibar. +#define MS5803_PRESSURE_MIN_MBAR 0.0 +/// @brief Maximum pressure in millibar. +#define MS5803_PRESSURE_MAX_MBAR 14000.0 +/// @brief Decimal places in string representation; pressure should have 3. #define MS5803_PRESSURE_RESOLUTION 3 /// @brief Sensor variable number; pressure is stored in sensorValues[1]. #define MS5803_PRESSURE_VAR_NUM 1 @@ -235,7 +243,7 @@ class MeaSpecMS5803 : public Sensor { /** * @brief Destroy the MeaSpecMS5803 object */ - ~MeaSpecMS5803(); + ~MeaSpecMS5803() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -249,16 +257,11 @@ class MeaSpecMS5803 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** @@ -300,23 +303,13 @@ class MeaSpecMS5803_Temp : public Variable { explicit MeaSpecMS5803_Temp(MeaSpecMS5803* parentSense, const char* uuid = "", const char* varCode = MS5803_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)MS5803_TEMP_VAR_NUM, - (uint8_t)MS5803_TEMP_RESOLUTION, MS5803_TEMP_VAR_NAME, - MS5803_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeaSpecMS5803_Temp object. - * - * @note This must be tied with a parent MeaSpecMS5803 before it can be - * used. - */ - MeaSpecMS5803_Temp() - : Variable((uint8_t)MS5803_TEMP_VAR_NUM, - (uint8_t)MS5803_TEMP_RESOLUTION, MS5803_TEMP_VAR_NAME, - MS5803_TEMP_UNIT_NAME, MS5803_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, MS5803_TEMP_VAR_NUM, MS5803_TEMP_RESOLUTION, + MS5803_TEMP_VAR_NAME, MS5803_TEMP_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the MeaSpecMS5803_Temp object - no action needed. */ - ~MeaSpecMS5803_Temp() {} + ~MeaSpecMS5803_Temp() override = default; }; @@ -339,31 +332,18 @@ class MeaSpecMS5803_Pressure : public Variable { * @param uuid A universally unique identifier (UUID or GUID) for the * variable; optional with the default value of an empty string. * @param varCode A short code to help identify the variable in files; - * optional with a default value of th a default value of - * MeaSpecMS5803Pressure + * optional with a default value of "MeaSpecMS5803Pressure" */ explicit MeaSpecMS5803_Pressure( MeaSpecMS5803* parentSense, const char* uuid = "", const char* varCode = MS5803_PRESSURE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)MS5803_PRESSURE_VAR_NUM, - (uint8_t)MS5803_PRESSURE_RESOLUTION, - MS5803_PRESSURE_VAR_NAME, MS5803_PRESSURE_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new MeaSpecMS5803_Pressure object. - * - * @note This must be tied with a parent MeaSpecMS5803 before it can be - * used. - */ - MeaSpecMS5803_Pressure() - : Variable((uint8_t)MS5803_PRESSURE_VAR_NUM, - (uint8_t)MS5803_PRESSURE_RESOLUTION, - MS5803_PRESSURE_VAR_NAME, MS5803_PRESSURE_UNIT_NAME, - MS5803_PRESSURE_DEFAULT_CODE) {} + : Variable(parentSense, MS5803_PRESSURE_VAR_NUM, + MS5803_PRESSURE_RESOLUTION, MS5803_PRESSURE_VAR_NAME, + MS5803_PRESSURE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the MeaSpecMS5803_Pressure object - no action needed. */ - ~MeaSpecMS5803_Pressure() {} + ~MeaSpecMS5803_Pressure() override = default; }; /**@}*/ #endif // SRC_SENSORS_MEASPECMS5803_H_ diff --git a/src/sensors/MeterHydros21.h b/src/sensors/MeterHydros21.h index 449b525d5..f7e2a2d4f 100644 --- a/src/sensors/MeterHydros21.h +++ b/src/sensors/MeterHydros21.h @@ -127,8 +127,12 @@ * {{ @ref MeterHydros21_Cond::MeterHydros21_Cond }} */ /**@{*/ +/// @brief Minimum conductivity; 0 µS/cm +#define HYDROS21_COND_MIN_USCM 0 +/// @brief Maximum conductivity; 120000 µS/cm (120 mS/cm) +#define HYDROS21_COND_MAX_USCM 120000 /** - * @brief Decimals places in string representation; conductivity should have 1. + * @brief Decimal places in string representation; conductivity should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.001 mS/cm = 1 µS/cm @@ -158,8 +162,12 @@ * {{ @ref MeterHydros21_Temp::MeterHydros21_Temp }} */ /**@{*/ +/// @brief Minimum temperature; -11°C +#define HYDROS21_TEMP_MIN_C -11 +/// @brief Maximum temperature; 49°C +#define HYDROS21_TEMP_MAX_C 49 /** - * @brief Decimals places in string representation; temperature should have 2. + * @brief Decimal places in string representation; temperature should have 2. * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C @@ -189,8 +197,12 @@ * {{ @ref MeterHydros21_Depth::MeterHydros21_Depth }} */ /**@{*/ +/// @brief Minimum depth; 0 m (0 millimeter) +#define HYDROS21_DEPTH_MIN_MM 0 +/// @brief Maximum depth; 10 m (10000 millimeters) +#define HYDROS21_DEPTH_MAX_MM 10000 /** - * @brief Decimals places in string representation; depth should have 1. + * @brief Decimal places in string representation; depth should have 1. * * 0 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 2 mm @@ -273,11 +285,10 @@ class MeterHydros21 : public SDI12Sensors { "MeterHydros21", HYDROS21_NUM_VARIABLES, HYDROS21_WARM_UP_TIME_MS, HYDROS21_STABILIZATION_TIME_MS, HYDROS21_MEASUREMENT_TIME_MS, HYDROS21_EXTRA_WAKE_TIME_MS, HYDROS21_INC_CALC_VARIABLES) {} - /** * @brief Destroy the Meter Hydros 21 object */ - ~MeterHydros21() {} + ~MeterHydros21() override = default; }; @@ -304,23 +315,13 @@ class MeterHydros21_Cond : public Variable { explicit MeterHydros21_Cond( MeterHydros21* parentSense, const char* uuid = "", const char* varCode = HYDROS21_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HYDROS21_COND_VAR_NUM, - (uint8_t)HYDROS21_COND_RESOLUTION, HYDROS21_COND_VAR_NAME, - HYDROS21_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterHydros21_Cond object. - * - * @note This must be tied with a parent MeterHydros21 before it can be - * used. - */ - MeterHydros21_Cond() - : Variable((uint8_t)HYDROS21_COND_VAR_NUM, - (uint8_t)HYDROS21_COND_RESOLUTION, HYDROS21_COND_VAR_NAME, - HYDROS21_COND_UNIT_NAME, HYDROS21_COND_DEFAULT_CODE) {} + : Variable(parentSense, HYDROS21_COND_VAR_NUM, HYDROS21_COND_RESOLUTION, + HYDROS21_COND_VAR_NAME, HYDROS21_COND_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the MeterHydros21_Cond object - no action needed. */ - ~MeterHydros21_Cond() {} + ~MeterHydros21_Cond() override = default; }; @@ -347,23 +348,13 @@ class MeterHydros21_Temp : public Variable { explicit MeterHydros21_Temp( MeterHydros21* parentSense, const char* uuid = "", const char* varCode = HYDROS21_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HYDROS21_TEMP_VAR_NUM, - (uint8_t)HYDROS21_TEMP_RESOLUTION, HYDROS21_TEMP_VAR_NAME, - HYDROS21_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterHydros21_Temp object. - * - * @note This must be tied with a parent MeterHydros21 before it can be - * used. - */ - MeterHydros21_Temp() - : Variable((uint8_t)HYDROS21_TEMP_VAR_NUM, - (uint8_t)HYDROS21_TEMP_RESOLUTION, HYDROS21_TEMP_VAR_NAME, - HYDROS21_TEMP_UNIT_NAME, HYDROS21_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, HYDROS21_TEMP_VAR_NUM, HYDROS21_TEMP_RESOLUTION, + HYDROS21_TEMP_VAR_NAME, HYDROS21_TEMP_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the MeterHydros21_Temp object - no action needed. */ - ~MeterHydros21_Temp() {} + ~MeterHydros21_Temp() override = default; }; @@ -390,23 +381,13 @@ class MeterHydros21_Depth : public Variable { explicit MeterHydros21_Depth( MeterHydros21* parentSense, const char* uuid = "", const char* varCode = HYDROS21_DEPTH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)HYDROS21_DEPTH_VAR_NUM, - (uint8_t)HYDROS21_DEPTH_RESOLUTION, HYDROS21_DEPTH_VAR_NAME, + : Variable(parentSense, HYDROS21_DEPTH_VAR_NUM, + HYDROS21_DEPTH_RESOLUTION, HYDROS21_DEPTH_VAR_NAME, HYDROS21_DEPTH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterHydros21_Depth object. - * - * @note This must be tied with a parent MeterHydros21 before it can be - * used. - */ - MeterHydros21_Depth() - : Variable((uint8_t)HYDROS21_DEPTH_VAR_NUM, - (uint8_t)HYDROS21_DEPTH_RESOLUTION, HYDROS21_DEPTH_VAR_NAME, - HYDROS21_DEPTH_UNIT_NAME, HYDROS21_DEPTH_DEFAULT_CODE) {} /** * @brief Destroy the MeterHydros21_Depth object - no action needed. */ - ~MeterHydros21_Depth() {} + ~MeterHydros21_Depth() override = default; }; /**@}*/ #endif // SRC_SENSORS_METERHYDROS21_H_ diff --git a/src/sensors/MeterTeros11.cpp b/src/sensors/MeterTeros11.cpp index 4da65d1d4..c8690e3b2 100644 --- a/src/sensors/MeterTeros11.cpp +++ b/src/sensors/MeterTeros11.cpp @@ -22,8 +22,8 @@ bool MeterTeros11::getResults(bool verify_crc) { float raw = sensorValues[TEROS11_COUNT_VAR_NUM]; // Set up the float variables for calculated variable - float ea = -9999; - float VWC = -9999; + float ea = MS_INVALID_VALUE; + float VWC = MS_INVALID_VALUE; // Calculate the dielectric EA from the raw count value. // Equation 8 from the Teros 11 user manual: @@ -32,25 +32,24 @@ bool MeterTeros11::getResults(bool verify_crc) { MS_DBG( F("WARNING: raw results out of range (0-5000)! Cannot calculate " "Ea or VWC")); - raw = -9999; + raw = MS_INVALID_VALUE; } - if (raw != -9999) { - ea = ((2.887e-9 * (raw * raw * raw)) - (2.08e-5 * (raw * raw)) + - (5.276e-2 * raw) - 43.39) * - ((2.887e-9 * (raw * raw * raw)) - (2.08e-5 * (raw * raw)) + - (5.276e-2 * raw) - 43.39); + if (raw != MS_INVALID_VALUE) { + double cubic = (2.887e-9 * raw * raw * raw) - (2.08e-5 * raw * raw) + + (5.276e-2 * raw) - 43.39; + ea = cubic * cubic; MS_DBG(F("Calculated Ea:"), ea); } // Calculate the VWC from EA using the Topp equation // range check - if (ea < 0 || ea > 350) { + if (ea != MS_INVALID_VALUE && (ea < 0 || ea > 350)) { MS_DBG(F("WARNING: Ea results out of range (0-350)! Cannot calculate " "VWC")); - ea = -9999; + ea = MS_INVALID_VALUE; } // calculate - if (ea != -9999) { + if (ea != MS_INVALID_VALUE) { VWC = (4.3e-6 * (ea * ea * ea)) - (5.5e-4 * (ea * ea)) + (2.92e-2 * ea) - 5.3e-2; VWC *= 100; // Convert to actual percent @@ -67,9 +66,10 @@ bool MeterTeros11::getResults(bool verify_crc) { // VWC = 3.879e-4*raw-0.6956; // equation for mineral soils - // range check on temp; range is - 40°C to + 50°C + // range check on temp; sensor range is -40°C to +50°C; add an extra 10°C + // buffer on either side if (temp < -50 || temp > 60) { - temp = -9999; + temp = MS_INVALID_VALUE; MS_DBG(F("WARNING: temperature results out of range (-50-60)!")); } @@ -79,5 +79,5 @@ bool MeterTeros11::getResults(bool verify_crc) { verifyAndAddMeasurementResult(TEROS11_EA_VAR_NUM, ea); verifyAndAddMeasurementResult(TEROS11_VWC_VAR_NUM, VWC); - return success && temp != -9999; + return success && temp != MS_INVALID_VALUE; } diff --git a/src/sensors/MeterTeros11.h b/src/sensors/MeterTeros11.h index bfdad388e..7b3152d7c 100644 --- a/src/sensors/MeterTeros11.h +++ b/src/sensors/MeterTeros11.h @@ -139,11 +139,14 @@ * The raw VWC counts variable from a Meter Teros 11 * - Range and accuracy of the raw count values are not specified * + * @todo Find and define minimum and maximum raw count measurement range from a + * Meter Teros 11. + * * {{ @ref MeterTeros11_Count::MeterTeros11_Count }} */ /**@{*/ /** - * @brief Decimals places in string representation; EA should have 1. + * @brief Decimal places in string representation; EA should have 1. */ #define TEROS11_COUNT_RESOLUTION 1 /// @brief Sensor variable number; EA is stored in sensorValues[0]. @@ -172,8 +175,12 @@ * {{ @ref MeterTeros11_Temp::MeterTeros11_Temp }} */ /**@{*/ +/// @brief Minimum temperature; -40°C +#define TEROS11_TEMP_MIN_C -40 +/// @brief Maximum temperature; 60°C +#define TEROS11_TEMP_MAX_C 60 /** - * @brief Decimals places in string representation; temperature should have 2. + * @brief Decimal places in string representation; temperature should have 2. * * 1 is reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.1°C @@ -205,8 +212,12 @@ * {{ @ref MeterTeros11_Ea::MeterTeros11_Ea }} */ /**@{*/ +/// @brief Minimum EA; 1 (air) +#define TEROS11_EA_MIN 1 +/// @brief Maximum EA; 80 (water) +#define TEROS11_EA_MAX 80 /** - * @brief Decimals places in string representation; EA should have 5. + * @brief Decimal places in string representation; EA should have 5. * * 4 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - resolution is 0.00001 @@ -242,8 +253,12 @@ * {{ @ref MeterTeros11_VWC::MeterTeros11_VWC }} */ /**@{*/ +/// @brief Minimum VWC; 0% (0.0 m3/m3) +#define TEROS11_VWC_MIN_PCT 0 +/// @brief Maximum VWC; 100% (1.0 m3/m3, soilless media calibration) +#define TEROS11_VWC_MAX_PCT 100 /** - * @brief Decimals places in string representation; VWC should have 3. + * @brief Decimal places in string representation; VWC should have 3. * * 2 are reported, adding extra digit to resolution to allow the proper number * of significant figures for averaging - Resolution is 0.001 m3/m3 (0.1% VWC) @@ -330,7 +345,7 @@ class MeterTeros11 : public SDI12Sensors { /** * @brief Destroy the Meter Teros 11 object */ - ~MeterTeros11() {} + ~MeterTeros11() override = default; /** * @copydoc SDI12Sensors::getResults(bool verify_crc) @@ -364,22 +379,13 @@ class MeterTeros11_Count : public Variable { explicit MeterTeros11_Count( MeterTeros11* parentSense, const char* uuid = "", const char* varCode = TEROS11_COUNT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TEROS11_COUNT_VAR_NUM, - (uint8_t)TEROS11_COUNT_RESOLUTION, TEROS11_COUNT_VAR_NAME, - TEROS11_COUNT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterTeros11_Count object. - * - * @note This must be tied with a parent MeterTeros11 before it can be used. - */ - MeterTeros11_Count() - : Variable((uint8_t)TEROS11_COUNT_VAR_NUM, - (uint8_t)TEROS11_COUNT_RESOLUTION, TEROS11_COUNT_VAR_NAME, - TEROS11_COUNT_UNIT_NAME, TEROS11_COUNT_DEFAULT_CODE) {} + : Variable(parentSense, TEROS11_COUNT_VAR_NUM, TEROS11_COUNT_RESOLUTION, + TEROS11_COUNT_VAR_NAME, TEROS11_COUNT_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the MeterTeros11_Count object - no action needed. */ - ~MeterTeros11_Count() {} + ~MeterTeros11_Count() override = default; }; @@ -406,22 +412,13 @@ class MeterTeros11_Temp : public Variable { */ explicit MeterTeros11_Temp(MeterTeros11* parentSense, const char* uuid = "", const char* varCode = TEROS11_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TEROS11_TEMP_VAR_NUM, - (uint8_t)TEROS11_TEMP_RESOLUTION, TEROS11_TEMP_VAR_NAME, - TEROS11_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterTeros11_Temp object. - * - * @note This must be tied with a parent MeterTeros11 before it can be used. - */ - MeterTeros11_Temp() - : Variable((uint8_t)TEROS11_TEMP_VAR_NUM, - (uint8_t)TEROS11_TEMP_RESOLUTION, TEROS11_TEMP_VAR_NAME, - TEROS11_TEMP_UNIT_NAME, TEROS11_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, TEROS11_TEMP_VAR_NUM, TEROS11_TEMP_RESOLUTION, + TEROS11_TEMP_VAR_NAME, TEROS11_TEMP_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the MeterTeros11_Temp object - no action needed. */ - ~MeterTeros11_Temp() {} + ~MeterTeros11_Temp() override = default; }; @@ -449,22 +446,12 @@ class MeterTeros11_Ea : public Variable { */ explicit MeterTeros11_Ea(MeterTeros11* parentSense, const char* uuid = "", const char* varCode = TEROS11_EA_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TEROS11_EA_VAR_NUM, - (uint8_t)TEROS11_EA_RESOLUTION, TEROS11_EA_VAR_NAME, - TEROS11_EA_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterTeros11_Ea object. - * - * @note This must be tied with a parent MeterTeros11 before it can be used. - */ - MeterTeros11_Ea() - : Variable((uint8_t)TEROS11_EA_VAR_NUM, (uint8_t)TEROS11_EA_RESOLUTION, - TEROS11_EA_VAR_NAME, TEROS11_EA_UNIT_NAME, - TEROS11_EA_DEFAULT_CODE) {} + : Variable(parentSense, TEROS11_EA_VAR_NUM, TEROS11_EA_RESOLUTION, + TEROS11_EA_VAR_NAME, TEROS11_EA_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the MeterTeros11_Ea object - no action needed. */ - ~MeterTeros11_Ea() {} + ~MeterTeros11_Ea() override = default; }; @@ -491,22 +478,13 @@ class MeterTeros11_VWC : public Variable { */ explicit MeterTeros11_VWC(MeterTeros11* parentSense, const char* uuid = "", const char* varCode = TEROS11_VWC_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TEROS11_VWC_VAR_NUM, - (uint8_t)TEROS11_VWC_RESOLUTION, TEROS11_VWC_VAR_NAME, - TEROS11_VWC_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new MeterTeros11_VWC object. - * - * @note This must be tied with a parent MeterTeros11 before it can be used. - */ - MeterTeros11_VWC() - : Variable((uint8_t)TEROS11_VWC_VAR_NUM, - (uint8_t)TEROS11_VWC_RESOLUTION, TEROS11_VWC_VAR_NAME, - TEROS11_VWC_UNIT_NAME, TEROS11_VWC_DEFAULT_CODE) {} + : Variable(parentSense, TEROS11_VWC_VAR_NUM, TEROS11_VWC_RESOLUTION, + TEROS11_VWC_VAR_NAME, TEROS11_VWC_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the MeterTeros11_VWC object - no action needed. */ - ~MeterTeros11_VWC() {} + ~MeterTeros11_VWC() override = default; }; /**@}*/ #endif // SRC_SENSORS_METERTEROS11_H_ diff --git a/src/sensors/PaleoTerraRedox.cpp b/src/sensors/PaleoTerraRedox.cpp index 9414ef9a2..1abbea088 100644 --- a/src/sensors/PaleoTerraRedox.cpp +++ b/src/sensors/PaleoTerraRedox.cpp @@ -45,7 +45,7 @@ PaleoTerraRedox::PaleoTerraRedox(TwoWire* theI2C, int8_t powerPin, PTR_STABILIZATION_TIME_MS, PTR_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage), _i2cAddressHex(i2cAddressHex), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} PaleoTerraRedox::PaleoTerraRedox(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) : Sensor("PaleoTerraRedox", PTR_NUM_VARIABLES, PTR_WARM_UP_TIME_MS, @@ -68,20 +68,25 @@ PaleoTerraRedox::~PaleoTerraRedox() {} #endif -String PaleoTerraRedox::getSensorLocation(void) { +String PaleoTerraRedox::getSensorLocation() { #if defined(MS_PALEOTERRA_SOFTWAREWIRE) - String address = F("SoftwareWire"); + String address; + address.reserve( + 25); // Reserve for "SoftwareWire" + pin# + "_0x" + hex chars + address = F("SoftwareWire"); if (_dataPin >= 0) address += _dataPin; address += F("_0x"); #else - String address = F("I2C_0x"); + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); #endif address += String(_i2cAddressHex, HEX); return address; } -bool PaleoTerraRedox::setup(void) { +bool PaleoTerraRedox::setup() { _i2c->begin(); // Start the wire library (sensor power not required) // Eliminate any potential extra waits in the wire library // These waits would be caused by a readBytes or parseX being called @@ -96,65 +101,78 @@ bool PaleoTerraRedox::setup(void) { } -bool PaleoTerraRedox::addSingleMeasurementResult(void) { - bool success = false; - - byte config = 0; // Data transfer values - - float res = 0; // Calculated voltage in uV - - byte i2c_status = -1; - if (_millisMeasurementRequested > 0) { - _i2c->beginTransmission(_i2cAddressHex); - _i2c->write(0b10001100); // initiate conversion, One-Shot mode, 18 - // bits, PGA x1 - i2c_status = _i2c->endTransmission(); - - delay(300); - - _i2c->requestFrom(int(_i2cAddressHex), - 4); // Get 4 bytes from device - byte res1 = _i2c->read(); - byte res2 = _i2c->read(); - byte res3 = _i2c->read(); - config = _i2c->read(); - - res = 0; - int sign = bitRead(res1, 1); // one but least significant bit - if (sign == 1) { - res1 = ~res1; - res2 = ~res2; - res3 = ~res3; // two's complements - res = bitRead(res1, 0) * - -1024; // 256 * 256 * 15.625 uV per LSB = 16 - res -= res2 * 4; - res -= res3 * 0.015625; - res -= 0.015625; - } else { - res = bitRead(res1, 0) * - 1024; // 256 * 256 * 15.625 uV per LSB = 16 - res += res2 * 4; - res += res3 * 0.015625; - } - } else { - MS_DBG(F("Sensor is not currently measuring!\n")); +bool PaleoTerraRedox::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + bool success = false; + byte config = 0; // Returned config + int32_t adcValue = 0; // Raw ADC value from the sensor + float res = MS_INVALID_VALUE; // Calculated voltage in uV + + byte i2c_status; + + _i2c->beginTransmission(_i2cAddressHex); + // When writing config: + // - Bit 7: RDY (**1** = initiate conversion, 0 = no effect) + // - Bit 6-5: No effect on the MCP3421 (set both to 0) + // - Bit 4: O/C (1 = Continuous mode, **0** = One-Shot mode) + // - Bits 3-2: S1, S0 (00 = 12-bit/240 SPS, 01 = 14-bit/60 SPS, 10 = + // 16-bit/15 SPS, **11** = 18-bit/3.75 SPS) + // - Bits 1-0: G1, G0 (**00** = PGA x1, 01 = PGA x2, 10 = PGA x4, 11 = PGA + // x8) + _i2c->write( + 0b10001100); // initiate conversion, One-Shot mode, 18 bits, PGA x1 + i2c_status = _i2c->endTransmission(); + // fail if transmission error + if (i2c_status != 0) { return finalizeMeasurementAttempt(false); } + + // wait for the conversion to complete + delay(PTR_CONVERSION_WAIT_TIME_MS); + + // Get 4 bytes from device + if (_i2c->requestFrom(int(_i2cAddressHex), 4) != 4) { + return finalizeMeasurementAttempt(false); + } + // per the datasheet, in 18 bit mode: + // byte 1: [MMMMMM D17 D16 (1st data byte] + // byte 2: [ D15 ~ D8 (2nd data byte)] + // byte 3: [ D7 ~ D0 (3rd data byte)] + // byte 4: [config byte: RDY, C1, C0, O/C, S1, S0, G1, G0] + // NOTE: D17 is MSB (= sign bit), M is repeated MSB of the data byte. + byte res1 = _i2c->read(); // byte 1: [MMMMMM D17 D16] + byte res2 = _i2c->read(); // byte 2: [ D15 ~ D8 ] + byte res3 = _i2c->read(); // byte 3: [ D7 ~ D0 ] + config = _i2c->read(); // byte 4: [config byte] + + // Assemble the 18-bit raw sample from the three bytes + // Only use the lower 2 bits of res1 (D17 D16), ignore sign-extension bits + // Cast to uint32_t to ensure sufficient bit width for left shift operations + adcValue = static_cast( + (((uint32_t)(res1 & 0x03)) + << 16) // extract D17 D16 and shift to position + | (((uint32_t)res2) << 8) // shift res2 up to middle byte + | ((uint32_t)res3)); // res3 is already in the right place as the LSB + + // Check if this is a negative value (sign bit 17 is set) + if (res1 & 0x02) { // Test bit 17 + // Sign-extend the 18-bit value to get correct negative magnitude + adcValue |= 0xFFFC0000; // Sign extend from bit 17 (set all bits 18-31) + } + + // convert the raw ADC value to voltage in millivolts (mV) + res = adcValue * PTR_MV_PER_LSB; // 0.015625 mV per LSB + + MS_DBG(F("Raw ADC reading in bits:"), adcValue); + MS_DBG(F("Config byte:"), config); + MS_DBG(F("Calculated voltage in mV:"), res); + + success = (!isnan(res)) && !(adcValue == 0 && config == 0); + if (success) { + // Store the results in the sensorValues array + verifyAndAddMeasurementResult(PTR_VOLTAGE_VAR_NUM, res); } - // ADD FAILURE CONDITIONS!! - if (isnan(res)) - res = -9999; // list a failure if the sensor returns nan (not sure - // how this would happen, keep to be safe) - else if (res == 0 && i2c_status == 0 && config == 0) - res = -9999; // List a failure when the sensor is not connected - // Store the results in the sensorValues array - verifyAndAddMeasurementResult(PTR_VOLTAGE_VAR_NUM, res); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bit for a measurement having been requested (bit 5) - clearStatusBit(MEASUREMENT_ATTEMPTED); - // Set the status bit for measurement completion (bit 6) - setStatusBit(MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/PaleoTerraRedox.h b/src/sensors/PaleoTerraRedox.h index 5448b306e..098c7089d 100644 --- a/src/sensors/PaleoTerraRedox.h +++ b/src/sensors/PaleoTerraRedox.h @@ -15,6 +15,9 @@ * This depends on Testato's * [SoftwareWire](https://github.com/Testato/SoftwareWire) library if software * I2C is needed. + * + * @note These sensors are no longer being produced, but this code is being + * maintained for users who have them in their monitoring kits. */ /* clang-format off */ /** @@ -40,6 +43,12 @@ * @section sensor_pt_redox_flags Build flags * - `-D MS_PALEOTERRA_SOFTWAREWIRE` * - switches from using hardware I2C to software I2C + * - `PTR_MV_PER_LSB` + * - ADC LSB to microvolts conversion factor (15.625 μV per LSB) + * - Used to convert raw ADC values to voltage measurements + * - `MCP3421_ADR` + * - Default I2C address of the PaleoTerra redox sensor (0x68) + * - Can be overridden in constructor if sensor address is different * @warning Either all or none of your attached redox probes may use software I2C. * Using some with software I2C and others with hardware I2C is not supported. * @@ -89,6 +98,23 @@ /** @ingroup sensor_pt_redox */ /**@{*/ +/** + * @anchor sensor_pt_redox_config + * @name Configuration Defines + * Defines to set the address of the PaleoTerra redox sensor. + */ +/**@{*/ +/// @brief The default I2C address of the PaleoTerra redox sensor +#ifndef MCP3421_ADR +#define MCP3421_ADR 0x68 +#endif +/// @brief ADC LSB to millivolts conversion factor (15.625 µV per LSB = 0.015625 +/// mV per LSB) +#ifndef PTR_MV_PER_LSB +#define PTR_MV_PER_LSB 0.015625f +#endif +/**@}*/ + /** * @anchor sensor_pt_redox_var_counts * @name Sensor Variable Counts @@ -102,16 +128,6 @@ #define PTR_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_pt_redox_config - * @name Configuration Defines - * Defines to set the address of the PaleoTerra redox sensor. - */ -/**@{*/ -/// @brief The default I2C address of the PaleoTerra redox sensor -#define MCP3421_ADR 0x68 -/**@}*/ - /** * @anchor sensor_pt_redox_timing * @name Sensor Timing @@ -127,6 +143,8 @@ /// @brief Sensor::_measurementTime_ms; the PaleoTerra redox sensor takes 67ms /// to complete a measurement. #define PTR_MEASUREMENT_TIME_MS 67 +/// @brief The time to wait after starting a conversion before data is ready. +#define PTR_CONVERSION_WAIT_TIME_MS 300 /**@}*/ /** @@ -135,10 +153,13 @@ * The voltage variable from a PaleoTerra redox probe * - Accuracy is ±5mV * + * @todo Find and define minimum and maximum voltage measurement range from a + * PaleoTerra redox probe. + * * {{ @ref PaleoTerraRedox_Voltage::PaleoTerraRedox_Voltage }} */ /**@{*/ -/** @brief Decimals places in string representation; voltage should have 2. +/** @brief Decimal places in string representation; voltage should have 2. * * Resolution is 1mV and 1 extra digit is added to increase the number of * significant figures to allow for averaging of multiple measurements. @@ -213,7 +234,7 @@ class PaleoTerraRedox : public Sensor { uint8_t i2cAddressHex = MCP3421_ADR, uint8_t measurementsToAverage = 1); #endif -#if !defined(MS_PALEOTERRA_SOFTWAREWIRE) | defined DOXYGEN +#if !defined(MS_PALEOTERRA_SOFTWAREWIRE) || defined(DOXYGEN) /** * @brief Construct a new PaleoTerra Redox object using a secondary * *hardware* I2C instance. @@ -252,7 +273,7 @@ class PaleoTerraRedox : public Sensor { * @brief Destroy the PaleoTerra Redox object. Also destroy the software * I2C instance if one was created. */ - ~PaleoTerraRedox(); + ~PaleoTerraRedox() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -263,16 +284,11 @@ class PaleoTerraRedox : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** @@ -321,23 +337,13 @@ class PaleoTerraRedox_Voltage : public Variable { explicit PaleoTerraRedox_Voltage( Sensor* parentSense, const char* uuid = "", const char* varCode = PTR_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)PTR_VOLTAGE_VAR_NUM, - (uint8_t)PTR_VOLTAGE_RESOLUTION, PTR_VOLTAGE_VAR_NAME, - PTR_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new PaleoTerraRedox_Voltage object. - * - * @note This must be tied with a parent PaleoTerraRedox before it can be - * used. - */ - PaleoTerraRedox_Voltage() - : Variable((uint8_t)PTR_VOLTAGE_VAR_NUM, - (uint8_t)PTR_VOLTAGE_RESOLUTION, PTR_VOLTAGE_VAR_NAME, - PTR_VOLTAGE_UNIT_NAME, PTR_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, PTR_VOLTAGE_VAR_NUM, PTR_VOLTAGE_RESOLUTION, + PTR_VOLTAGE_VAR_NAME, PTR_VOLTAGE_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the PaleoTerraRedox_Voltage object - no action needed. */ - ~PaleoTerraRedox_Voltage() {} + ~PaleoTerraRedox_Voltage() override = default; }; /** diff --git a/src/sensors/ProcessorAnalog.cpp b/src/sensors/ProcessorAnalog.cpp new file mode 100644 index 000000000..c90c805cf --- /dev/null +++ b/src/sensors/ProcessorAnalog.cpp @@ -0,0 +1,183 @@ +/** + * @file ProcessorAnalog.cpp + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * + * @brief Implements the ProcessorAnalog class. + */ + + +#include "ProcessorAnalog.h" + + +// ============================================================================ +// ProcessorAnalogReader Constructor +// ============================================================================ + +ProcessorAnalogReader::ProcessorAnalogReader(float voltageMultiplier, + float operatingVoltage) + : AnalogVoltageReader(voltageMultiplier, operatingVoltage) {} + + +// ============================================================================ +// ProcessorAnalogReader Functions +// ============================================================================ + +bool ProcessorAnalogReader::begin() { + // For processor analog systems, no special initialization is required + // beyond what is done in the constructor + return true; +} + +bool ProcessorAnalogReader::readVoltageSingleEnded(int8_t analogChannel, + float& resultValue) { + // Compile-time validation of ADC configuration + static_assert(PROCESSOR_ADC_MAX > 0, + "PROCESSOR_ADC_MAX must be greater than 0. Check " + "MS_PROCESSOR_ADC_RESOLUTION configuration."); + + // Validate parameters + if (analogChannel < 0 || analogChannel > MS_PROCESSOR_ANALOG_MAX_CHANNEL || + _supplyVoltage <= 0 || _voltageMultiplier <= 0) { + MS_DBG(F("Invalid configuration: either the analog channel, the supply " + "voltage, or the voltage multiplier is not set correctly!")); + resultValue = MS_INVALID_VALUE; + return false; + } + + // Get the analog voltage + MS_DBG(F("Getting analog voltage from pin"), analogChannel); + pinMode(analogChannel, INPUT); + analogRead(analogChannel); // priming reading + // The return value from analogRead() is IN BITS NOT IN VOLTS!! + analogRead(analogChannel); // another priming reading + int rawAdc = analogRead(analogChannel); + MS_DBG(F("Raw analog pin reading in bits:"), rawAdc); + + // convert bits to volts + // Use (PROCESSOR_ADC_MAX + 1) as divisor for correct 2^n scaling + resultValue = + (_supplyVoltage / (static_cast(PROCESSOR_ADC_MAX) + 1.0f)) * + _voltageMultiplier * static_cast(rawAdc); + MS_DBG(F("Voltage:"), resultValue); + + // NOTE: We don't actually have any criteria for if the reading was any + // good or not, so we mark it as successful no matter what. + return true; +} + +String ProcessorAnalogReader::getAnalogLocation( + int8_t analogChannel, int8_t /*analogReferenceChannel*/) { + String sensorLocation; + sensorLocation += F("ProcessorAnalog_Pin"); + sensorLocation += String(analogChannel); + return sensorLocation; +} + +bool ProcessorAnalogReader::readVoltageDifferential( + int8_t /*analogChannel*/, int8_t /*analogReferenceChannel*/, + float& resultValue) { + // ProcessorAnalog does not support differential measurements + MS_DBG(F("ProcessorAnalog does not support differential measurements")); + resultValue = MS_INVALID_VALUE; + return false; +} + +float ProcessorAnalogReader::calculateAnalogResolutionVolts() { + // Use the configured processor ADC resolution + uint8_t resolutionBits = MS_PROCESSOR_ADC_RESOLUTION; + + // For processor ADCs, the full scale range is the supply voltage + // (single-ended measurements from 0V to supply voltage) + float fullScaleRangeVolts = _supplyVoltage; + + if (resolutionBits == 0 || resolutionBits >= 32 || + fullScaleRangeVolts <= 0.0f) { + MS_DBG(F("Invalid ADC configuration - bits: "), resolutionBits, + F(", supply voltage: "), fullScaleRangeVolts, F("V")); + return MS_INVALID_VALUE; + } + + // Calculate the total number of ADC codes + uint32_t totalCodes = 1UL << resolutionBits; // 2^resolutionBits + + // Voltage resolution is the full scale range divided by total codes + float resolutionVolts = fullScaleRangeVolts / + static_cast(totalCodes); + + MS_DBG(F("Processor ADC resolution calculation:")); + MS_DBG(F(" ADC resolution: "), resolutionBits, F(" bits")); + MS_DBG(F(" Supply voltage: "), fullScaleRangeVolts, F("V")); + MS_DBG(F(" Total codes: "), totalCodes); + MS_DBG(F(" Voltage resolution: "), resolutionVolts, F("V/LSB")); + + return resolutionVolts; +} + + +// ============================================================================ +// ProcessorAnalog Functions +// ============================================================================ + +// The constructor - need the power pin, the data pin, and the number of +// measurements to average, with an optional external analog reader +ProcessorAnalog::ProcessorAnalog(int8_t powerPin, int8_t dataPin, + uint8_t measurementsToAverage, + ProcessorAnalogReader* analogVoltageReader) + : Sensor("ProcessorAnalog", PROCESSOR_ANALOG_NUM_VARIABLES, + PROCESSOR_ANALOG_WARM_UP_TIME_MS, + PROCESSOR_ANALOG_STABILIZATION_TIME_MS, + PROCESSOR_ANALOG_MEASUREMENT_TIME_MS, powerPin, dataPin, + measurementsToAverage, PROCESSOR_ANALOG_INC_CALC_VARIABLES), + // If no analog base provided, create one with default settings + _analogVoltageReader(analogVoltageReader == nullptr + ? new ProcessorAnalogReader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} + +// Destructor +ProcessorAnalog::~ProcessorAnalog() { + // Clean up the analog base object if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + _analogVoltageReader = nullptr; + } +} + +String ProcessorAnalog::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + return _analogVoltageReader->getAnalogLocation(_dataPin, -1); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } +} + +bool ProcessorAnalog::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + float resultValue = MS_INVALID_VALUE; + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + resultValue); + + if (success) { + verifyAndAddMeasurementResult(PROCESSOR_ANALOG_VAR_NUM, resultValue); + } else { + MS_DBG(F(" Failed to get valid voltage from analog reader")); + } + + // Return success value when finished + return finalizeMeasurementAttempt(success); +} diff --git a/src/sensors/ProcessorAnalog.h b/src/sensors/ProcessorAnalog.h new file mode 100644 index 000000000..2423e5434 --- /dev/null +++ b/src/sensors/ProcessorAnalog.h @@ -0,0 +1,338 @@ +/** + * @file ProcessorAnalog.h + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * + * @brief This file contains the ProcessorAnalog sensor subclass and the + * ProcessorAnalog_Voltage variable subclass. + * + * These are used for any voltage measurable on an analog processor pin. There + * is a multiplier allowed for a voltage divider between the raw voltage and the + * processor pin. + */ +/** + * @defgroup sensor_processor_analog Processor Analog Voltage Sensor + * Classes for simple external analog voltage measurements using the built-in + * processor ADC. + * + * @ingroup analog_group + * + * @tableofcontents + * @m_footernavigation + * + * @section sensor_processor_analog_intro Introduction + * + * The Processor Analog module is used for any case where the voltage itself is + * the desired value (as for an external battery) and the voltage will be + * measured on a processor pin using the built-in ADC. It can also be used in + * combination with a calculated variable to support any other analog sensor not + * explicitly supported by ModularSensors. To increase the range available for + * voltage measurements, this module supports the use of a voltage divider. + * + * @section sensor_processor_analog_flags Build flags + * - `-D MS_PROCESSOR_ADC_RESOLUTION=##` + * - used to set the resolution of the processor ADC + * - @see #MS_PROCESSOR_ADC_RESOLUTION + * - `-D MS_PROCESSOR_ADC_REFERENCE_MODE=xxx` + * - used to set the processor ADC value reference mode + * - @see #MS_PROCESSOR_ADC_REFERENCE_MODE + * + * @section sensor_processor_analog_ctor Sensor Constructor + * {{ @ref ProcessorAnalog::ProcessorAnalog }} + * + * ___ + * @section sensor_processor_analog_examples Example Code + * The processor analog voltage sensor is used in the + * @menulink{processor_analog} example. + * + * @menusnip{processor_analog} + */ + +// Header Guards +#ifndef SRC_SENSORS_PROCESSOR_ANALOG_H_ +#define SRC_SENSORS_PROCESSOR_ANALOG_H_ + +// Include the library config before anything else +#include "ModSensorConfig.h" + +// Include the debugging config +#include "ModSensorDebugConfig.h" + +// Define the print label[s] for the debugger +#ifdef MS_PROCESSOR_ANALOG_DEBUG +#define MS_DEBUGGING_STD "ProcessorAnalog" +#endif + +// Include the debugger +#include "ModSensorDebugger.h" +// Undefine the debugger label[s] +#undef MS_DEBUGGING_STD + +// Include other in-library and external dependencies +#include "VariableBase.h" +#include "SensorBase.h" +#include "AnalogVoltageReader.h" + +/** @ingroup sensor_processor_analog */ +/**@{*/ + +/** + * @anchor sensor_processor_analog_var_counts + * @name Sensor Variable Counts + * The number of variables that can be returned from an analog reading on a + * processor pin. + */ +/**@{*/ +/// @brief Sensor::_numReturnedValues; an analog reading on a processor pin is 1 +/// value. +#define PROCESSOR_ANALOG_NUM_VARIABLES 1 +/// @brief Sensor::_incCalcValues; we don't calculate any additional values. +#define PROCESSOR_ANALOG_INC_CALC_VARIABLES 0 +/**@}*/ + +/** + * @anchor sensor_processor_analog_timing + * @name Sensor Timing + * The sensor timing for the processor ADC + */ +/**@{*/ +/// @brief Sensor::_warmUpTime_ms; the processor ADC does not need to warm up, +#define PROCESSOR_ANALOG_WARM_UP_TIME_MS 0 +/** + * @brief Sensor::_stabilizationTime_ms; the processor ADC is stable 0ms after + * warm-up - we assume a voltage is instantly ready. + * + * It's not really *quite* instantly, but it is very fast and the time to + * measure is included in the read function. + * On ATmega based boards (UNO, Nano, Mini, Mega), it takes about 100 + * microseconds (0.0001 s) to read an analog input, so the maximum reading rate + * is about 10,000 times a second. + */ +#define PROCESSOR_ANALOG_STABILIZATION_TIME_MS 0 +/** + * @brief Sensor::_measurementTime_ms; the processor ADC measurement time is + * variable, but we assume it is effectively instant. + */ +#define PROCESSOR_ANALOG_MEASUREMENT_TIME_MS 0 +/**@}*/ + +/** + * @anchor sensor_processor_analog_volt + * @name Voltage + * The voltage variable for the processor analog voltage sensor + * - Range is dependent on the supply voltage and any voltage divider. We do + * not set a specific maximum for this variable. + * + * {{ @ref ProcessorAnalog_Voltage::ProcessorAnalog_Voltage }} + */ +/**@{*/ +/// @brief Minimum analog voltage in volts. +#define PROCESSOR_ANALOG_MIN_V 0.0 +/// Variable number; voltage is stored in sensorValues[0]. +#define PROCESSOR_ANALOG_VAR_NUM 0 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "voltage" +#define PROCESSOR_ANALOG_VAR_NAME "voltage" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "volt" +#define PROCESSOR_ANALOG_UNIT_NAME "volt" +/// @brief Default variable short code; "analogVoltage" +#define PROCESSOR_ANALOG_DEFAULT_CODE "analogVoltage" + +#if MS_PROCESSOR_ADC_RESOLUTION == 12 +/// @brief Decimal places in string representation; a 3.3V processor at 12-bit +/// resolution should have 4 [3.3V / 4096 ~= 0.0008] . +#define PROCESSOR_ANALOG_RESOLUTION 4 +#elif MS_PROCESSOR_ADC_RESOLUTION == 10 +/// @brief Decimal places in string representation; a 3.3V processor at 10-bit +/// resolution should have 3 [3.3V / 1024 ~= 0.0032] . +#define PROCESSOR_ANALOG_RESOLUTION 3 +#else +// Default to 10-bit behavior if not specified +#define PROCESSOR_ANALOG_RESOLUTION 3 +#endif +/**@}*/ + +/** + * @brief Processor analog base class that inherits from AnalogVoltageReader + * + * This class provides processor-specific analog functionality on top of the + * generic AnalogVoltageReader class. It handles processor ADC configuration and + * processor-based analog voltage reads for requested channels. + */ +class ProcessorAnalogReader : public AnalogVoltageReader { + public: + /** + * @brief Construct a new ProcessorAnalogReader object + * + * @param voltageMultiplier Any multiplier needed to convert raw battery + * readings from `analogRead()` into true voltage values based on any + * voltage divider resistors + * @param operatingVoltage The processor's operating voltage; most + * likely 3.3 or 5. + */ + ProcessorAnalogReader(float voltageMultiplier = 1.0f, + float operatingVoltage = OPERATING_VOLTAGE); + + /** + * @brief Destroy the ProcessorAnalogReader object + */ + ~ProcessorAnalogReader() override = default; + + /** + * @brief Initialize the processor analog system + * + * For processor analog systems, no special initialization is required + * beyond what is done in the constructor, so this function does nothing. + * + * @return Always true indicating successful initialization + */ + bool begin() override; + + /** + * @brief Read a single-ended voltage measurement from the processor ADC + * + * @param analogChannel The processor ADC pin used to read the target + * voltage. Negative or invalid channel numbers are not clamped and will + * cause the reading to fail and emit a warning. + * @param resultValue Reference to store the resulting voltage measurement + * @return True if the voltage reading was successful + */ + bool readVoltageSingleEnded(int8_t analogChannel, + float& resultValue) override; + + /** + * @brief Read a differential voltage measurement from the processor ADC + * + * ProcessorAnalog does not support differential measurements, so this + * always returns false. + * + * @param analogChannel The primary analog channel (ignored) + * @param analogReferenceChannel The secondary (reference) analog channel + * (ignored) + * @param resultValue Reference to store the resulting voltage measurement. + * This will be set to #MS_INVALID_VALUE to indicate an invalid reading. + * @return Always false (differential not supported) + */ + bool readVoltageDifferential(int8_t analogChannel, + int8_t analogReferenceChannel, + float& resultValue) override; + + String getAnalogLocation(int8_t analogChannel, + int8_t analogReferenceChannel) override; + + /** + * @brief Calculate the analog resolution in volts for the processor ADC + * + * For processor ADCs, this calculates the voltage resolution based on the + * configured ADC resolution and supply voltage. The calculation uses: + * - ADC resolution in bits: MS_PROCESSOR_ADC_RESOLUTION + * - Full scale range: processor supply voltage (single-ended, 0V to Vcc) + * + * @return The analog resolution in volts per LSB + */ + float calculateAnalogResolutionVolts() override; +}; + +/* clang-format off */ +/** + * @brief The Sensor sub-class for the + * [external voltage as measured by the processor ADC](@ref sensor_processor_analog). + * + * @ingroup sensor_processor_analog + */ +/* clang-format on */ +class ProcessorAnalog : public Sensor { + public: + /** + * @brief Construct a new Processor Analog object - need the power pin and + * the data pin on the processor. + * + * @param powerPin The pin on the mcu controlling power to the sensor + * Use -1 if it is continuously powered. + * @param dataPin The processor ADC pin used to read the target voltage. Not + * all processor pins can be used as analog pins. Those usable as analog + * pins generally are numbered with an "A" in front of the number + * - i.e., A1. + * @param measurementsToAverage The number of measurements to take and + * average before giving a "final" result from the sensor; optional with a + * default value of 1. + * @param analogVoltageReader Pointer to ProcessorAnalogReader object for + * analog functionality. If nullptr (default), creates a new + * ProcessorAnalogReader with default settings. + */ + ProcessorAnalog(int8_t powerPin, int8_t dataPin, + uint8_t measurementsToAverage = 1, + ProcessorAnalogReader* analogVoltageReader = nullptr); + /** + * @brief Destroy the Processor Analog object + */ + ~ProcessorAnalog() override; + + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + ProcessorAnalog(const ProcessorAnalog&) = delete; + ProcessorAnalog& operator=(const ProcessorAnalog&) = delete; + + // Delete move constructor and move assignment operator + ProcessorAnalog(ProcessorAnalog&&) = delete; + ProcessorAnalog& operator=(ProcessorAnalog&&) = delete; + + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; + + private: + /** + * @brief Pointer to the ProcessorAnalogReader object providing analog + * functionality + */ + ProcessorAnalogReader* _analogVoltageReader = nullptr; + + /** + * @brief Whether this object owns the _analogVoltageReader pointer and + * should delete it + */ + bool _ownsAnalogVoltageReader = false; +}; + + +// The single available variable is voltage +/* clang-format off */ +/** + * @brief The Variable sub-class used for the + * [voltage output](@ref sensor_processor_analog_volt) from a + * [analog processor pin](@ref sensor_processor_analog). + * + * @ingroup sensor_processor_analog + */ +/* clang-format on */ +class ProcessorAnalog_Voltage : public Variable { + public: + /** + * @brief Construct a new ProcessorAnalog_Voltage object. + * + * @param parentSense The parent ProcessorAnalog providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "analogVoltage". + */ + explicit ProcessorAnalog_Voltage( + ProcessorAnalog* parentSense, const char* uuid = "", + const char* varCode = PROCESSOR_ANALOG_DEFAULT_CODE) + : Variable(parentSense, PROCESSOR_ANALOG_VAR_NUM, + PROCESSOR_ANALOG_RESOLUTION, PROCESSOR_ANALOG_VAR_NAME, + PROCESSOR_ANALOG_UNIT_NAME, varCode, uuid) {} + /** + * @brief Destroy the ProcessorAnalog_Voltage object - no action needed. + */ + ~ProcessorAnalog_Voltage() override = default; +}; + +/**@}*/ +#endif // SRC_SENSORS_PROCESSOR_ANALOG_H_ diff --git a/src/sensors/ProcessorStats.cpp b/src/sensors/ProcessorStats.cpp index 21ec4d541..0221b0a3b 100644 --- a/src/sensors/ProcessorStats.cpp +++ b/src/sensors/ProcessorStats.cpp @@ -19,65 +19,48 @@ ProcessorStats::ProcessorStats(const char* version, -1, measurementsToAverage, PROCESSOR_INC_CALC_VARIABLES), _version(version), _boardName(LOGGER_BOARD) { - // change the battery related settings for known boards + _operatingVoltage = OPERATING_VOLTAGE; + _batteryPin = BATTERY_PIN; + _batteryMultiplier = BATTERY_MULTIPLIER; + + // change the battery related settings for known boards where the pin or + // multiplier is version dependent #if defined(ARDUINO_AVR_ENVIRODIY_MAYFLY) - _operatingVoltage = 3.3; - _batteryPin = A6; - if (strcmp(_version, "v0.3") == 0 || strcmp(_version, "v0.4") == 0) { + // fix battery multiplier for older Mayfly versions + if (_version != nullptr && + (strcmp_P(_version, PSTR("v0.3")) == 0 || + strcmp_P(_version, PSTR("v0.4")) == 0)) { _batteryMultiplier = 1.47; - } else if (strcmp(_version, "v0.5") == 0 || strcmp(_version, "v0.5b") || - strcmp(_version, "v1.0") || strcmp(_version, "v1.1") == 0) { - _batteryMultiplier = 4.7; - } else { - _batteryPin = -1; - _batteryMultiplier = -1; } -#elif defined(ENVIRODIY_STONEFLY_M4) - if (strcmp(_version, "v0.1") == 0) { - _operatingVoltage = 3.3; - _batteryPin = A9; // aka 75 - _batteryMultiplier = 4.7; +#elif defined(ARDUINO_SODAQ_ONE) || defined(ARDUINO_SODAQ_ONE_BETA) + // only versions v0.1 and v0.2 of the Sodaq One are supported, and they have + // different battery multipliers (_batteryPin uses the default) + if (_version != nullptr) { + if (strcmp_P(_version, PSTR("v0.1")) == 0) { + _batteryMultiplier = 2; + } else if (strcmp_P(_version, PSTR("v0.2")) == 0) { + _batteryMultiplier = 1.47; + } else { + MS_DBG(F("Unsupported Sodaq One version:"), _version, + F("- setting battery multiplier to -1")); + _batteryMultiplier = -1; + } } else { - _batteryPin = -1; + MS_DBG(F("No Sodaq One version specified - setting battery multiplier " + "to -1")); _batteryMultiplier = -1; } -#elif defined(ARDUINO_AVR_FEATHER328P) || defined(ARDUINO_AVR_FEATHER32U4) || \ - defined(ARDUINO_SAMD_FEATHER_M0) || defined(SAMD_FEATHER_M0) || \ - defined(ARDUINO_SAMD_FEATHER_M0_EXPRESS) || \ - defined(SAMD_FEATHER_M0_EXPRESS) || defined(ARDUINO_FEATHER_M4) || \ - defined(ADAFRUIT_FEATHER_M4_EXPRESS) || defined(ARDUINO_FEATHER_M4_CAN) || \ - defined(ADAFRUIT_FEATHER_M4_CAN) || defined(ADAFRUIT_FEATHER_M4_ADALOGGER) - _operatingVoltage = 3.3; - _batteryPin = 9; - _batteryMultiplier = 2; -#elif defined(ARDUINO_AVR_SODAQ_MBILI) - _operatingVoltage = 3.3; - _batteryPin = A6; - _batteryMultiplier = 1.47; -#elif defined(ARDUINO_AVR_SODAQ_NDOGO) - _operatingVoltage = 3.3; - _batteryPin = 10; - _batteryMultiplier = 1.47; -#elif defined(ARDUINO_SODAQ_ONE) || defined(ARDUINO_SODAQ_ONE_BETA) - _operatingVoltage = 3.3; - _batteryPin = 10; - if (strcmp(_version, "v0.1") == 0) { - _batteryMultiplier = 2; - } else if (strcmp(_version, "v0.2") == 0) { - _batteryMultiplier = 1.47; +#elif defined(ARDUINO_SODAQ_AUTONOMO) + if (_version != nullptr) { + if (strcmp_P(_version, PSTR("v0.1")) == 0) { + _batteryPin = 48; + } else { + _batteryPin = 33; + } } else { - _batteryMultiplier = -1 + MS_DBG(F( + "No Sodaq Autonomo version specified - using default battery pin")); } -#elif defined(ARDUINO_SODAQ_AUTONOMO) - _operatingVoltage = 3.3; - _batteryMultiplier = 1.47; - if (strcmp(_version, "v0.1") == 0) - _batteryPin = 48; - else - _batteryPin = 33; -#else - _batteryPin = -1; - _batteryMultiplier = -1; #endif } @@ -96,16 +79,20 @@ ProcessorStats::ProcessorStats(const char* boardName, const char* version, _batteryMultiplier(batteryMultiplier), _operatingVoltage(operatingVoltage) {} -// Destructor -ProcessorStats::~ProcessorStats() {} - -String ProcessorStats::getSensorLocation(void) { - return String(_boardName) + " " + String(_version); +String ProcessorStats::getSensorLocation() { + String result; + result.reserve(50); // Reserve for board name + " " + version + result = String(_boardName); + if (_version != nullptr) { + result += " "; + result += _version; + } + return result; } -float ProcessorStats::getBatteryVoltage(void) { - float sensorValue_battery = -9999; +float ProcessorStats::getBatteryVoltage() { + float sensorValue_battery = MS_INVALID_VALUE; if (_batteryPin >= 0 && _batteryMultiplier > 0) { // Get the battery voltage MS_DBG(F("Getting battery voltage from pin"), _batteryPin); @@ -189,7 +176,7 @@ int16_t FreeRam() { return &stack_dummy - sbrk(0); } -uint8_t ProcessorStats::getLastResetCode(void) { +uint8_t ProcessorStats::getLastResetCode() { return PM->RCAUSE.reg; } String ProcessorStats::getLastResetCause() { @@ -220,7 +207,7 @@ int16_t FreeRam() { return sensorValue_freeRam; } -uint8_t ProcessorStats::getLastResetCode(void) { +uint8_t ProcessorStats::getLastResetCode() { return MCUSR; } String ProcessorStats::getLastResetCause() { @@ -237,21 +224,26 @@ String ProcessorStats::getLastResetCause() { #endif -bool ProcessorStats::addSingleMeasurementResult(void) { +bool ProcessorStats::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // NOTE: There is no way for the measurement start to fail! + float sensorValue_battery = getBatteryVoltage(); verifyAndAddMeasurementResult(PROCESSOR_BATTERY_VAR_NUM, sensorValue_battery); // NOTE: Only running this section if there are no measurements already for // the RAM! - if (numberGoodMeasurementsMade[PROCESSOR_RAM_VAR_NUM] == 0) { + if (validCount[PROCESSOR_RAM_VAR_NUM] == 0) { // Used only for debugging - can be removed MS_DBG(F("Getting Free RAM")); #if !defined(__SAMD51__) float sensorValue_freeRam = FreeRam(); #else - float sensorValue_freeRam = -9999; + float sensorValue_freeRam = MS_INVALID_VALUE; #endif verifyAndAddMeasurementResult(PROCESSOR_RAM_VAR_NUM, @@ -265,7 +257,7 @@ bool ProcessorStats::addSingleMeasurementResult(void) { // average-able measurement, only for new measurements. This is a workaround // in case someone wants to average more than one measurement of the battery // voltage. - if (numberGoodMeasurementsMade[PROCESSOR_SAMPNUM_VAR_NUM] == 0) { + if (validCount[PROCESSOR_SAMPNUM_VAR_NUM] == 0) { // bump up the sample number sampNum += 1; @@ -276,7 +268,7 @@ bool ProcessorStats::addSingleMeasurementResult(void) { // NOTE: Only running this section if there are no measurements already for // the reset cause! - if (numberGoodMeasurementsMade[PROCESSOR_RESET_VAR_NUM] == 0) { + if (validCount[PROCESSOR_RESET_VAR_NUM] == 0) { // Used only for debugging - can be removed MS_DBG(F("Getting last reset cause")); @@ -289,13 +281,8 @@ bool ProcessorStats::addSingleMeasurementResult(void) { MS_DBG(F("Skipping reset cause check on reps")); } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // Return true when finished - return true; + // Return true value when finished + return finalizeMeasurementAttempt(true); } // cSpell:ignore ADALOGGER RSTC RCAUSE BKUPEXIT BODCORE BODVDD BBPS brkval MCUSR diff --git a/src/sensors/ProcessorStats.h b/src/sensors/ProcessorStats.h index 2e4387e96..57f124f0f 100644 --- a/src/sensors/ProcessorStats.h +++ b/src/sensors/ProcessorStats.h @@ -26,8 +26,8 @@ * The processor can return the number of "samples" it has taken, the amount of * RAM it has available and, for some boards, the battery voltage (EnviroDIY * Mayfly, Sodaq Mbili, Ndogo, Autonomo, and One, Adafruit Feathers). The - * version of the board is required as input (ie, for a EnviroDIY Mayfly: "v0.3" - * or "v0.4" or "v0.5"). Use a blank value (ie, "") for un-versioned boards. + * version of the board is required as input (i.e., for a EnviroDIY Mayfly: "v0.3" + * or "v0.4" or "v0.5"). Use a blank value (i.e., "") for un-versioned boards. * Please note that while you cannot opt to average more than one sample, it really * makes no sense to do so for the processor. These values are only intended to be * used as diagnostics. @@ -40,11 +40,11 @@ * - [Atmel ATmega16U4 32U4 Datasheet Summary](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega16U4-32U4-Datasheet-Summary.pdf) * - [Atmel ATmega16U4 32U4 Datasheet](https://github.com/EnviroDIY/ModularSensors/wiki/Processor-Datasheets/Atmel-ATmega16U4-32U4-Datasheet.pdf) * - * @section sensor_processor_sensor_ctor Sensor Constructor + * @section sensor_processor_stats_ctor Sensor Constructor * {{ @ref ProcessorStats::ProcessorStats }} * * ___ - * @section sensor_processor_sensor_examples Example Code + * @section sensor_processor_stats_examples Example Code * The processor is used as a sensor in all of the examples, including the * @menulink{processor_stats} example. * @@ -93,7 +93,7 @@ /**@}*/ /** - * @anchor sensor_processor_sensor_timing + * @anchor sensor_processor_stats_timing * @name Sensor Timing * The sensor timing for the processor/mcu * - Timing variables do not apply to the processor in the same way they do to @@ -117,12 +117,15 @@ * The battery voltage variable from the processor/mcu * This is the voltage as measured on the battery attached to the MCU using the * inbuilt ADC, if applicable. - * - Range is assumed to be 0 to 5V - * - Accuracy is processor dependent + * - Range and accuracy are processor dependent. + * + * We do not set a specific maximum for this variable. */ /**@{*/ +/// @brief Minimum battery voltage in volts. +#define PROCESSOR_BATTERY_MIN_V 0.0 /** - * @brief Decimals places in string representation; battery voltage should + * @brief Decimal places in string representation; battery voltage should * have 3. * * The resolution is of the EnviroDIY Mayfly is 0.005V, we will use that @@ -157,7 +160,7 @@ * {{ @ref ProcessorStats_FreeRam::ProcessorStats_FreeRam }} */ /**@{*/ -/// @brief Decimals places in string representation; ram should have 0 - +/// @brief Decimal places in string representation; ram should have 0 - /// resolution is 1 bit. #define PROCESSOR_RAM_RESOLUTION 0 /// @brief Free RAM is stored in sensorValues[1] @@ -187,7 +190,11 @@ * {{ @ref ProcessorStats_SampleNumber::ProcessorStats_SampleNumber }} */ /**@{*/ -/// @brief Decimals places in string representation; sample number should have +/// @brief Minimum sample number. +#define PROCESSOR_SAMPNUM_MIN_NUM 0 +/// @brief Maximum sample number. +#define PROCESSOR_SAMPNUM_MAX_NUM 32767 +/// @brief Decimal places in string representation; sample number should have /// 0 - resolution is 1. #define PROCESSOR_SAMPNUM_RESOLUTION 0 /// @brief Sample number is stored in sensorValues[2] @@ -217,10 +224,14 @@ * {{ @ref ProcessorStats_ResetCode::ProcessorStats_ResetCode }} */ /**@{*/ -/// @brief Decimals places in string representation; ram should have 0 - +/// @brief Minimum reset code value. +#define PROCESSOR_RESET_MIN_CODE 0 +/// @brief Maximum reset code value. +#define PROCESSOR_RESET_MAX_CODE 255 +/// @brief Decimal places in string representation; reset code should have 0 - /// it's just a code #define PROCESSOR_RESET_RESOLUTION 0 -/// @brief Free RAM is stored in sensorValues[1] +/// @brief Reset code is stored in sensorValues[3] #define PROCESSOR_RESET_VAR_NUM 3 /// @brief Variable name in /// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); @@ -234,106 +245,6 @@ #define PROCESSOR_RESET_DEFAULT_CODE "ResetCode" /**@}*/ -/** - * @def LOGGER_BOARD - * @brief Pretty text for the board name derived from the board's compiler - * define. - */ - -// EnviroDIY boards -#if defined(ARDUINO_AVR_ENVIRODIY_MAYFLY) -#define LOGGER_BOARD "EnviroDIY Mayfly" -#elif defined(ENVIRODIY_STONEFLY_M4) -#define LOGGER_BOARD "EnviroDIY Stonefly" - -// Sodaq boards -#elif defined(ARDUINO_SODAQ_EXPLORER) -#define LOGGER_BOARD "SODAQ ExpLoRer" -#elif defined(ARDUINO_SODAQ_AUTONOMO) -#define LOGGER_BOARD "SODAQ Autonomo" -#elif defined(ARDUINO_SODAQ_ONE_BETA) -#define LOGGER_BOARD "SODAQ ONE Beta" -#elif defined(ARDUINO_SODAQ_ONE) -#define LOGGER_BOARD "SODAQ ONE" -#elif defined(ARDUINO_AVR_SODAQ_MBILI) -#define LOGGER_BOARD "SODAQ Mbili" -#elif defined(ARDUINO_AVR_SODAQ_NDOGO) -#define LOGGER_BOARD "SODAQ Ndogo" -#elif defined(ARDUINO_AVR_SODAQ_TATU) -#define LOGGER_BOARD "SODAQ Tatu" -#elif defined(ARDUINO_AVR_SODAQ_MOJA) -#define LOGGER_BOARD "SODAQ Moja" - -// Adafruit boards -#elif defined(ARDUINO_AVR_FEATHER328P) -#define LOGGER_BOARD "Feather 328p" -#elif defined(ARDUINO_AVR_FEATHER32U4) -#define LOGGER_BOARD "Feather 32u4" -#elif defined(ARDUINO_SAMD_FEATHER_M0_EXPRESS) || \ - defined(ADAFRUIT_FEATHER_M0_EXPRESS) -#define LOGGER_BOARD "Feather M0 Express" -#elif defined(ARDUINO_SAMD_FEATHER_M0) || defined(ADAFRUIT_FEATHER_M0) -#define LOGGER_BOARD "Feather M0" -#elif defined(ADAFRUIT_GRAND_CENTRAL_M4) -#define LOGGER_BOARD "Grand Central" -#elif defined(ADAFRUIT_FEATHER_M4_ADALOGGER) -#define LOGGER_BOARD "Feather M4 Adalogger" -#elif defined(ARDUINO_FEATHER_M4_CAN) || defined(ADAFRUIT_FEATHER_M4_CAN) -#define LOGGER_BOARD "Feather M4 CAN" -#elif defined(ARDUINO_FEATHER_M4) || defined(ADAFRUIT_FEATHER_M4_EXPRESS) -#define LOGGER_BOARD "Feather M4" - -// Arduino boards -#elif defined(ARDUINO_AVR_ADK) -#define LOGGER_BOARD "Mega Adk" -// Bluetooth -#elif defined(ARDUINO_AVR_BT) -#define LOGGER_BOARD "Bt" -#elif defined(ARDUINO_AVR_DUEMILANOVE) -#define LOGGER_BOARD "Duemilanove" -#elif defined(ARDUINO_AVR_ESPLORA) -#define LOGGER_BOARD "Esplora" -#elif defined(ARDUINO_AVR_ETHERNET) -#define LOGGER_BOARD "Ethernet" -#elif defined(ARDUINO_AVR_FIO) -#define LOGGER_BOARD "Fio" -#elif defined(ARDUINO_AVR_GEMMA) -#define LOGGER_BOARD "Gemma" -#elif defined(ARDUINO_AVR_LEONARDO) -#define LOGGER_BOARD "Leonardo" -#elif defined(ARDUINO_AVR_LILYPAD) -#define LOGGER_BOARD "Lilypad" -#elif defined(ARDUINO_AVR_LILYPAD_USB) -#define LOGGER_BOARD "Lilypad Usb" -#elif defined(ARDUINO_AVR_MEGA) -#define LOGGER_BOARD "Mega" -#elif defined(ARDUINO_AVR_MEGA2560) -#define LOGGER_BOARD "Mega 2560" -#elif defined(ARDUINO_AVR_MICRO) -#define LOGGER_BOARD "Micro" -#elif defined(ARDUINO_AVR_MINI) -#define LOGGER_BOARD "Mini" -#elif defined(ARDUINO_AVR_NANO) -#define LOGGER_BOARD "Nano" -#elif defined(ARDUINO_AVR_NG) -#define LOGGER_BOARD "NG" -#elif defined(ARDUINO_AVR_PRO) -#define LOGGER_BOARD "Pro" -#elif defined(ARDUINO_AVR_ROBOT_CONTROL) -#define LOGGER_BOARD "Robot Ctrl" -#elif defined(ARDUINO_AVR_ROBOT_MOTOR) -#define LOGGER_BOARD "Robot Motor" -#elif defined(ARDUINO_AVR_UNO) -#define LOGGER_BOARD "Uno" -#elif defined(ARDUINO_AVR_YUN) -#define LOGGER_BOARD "Yun" -#elif defined(ARDUINO_SAMD_ZERO) -#define LOGGER_BOARD "Zero" - -#else -#define LOGGER_BOARD "Unknown" -#endif - // The main class for the Processor // Only need a sleep and wake since these DON'T use the default of powering @@ -407,24 +318,23 @@ class ProcessorStats : public Sensor { * voltage measurement! */ ProcessorStats(const char* boardName, const char* version, - int8_t batteryPin, float batteryMultiplier, - float operatingVoltage, uint8_t measurementsToAverage = 1); + int8_t batteryPin = BATTERY_PIN, + float batteryMultiplier = BATTERY_MULTIPLIER, + float operatingVoltage = OPERATING_VOLTAGE, + uint8_t measurementsToAverage = 1); /** * @brief Destroy the Processor Stats object */ - ~ProcessorStats(); + ~ProcessorStats() override = default; /** * @copydoc Sensor::getSensorLocation() * * This returns the processor name as read from the compiler variable. */ - String getSensorLocation(void) override; + String getSensorLocation() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; /** * @brief A helper to get battery voltage as measured by a direct connection @@ -432,7 +342,7 @@ class ProcessorStats : public Sensor { * * @return The battery voltage in volts */ - float getBatteryVoltage(void); + float getBatteryVoltage(); /** * @brief Get the processor code for the last reset cause @@ -440,16 +350,16 @@ class ProcessorStats : public Sensor { * @return The processor code for the last reset cause */ #if !defined(__SAMD51__) - uint8_t getLastResetCode(void); + uint8_t getLastResetCode(); #else - uint16_t getLastResetCode(void); + uint16_t getLastResetCode(); #endif /** * @brief Get the cause of the last reset as a string description. * * @return A string describing the last reset cause */ - String getLastResetCause(void); + String getLastResetCause(); private: const char* _version; ///< Internal reference to the board version @@ -487,25 +397,13 @@ class ProcessorStats_Battery : public Variable { explicit ProcessorStats_Battery( ProcessorStats* parentSense, const char* uuid = "", const char* varCode = PROCESSOR_BATTERY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)PROCESSOR_BATTERY_VAR_NUM, - (uint8_t)PROCESSOR_BATTERY_RESOLUTION, - PROCESSOR_BATTERY_VAR_NAME, PROCESSOR_BATTERY_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new ProcessorStats_Battery object. - * - * @note This must be tied with a parent ProcessorStats before it can be - * used. - */ - ProcessorStats_Battery() - : Variable((uint8_t)PROCESSOR_BATTERY_VAR_NUM, - (uint8_t)PROCESSOR_BATTERY_RESOLUTION, - PROCESSOR_BATTERY_VAR_NAME, PROCESSOR_BATTERY_UNIT_NAME, - PROCESSOR_BATTERY_DEFAULT_CODE) {} + : Variable(parentSense, PROCESSOR_BATTERY_VAR_NUM, + PROCESSOR_BATTERY_RESOLUTION, PROCESSOR_BATTERY_VAR_NAME, + PROCESSOR_BATTERY_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ProcessorStats_Battery object - no action needed. */ - ~ProcessorStats_Battery() {} + ~ProcessorStats_Battery() override = default; }; @@ -540,23 +438,13 @@ class ProcessorStats_FreeRam : public Variable { explicit ProcessorStats_FreeRam( ProcessorStats* parentSense, const char* uuid = "", const char* varCode = PROCESSOR_RAM_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)PROCESSOR_RAM_VAR_NUM, - (uint8_t)PROCESSOR_RAM_RESOLUTION, PROCESSOR_RAM_VAR_NAME, - PROCESSOR_RAM_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ProcessorStats_FreeRam object. - * - * @note This must be tied with a parent ProcessorStats before it can be - * used. - */ - ProcessorStats_FreeRam() - : Variable((uint8_t)PROCESSOR_RAM_VAR_NUM, - (uint8_t)PROCESSOR_RAM_RESOLUTION, PROCESSOR_RAM_VAR_NAME, - PROCESSOR_RAM_UNIT_NAME, PROCESSOR_RAM_DEFAULT_CODE) {} + : Variable(parentSense, PROCESSOR_RAM_VAR_NUM, PROCESSOR_RAM_RESOLUTION, + PROCESSOR_RAM_VAR_NAME, PROCESSOR_RAM_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the ProcessorStats_FreeRam object - no action needed. */ - ~ProcessorStats_FreeRam() {} + ~ProcessorStats_FreeRam() override = default; }; @@ -587,26 +475,14 @@ class ProcessorStats_SampleNumber : public Variable { explicit ProcessorStats_SampleNumber( ProcessorStats* parentSense, const char* uuid = "", const char* varCode = PROCESSOR_SAMPNUM_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)PROCESSOR_SAMPNUM_VAR_NUM, - (uint8_t)PROCESSOR_SAMPNUM_RESOLUTION, - PROCESSOR_SAMPNUM_VAR_NAME, PROCESSOR_SAMPNUM_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new ProcessorStats_SampleNumber object. - * - * @note This must be tied with a parent ProcessorStats before it can be - * used. - */ - ProcessorStats_SampleNumber() - : Variable((uint8_t)PROCESSOR_SAMPNUM_VAR_NUM, - (uint8_t)PROCESSOR_SAMPNUM_RESOLUTION, - PROCESSOR_SAMPNUM_VAR_NAME, PROCESSOR_SAMPNUM_UNIT_NAME, - PROCESSOR_SAMPNUM_DEFAULT_CODE) {} + : Variable(parentSense, PROCESSOR_SAMPNUM_VAR_NUM, + PROCESSOR_SAMPNUM_RESOLUTION, PROCESSOR_SAMPNUM_VAR_NAME, + PROCESSOR_SAMPNUM_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ProcessorStats_SampleNumber() object - no action * needed. */ - ~ProcessorStats_SampleNumber() {} + ~ProcessorStats_SampleNumber() override = default; }; @@ -638,27 +514,15 @@ class ProcessorStats_ResetCode : public Variable { explicit ProcessorStats_ResetCode( ProcessorStats* parentSense, const char* uuid = "", const char* varCode = PROCESSOR_RESET_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)PROCESSOR_RESET_VAR_NUM, - (uint8_t)PROCESSOR_RESET_RESOLUTION, - PROCESSOR_RESET_VAR_NAME, PROCESSOR_RESET_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new ProcessorStats_ResetCode object. - * - * @note This must be tied with a parent ProcessorStats before it can be - * used. - */ - ProcessorStats_ResetCode() - : Variable((uint8_t)PROCESSOR_RESET_VAR_NUM, - (uint8_t)PROCESSOR_RESET_RESOLUTION, - PROCESSOR_RESET_VAR_NAME, PROCESSOR_RESET_UNIT_NAME, - PROCESSOR_RESET_DEFAULT_CODE) {} + : Variable(parentSense, PROCESSOR_RESET_VAR_NUM, + PROCESSOR_RESET_RESOLUTION, PROCESSOR_RESET_VAR_NAME, + PROCESSOR_RESET_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ProcessorStats_ResetCode object - no action needed. */ - ~ProcessorStats_ResetCode() {} + ~ProcessorStats_ResetCode() override = default; }; /**@}*/ #endif // SRC_SENSORS_PROCESSORSTATS_H_ -// cSpell:ignore SAMPNUM sampno Tatu Moja Adalogger Duemilanove Esplora Lilypad +// cSpell:words SAMPNUM sampno Tatu Moja Adalogger Duemilanove Esplora diff --git a/src/sensors/RainCounterI2C.cpp b/src/sensors/RainCounterI2C.cpp index 6b677d7cd..bbb91b0f7 100644 --- a/src/sensors/RainCounterI2C.cpp +++ b/src/sensors/RainCounterI2C.cpp @@ -40,7 +40,7 @@ RainCounterI2C::RainCounterI2C(TwoWire* theI2C, uint8_t i2cAddressHex, 1), _rainPerTip(rainPerTip), _i2cAddressHex(i2cAddressHex), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} RainCounterI2C::RainCounterI2C(uint8_t i2cAddressHex, float rainPerTip) : Sensor("RainCounterI2C", BUCKET_NUM_VARIABLES, BUCKET_WARM_UP_TIME_MS, BUCKET_STABILIZATION_TIME_MS, BUCKET_MEASUREMENT_TIME_MS, -1, -1, @@ -63,20 +63,25 @@ RainCounterI2C::~RainCounterI2C() {} #endif -String RainCounterI2C::getSensorLocation(void) { +String RainCounterI2C::getSensorLocation() { #if defined(MS_RAIN_SOFTWAREWIRE) - String address = F("SoftwareWire"); + String address; + address.reserve( + 25); // Reserve for "SoftwareWire" + pin# + "_0x" + hex chars + address = F("SoftwareWire"); if (_dataPin >= 0) address += _dataPin; address += F("_0x"); #else - String address = F("I2C_0x"); + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); #endif address += String(_i2cAddressHex, HEX); return address; } -bool RainCounterI2C::setup(void) { +bool RainCounterI2C::setup() { _i2c->begin(); // Start the wire library (sensor power not required) // Eliminate any potential extra waits in the wire library // These waits would be caused by a readBytes or parseX being called @@ -90,10 +95,14 @@ bool RainCounterI2C::setup(void) { } -bool RainCounterI2C::addSingleMeasurementResult(void) { - // initialize values - float rain = -9999; // Number of mm of rain - int32_t tips = -9999; // Number of tip events, increased for anemometer +bool RainCounterI2C::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + bool success = false; // assume the worst + float rain = MS_INVALID_VALUE; // Number of mm of rain + int32_t tips = + MS_INVALID_VALUE; // Number of tip events, increased for anemometer // Get data from external tip counter // if the 'requestFrom' returns 0, it means no bytes were received @@ -101,14 +110,19 @@ bool RainCounterI2C::addSingleMeasurementResult(void) { static_cast(4))) { MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - 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(); + // Create a byte array of 4 bytes to hold incoming data + uint8_t SerialBuffer[4] = {0, 0, 0, 0}; + uint8_t byte_in = 0; // Start iterator for reading Bytes + while (_i2c->available() && byte_in < 4) { + SerialBuffer[byte_in] = _i2c->read(); MS_DBG(F(" SerialBuffer["), byte_in, F("] = "), SerialBuffer[byte_in]); byte_in++; // increment by 1 } + if (byte_in < 1) { + MS_DBG(F(" No data bytes received")); + return finalizeMeasurementAttempt(false); + } // Concatenate bytes into uint32_t by bit-shifting // https://thewanderingengineer.com/2015/05/06/sending-16-bit-and-32-bit-numbers-with-arduino-i2c/# @@ -137,24 +151,25 @@ bool RainCounterI2C::addSingleMeasurementResult(void) { _rainPerTip; // Multiply by tip coefficient (0.2 by default) if (tips < 0) - tips = -9999; // If negative value results, return failure + tips = + MS_INVALID_VALUE; // If negative value results, return failure if (rain < 0) - rain = -9999; // If negative value results, return failure + rain = + MS_INVALID_VALUE; // If negative value results, return failure MS_DBG(F(" Rain:"), rain); MS_DBG(F(" Tips:"), tips); + + if (rain != MS_INVALID_VALUE && + tips != MS_INVALID_VALUE) { // if both are valid + verifyAndAddMeasurementResult(BUCKET_RAIN_VAR_NUM, rain); + verifyAndAddMeasurementResult(BUCKET_TIPS_VAR_NUM, tips); + success = true; + } } else { MS_DBG(F("No bytes received from"), getSensorNameAndLocation()); } - verifyAndAddMeasurementResult(BUCKET_RAIN_VAR_NUM, rain); - verifyAndAddMeasurementResult(BUCKET_TIPS_VAR_NUM, tips); - - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // Return true when finished - return true; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/RainCounterI2C.h b/src/sensors/RainCounterI2C.h index b8bdd1804..b8907a430 100644 --- a/src/sensors/RainCounterI2C.h +++ b/src/sensors/RainCounterI2C.h @@ -137,13 +137,18 @@ * @anchor sensor_i2c_rain_depth * @name Rain Depth * The rain depth variable from a Trinket-based tipping bucket counter - * - Range and accuracy depend on the tipping bucket used + * - Range and accuracy depend on the tipping bucket used. We set it to a big + * number here. * * {{ @ref RainCounterI2C_Depth::RainCounterI2C_Depth }} */ /**@{*/ +/// @brief Minimum precipitation in millimeters. +#define BUCKET_RAIN_MIN_MM 0.0 +/// @brief Maximum precipitation in millimeters. +#define BUCKET_RAIN_MAX_MM 500.0 /** - * @brief Decimals places in string representation; rain depth should have 2. + * @brief Decimal places in string representation; rain depth should have 2. * * Resolution is typically either 0.01" or 0.2mm of rainfall, depending on * if bucket is calibrated to English or metric units. @@ -167,12 +172,17 @@ * @anchor sensor_i2c_rain_tips * @name Tip Count * Defines for tip count variable from a Trinket-based tipping bucket counter - * - Range and accuracy depend on the tipping bucket used. + * - Range and accuracy depend on the tipping bucket used. We set the maximum + * tip count to the maximum value of an unsigned long. * * {{ @ref RainCounterI2C_Tips::RainCounterI2C_Tips }} */ /**@{*/ -/// @brief Decimals places in string representation; the number of tips should +/// @brief Minimum number of bucket tips. +#define BUCKET_TIPS_MIN_TIPS 0 +/// @brief Maximum number of bucket tips. +#define BUCKET_TIPS_MAX_TIPS 4294967295 +/// @brief Decimal places in string representation; the number of tips should /// have 0 - resolution is 1 tip. #define BUCKET_TIPS_RESOLUTION 0 /// @brief Sensor variable number; the number of tips is stored in @@ -236,7 +246,7 @@ class RainCounterI2C : public Sensor { RainCounterI2C(int8_t dataPin, int8_t clockPin, uint8_t i2cAddressHex = 0x08, float rainPerTip = 0.2); #endif -#if !defined(MS_RAIN_SOFTWAREWIRE) | defined DOXYGEN +#if !defined(MS_RAIN_SOFTWAREWIRE) || defined(DOXYGEN) /** * @brief Construct a new Rain Counter I2C object using a secondary * *hardware* I2C instance. @@ -271,7 +281,7 @@ class RainCounterI2C : public Sensor { * @brief Destroy the Rain Counter I2C object. Also destroy the software * I2C instance if one was created. */ - ~RainCounterI2C(); + ~RainCounterI2C() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -282,16 +292,11 @@ class RainCounterI2C : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** @@ -346,23 +351,13 @@ class RainCounterI2C_Tips : public Variable { explicit RainCounterI2C_Tips(RainCounterI2C* parentSense, const char* uuid = "", const char* varCode = BUCKET_TIPS_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BUCKET_TIPS_VAR_NUM, - (uint8_t)BUCKET_TIPS_RESOLUTION, BUCKET_TIPS_VAR_NAME, - BUCKET_TIPS_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new RainCounterI2C_Tips object. - * - * @note This must be tied with a parent RainCounterI2C before it can be - * used. - */ - RainCounterI2C_Tips() - : Variable((uint8_t)BUCKET_TIPS_VAR_NUM, - (uint8_t)BUCKET_TIPS_RESOLUTION, BUCKET_TIPS_VAR_NAME, - BUCKET_TIPS_UNIT_NAME, BUCKET_TIPS_DEFAULT_CODE) {} + : Variable(parentSense, BUCKET_TIPS_VAR_NUM, BUCKET_TIPS_RESOLUTION, + BUCKET_TIPS_VAR_NAME, BUCKET_TIPS_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the RainCounterI2C_Tips object - no action needed. */ - ~RainCounterI2C_Tips() {} + ~RainCounterI2C_Tips() override = default; }; /** @@ -388,23 +383,13 @@ class RainCounterI2C_Depth : public Variable { explicit RainCounterI2C_Depth( RainCounterI2C* parentSense, const char* uuid = "", const char* varCode = BUCKET_RAIN_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)BUCKET_RAIN_VAR_NUM, - (uint8_t)BUCKET_RAIN_RESOLUTION, BUCKET_RAIN_VAR_NAME, - BUCKET_RAIN_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new RainCounterI2C_Depth object. - * - * @note This must be tied with a parent RainCounterI2C before it can be - * used. - */ - RainCounterI2C_Depth() - : Variable((uint8_t)BUCKET_RAIN_VAR_NUM, - (uint8_t)BUCKET_RAIN_RESOLUTION, BUCKET_RAIN_VAR_NAME, - BUCKET_RAIN_UNIT_NAME, BUCKET_RAIN_DEFAULT_CODE) {} + : Variable(parentSense, BUCKET_RAIN_VAR_NUM, BUCKET_RAIN_RESOLUTION, + BUCKET_RAIN_VAR_NAME, BUCKET_RAIN_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the RainCounterI2C_Depth object - no action needed. */ - ~RainCounterI2C_Depth() {} + ~RainCounterI2C_Depth() override = default; }; /**@}*/ #endif // SRC_SENSORS_RAINCOUNTERI2C_H_ diff --git a/src/sensors/SDI12Sensors.cpp b/src/sensors/SDI12Sensors.cpp index 3932ac26d..65fba5a67 100644 --- a/src/sensors/SDI12Sensors.cpp +++ b/src/sensors/SDI12Sensors.cpp @@ -48,6 +48,7 @@ SDI12Sensors::SDI12Sensors(char SDI12address, int8_t powerPin, int8_t dataPin, _SDI12Internal(dataPin), _SDI12address(SDI12address), _extraWakeTime(extraWakeTime) {} +// Delegating constructor SDI12Sensors::SDI12Sensors(char* SDI12address, int8_t powerPin, int8_t dataPin, uint8_t measurementsToAverage, const char* sensorName, @@ -56,12 +57,11 @@ SDI12Sensors::SDI12Sensors(char* SDI12address, int8_t powerPin, int8_t dataPin, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms, int8_t extraWakeTime, uint8_t incCalcValues) - : Sensor(sensorName, totalReturnedValues, warmUpTime_ms, - stabilizationTime_ms, measurementTime_ms, powerPin, dataPin, - measurementsToAverage, incCalcValues), - _SDI12Internal(dataPin), - _SDI12address(*SDI12address), - _extraWakeTime(extraWakeTime) {} + : SDI12Sensors(*SDI12address, powerPin, dataPin, measurementsToAverage, + sensorName, totalReturnedValues, warmUpTime_ms, + stabilizationTime_ms, measurementTime_ms, extraWakeTime, + incCalcValues) {} +// Delegating constructor SDI12Sensors::SDI12Sensors(int SDI12address, int8_t powerPin, int8_t dataPin, uint8_t measurementsToAverage, const char* sensorName, @@ -70,17 +70,13 @@ SDI12Sensors::SDI12Sensors(int SDI12address, int8_t powerPin, int8_t dataPin, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms, int8_t extraWakeTime, uint8_t incCalcValues) - : Sensor(sensorName, totalReturnedValues, warmUpTime_ms, - stabilizationTime_ms, measurementTime_ms, powerPin, dataPin, - measurementsToAverage, incCalcValues), - _SDI12Internal(dataPin), - _SDI12address(static_cast(SDI12address + '0')), - _extraWakeTime(extraWakeTime) {} -// Destructor -SDI12Sensors::~SDI12Sensors() {} + : SDI12Sensors(static_cast(SDI12address + '0'), powerPin, dataPin, + measurementsToAverage, sensorName, totalReturnedValues, + warmUpTime_ms, stabilizationTime_ms, measurementTime_ms, + extraWakeTime, incCalcValues) {} -bool SDI12Sensors::setup(void) { +bool SDI12Sensors::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -99,8 +95,8 @@ bool SDI12Sensors::setup(void) { // by the SDI-12 protocol for a sensor response. // May want to bump it up even further here. _SDI12Internal.setTimeout(150); - // Force the timeout value to be -9999 (This should be library default.) - _SDI12Internal.setTimeoutValue(-9999); + // Force the timeout value to be MS_INVALID_VALUE + _SDI12Internal.setTimeoutValue(MS_INVALID_VALUE); #if defined(__AVR__) || defined(ARDUINO_ARCH_AVR) // Allow the SDI-12 library access to interrupts @@ -126,7 +122,7 @@ bool SDI12Sensors::setup(void) { } -void SDI12Sensors::activate(void) { +void SDI12Sensors::activate() { // Begin the SDI-12 interface _SDI12Internal.begin(); @@ -134,7 +130,7 @@ void SDI12Sensors::activate(void) { _SDI12Internal.clearBuffer(); } -void SDI12Sensors::deactivate(void) { +void SDI12Sensors::deactivate() { // Empty the SDI-12 buffer _SDI12Internal.clearBuffer(); @@ -144,7 +140,7 @@ void SDI12Sensors::deactivate(void) { } -bool SDI12Sensors::requestSensorAcknowledgement(void) { +bool SDI12Sensors::requestSensorAcknowledgement() { // Empty the buffer _SDI12Internal.clearBuffer(); @@ -190,7 +186,7 @@ bool SDI12Sensors::requestSensorAcknowledgement(void) { // A helper function to run the "sensor info" SDI12 command -bool SDI12Sensors::getSensorInfo(void) { +bool SDI12Sensors::getSensorInfo() { activate(); // Check that the sensor is there and responding @@ -261,29 +257,32 @@ bool SDI12Sensors::getSensorInfo(void) { // The sensor vendor -String SDI12Sensors::getSensorVendor(void) { +String SDI12Sensors::getSensorVendor() { return _sensorVendor; } // The sensor model -String SDI12Sensors::getSensorModel(void) { +String SDI12Sensors::getSensorModel() { return _sensorModel; } // The sensor version -String SDI12Sensors::getSensorVersion(void) { +String SDI12Sensors::getSensorVersion() { return _sensorVersion; } // The sensor serial number -String SDI12Sensors::getSensorSerialNumber(void) { +String SDI12Sensors::getSensorSerialNumber() { return _sensorSerialNumber; } // The sensor installation location on the Mayfly -String SDI12Sensors::getSensorLocation(void) { - String sensorLocation = F("SDI12-"); +String SDI12Sensors::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve( + 20); // Reserve for "SDI12-" + address + "_Pin" + pin + sensorLocation = F("SDI12-"); sensorLocation += String(_SDI12address) + F("_Pin") + String(_dataPin); return sensorLocation; } @@ -375,7 +374,7 @@ int8_t SDI12Sensors::startSDI12Measurement(bool isConcurrent) { #ifndef MS_SDI12_NON_CONCURRENT // Sending the command to get a concurrent measurement -bool SDI12Sensors::startSingleMeasurement(void) { +bool SDI12Sensors::startSingleMeasurement() { // Sensor::startSingleMeasurement() checks that if it's awake/active and // sets the timestamp and status bits. If it returns false, there's no // reason to go on. @@ -615,10 +614,10 @@ bool SDI12Sensors::getResults(bool verify_crc) { F("Parsed value:"), String(result, len_post_dec)); #endif // The SDI-12 library should return our set timeout value of - // -9999 on timeout - if (result == -9999 || isnan(result)) { + // MS_INVALID_VALUE on timeout + if (result == MS_INVALID_VALUE || isnan(result)) { MS_DBG(F("Result is not valid!")); - result = -9999; + result = MS_INVALID_VALUE; } // Put the read value into the temporary buffer. After each // result is read, tick up the number of results received so @@ -626,7 +625,7 @@ bool SDI12Sensors::getResults(bool verify_crc) { // buffer. cmd_rx[cmd_results] = result; // add how many results we have - if (result != -9999) { + if (result != MS_INVALID_VALUE) { gotResults = true; cmd_results++; } @@ -708,31 +707,28 @@ bool SDI12Sensors::getResults(bool verify_crc) { #ifndef MS_SDI12_NON_CONCURRENT -bool SDI12Sensors::addSingleMeasurementResult(void) { - bool success = false; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - success = getResults(MS_SDI12_USE_CRC); - } else { - // If there's no measurement, need to make sure we send over all - // of the "failed" result values - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); - for (uint8_t i = 0; i < _numReturnedValues; i++) { - verifyAndAddMeasurementResult(i, static_cast(-9999)); - } - } +// This function is using concurrent measurements, so the MEASUREMENT_SUCCESSFUL +// bit was set in the specialized startSingleMeasurement function based on +// whether the response to the SDI-12 start measurement command. +bool SDI12Sensors::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + bool success = getResults(MS_SDI12_USE_CRC); - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } -#else -bool SDI12Sensors::addSingleMeasurementResult(void) { +#else // concurrent measurement disabled +// This is for non-concurrent measurements, so this function must both start the +// measurement and get results at once. For non-concurrent measurements, the +// MEASUREMENT_SUCCESSFUL bit is set in the generic sensor +// startSingleMeasurement function from sensor base, which only verifies that +// the sensor is awake and capable of starting measurements. +bool SDI12Sensors::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + bool success = false; String startCommand; @@ -752,7 +748,7 @@ bool SDI12Sensors::addSingleMeasurementResult(void) { MS_DBG(F(" NON-concurrent measurement started.")); // Update the time that a measurement was requested _millisMeasurementRequested = millis(); - // Set the status bit for measurement start success (bit 6) + // Re-set the status bit for measurement start success (bit 6) setStatusBit(MEASUREMENT_SUCCESSFUL); // Since this is not a concurrent measurement, we must sit around @@ -791,25 +787,16 @@ bool SDI12Sensors::addSingleMeasurementResult(void) { MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); for (uint8_t i = 0; i < _numReturnedValues; i++) { - verifyAndAddMeasurementResult(i, static_cast(-9999)); + verifyAndAddMeasurementResult( + i, static_cast(MS_INVALID_VALUE)); } } - } else { - // If there's no response, we still need to send over all the failed - // values - for (uint8_t i = 0; i < _numReturnedValues; i++) { - verifyAndAddMeasurementResult(i, static_cast(-9999)); - } } // Empty the buffer and de-activate the SDI-12 Object deactivate(); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } #endif // #ifndef MS_SDI12_NON_CONCURRENT diff --git a/src/sensors/SDI12Sensors.h b/src/sensors/SDI12Sensors.h index 896647e28..e64ca816d 100644 --- a/src/sensors/SDI12Sensors.h +++ b/src/sensors/SDI12Sensors.h @@ -165,7 +165,7 @@ class SDI12Sensors : public Sensor { /** * @brief Destroy the SDI12Sensors object - no action taken */ - virtual ~SDI12Sensors(); + ~SDI12Sensors() override = default; /** * @brief Get the stored sensor vendor name returned by a previously called @@ -174,7 +174,7 @@ class SDI12Sensors : public Sensor { * @return String The name of the sensor vendor as reported by the sensor * itself. */ - String getSensorVendor(void); + String getSensorVendor(); /** * @brief Get the stored sensor model name returned by a previously called * SDI-12 get sensor information (aI!) command. @@ -182,7 +182,7 @@ class SDI12Sensors : public Sensor { * @return String The name of the sensor model as reported by the sensor * itself. */ - String getSensorModel(void); + String getSensorModel(); /** * @brief Get the stored sensor version returned by a previously called * SDI-12 get sensor information (aI!) command. @@ -190,7 +190,7 @@ class SDI12Sensors : public Sensor { * @return String The version of the sensor as reported by the sensor * itself. */ - String getSensorVersion(void); + String getSensorVersion(); /** * @brief Get the stored sensor serial number returned by a previously * called SDI-12 get sensor information (aI!) command. @@ -198,27 +198,27 @@ class SDI12Sensors : public Sensor { * @return String The serial number of the sensor as reported by the sensor * itself. */ - String getSensorSerialNumber(void); + String getSensorSerialNumber(); /** * @copydoc Sensor::getSensorLocation() * * For SDI-12 sensors this returns a concatenation of the data pin number * and the SDI-12 address. */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Calls the begin for the SDI-12 object to set all of the * pre-scalers and timers. */ - void activate(void); + void activate(); /** * @brief Empties the SDI-12 object buffer and then ends it. The end * function unsets all timer pre-scalers and **--crucially--** disables the * interrupts on the SDI-12 data pin. */ - void deactivate(void); + void deactivate(); /** * @brief Do any one-time preparations needed before the sensor will be able @@ -233,28 +233,14 @@ class SDI12Sensors : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; // Only need this for concurrent measurements. // NOTE: By default, concurrent measurements are used! #ifndef MS_SDI12_NON_CONCURRENT - /** - * @brief Tell the sensor to start a single measurement, if needed. - * - * This also sets the #_millisMeasurementRequested timestamp. - * - * @note This function does NOT include any waiting for the sensor to be - * warmed up or stable! - * - * @return True if the start measurement function completed - * successfully. - */ - bool startSingleMeasurement(void) override; + bool startSingleMeasurement() override; #endif - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; protected: /** @@ -264,7 +250,7 @@ class SDI12Sensors : public Sensor { * @return True if the correct SDI-12 sensor replied to the * command. */ - bool requestSensorAcknowledgement(void); + bool requestSensorAcknowledgement(); /** * @brief Send the SDI-12 'info' command [address][I][!] to a sensor and * parse the result into the vendor, model, version, and serial number. @@ -272,7 +258,7 @@ class SDI12Sensors : public Sensor { * @return True if all expected information fields returned by the * sensor. */ - bool getSensorInfo(void); + bool getSensorInfo(); /** * @brief Tell the sensor to start a single measurement, if needed. * diff --git a/src/sensors/SensirionSHT4x.cpp b/src/sensors/SensirionSHT4x.cpp index 6455d3964..6fb30e057 100644 --- a/src/sensors/SensirionSHT4x.cpp +++ b/src/sensors/SensirionSHT4x.cpp @@ -18,7 +18,7 @@ SensirionSHT4x::SensirionSHT4x(TwoWire* theI2C, int8_t powerPin, bool useHeater, SHT4X_STABILIZATION_TIME_MS, SHT4X_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage), _useHeater(useHeater), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} SensirionSHT4x::SensirionSHT4x(int8_t powerPin, bool useHeater, uint8_t measurementsToAverage) : Sensor("SensirionSHT4x", SHT4X_NUM_VARIABLES, SHT4X_WARM_UP_TIME_MS, @@ -26,16 +26,14 @@ SensirionSHT4x::SensirionSHT4x(int8_t powerPin, bool useHeater, -1, measurementsToAverage, SHT4X_INC_CALC_VARIABLES), _useHeater(useHeater), _i2c(&Wire) {} -// Destructor -SensirionSHT4x::~SensirionSHT4x() {} -String SensirionSHT4x::getSensorLocation(void) { +String SensirionSHT4x::getSensorLocation() { return F("I2C_0x44"); } -bool SensirionSHT4x::setup(void) { +bool SensirionSHT4x::setup() { _i2c->begin(); // Start the wire library (sensor power not required) // Eliminate any potential extra waits in the wire library // These waits would be caused by a readBytes or parseX being called @@ -65,6 +63,9 @@ bool SensirionSHT4x::setup(void) { ntries++; } + // NOTE: The setPrecision and setHeater functions set internal variables in + // the Adafruit library; they don't require sensor communication. + // Set sensor for high precision sht4x_internal.setPrecision(SHT4X_HIGH_PRECISION); @@ -87,54 +88,51 @@ bool SensirionSHT4x::setup(void) { } -bool SensirionSHT4x::addSingleMeasurementResult(void) { - // Initialize float variables - float temp_val = -9999; - float humid_val = -9999; - bool ret_val = false; +bool SensirionSHT4x::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + bool success = false; + float temp_val = MS_INVALID_VALUE; + float humid_val = MS_INVALID_VALUE; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Make sure the heater is *not* going to run. We want the ambient - // values. - sht4x_internal.setHeater(SHT4X_NO_HEATER); + // Make sure the heater is *not* going to run. We want the ambient + // values. + sht4x_internal.setHeater(SHT4X_NO_HEATER); - // we need to create Adafruit "sensor events" to use the library - sensors_event_t temp_event; - sensors_event_t humidity_event; - ret_val = sht4x_internal.getEvent(&humidity_event, &temp_event); + // we need to create Adafruit "sensor events" to use the library + sensors_event_t temp_event{}; + sensors_event_t humidity_event{}; + success = sht4x_internal.getEvent(&humidity_event, &temp_event); - // get the values from the sensor events + if (success) { + // if getEvent returns true, get values from the sensor temp_val = temp_event.temperature; humid_val = humidity_event.relative_humidity; - - if (!ret_val || isnan(temp_val)) temp_val = -9999; - if (!ret_val || isnan(humid_val)) humid_val = -9999; - - MS_DBG(F(" Temp:"), temp_val, F("°C")); - MS_DBG(F(" Humidity:"), humid_val, '%'); } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(F(" getEvent failed; no values read!")); } - verifyAndAddMeasurementResult(SHT4X_TEMP_VAR_NUM, temp_val); - verifyAndAddMeasurementResult(SHT4X_HUMIDITY_VAR_NUM, humid_val); + if (success && !isnan(temp_val) && !isnan(humid_val)) { + verifyAndAddMeasurementResult(SHT4X_TEMP_VAR_NUM, temp_val); + verifyAndAddMeasurementResult(SHT4X_HUMIDITY_VAR_NUM, humid_val); + } else { + success = false; + } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + MS_DBG(F(" Temp:"), temp_val, F("°C")); + MS_DBG(F(" Humidity:"), humid_val, '%'); - return ret_val; + // Return success value when finished + return finalizeMeasurementAttempt(success); } // The function to run the internal heater before going to sleep -bool SensirionSHT4x::sleep(void) { - if (_useHeater) { return Sensor::sleep(); } +bool SensirionSHT4x::sleep() { + if (!_useHeater) { return Sensor::sleep(); } if (!checkPowerOn()) { return true; } if (_millisSensorActivated == 0) { @@ -161,8 +159,8 @@ bool SensirionSHT4x::sleep(void) { // case 1 second. Usually blocking steps are a problem, but in this case we // need the block because ModularSensors does not currently support a sleep // time like it supports a wake time. - sensors_event_t temp_event; - sensors_event_t humidity_event; + sensors_event_t temp_event{}; + sensors_event_t humidity_event{}; success = sht4x_internal.getEvent(&humidity_event, &temp_event); // Set the command back to no heat for the next measurement. diff --git a/src/sensors/SensirionSHT4x.h b/src/sensors/SensirionSHT4x.h index fb152789d..96bbe95f0 100644 --- a/src/sensors/SensirionSHT4x.h +++ b/src/sensors/SensirionSHT4x.h @@ -129,8 +129,12 @@ * {{ @ref SensirionSHT4x_Humidity::SensirionSHT4x_Humidity }} */ /**@{*/ +/// @brief Minimum relative humidity in percent. +#define SHT4X_HUMIDITY_MIN_RH 0.0 +/// @brief Maximum relative humidity in percent. +#define SHT4X_HUMIDITY_MAX_RH 100.0 /** - * @brief Decimals places in string representation; humidity should have 2 (0.01 + * @brief Decimal places in string representation; humidity should have 2 (0.01 * % RH). * * @note This resolution is some-what silly in light of the ± 1.8 % RH accuracy. @@ -160,9 +164,13 @@ * {{ @ref SensirionSHT4x_Temp::SensirionSHT4x_Temp }} */ /**@{*/ +/// @brief Minimum temperature in degrees Celsius. +#define SHT4X_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define SHT4X_TEMP_MAX_C 125.0 /** - * @brief Decimals places in string representation; humidity should have 2 (0.01 - * °C). + * @brief Decimal places in string representation; temperature should have 2 + * (0.01 °C). * * @note This resolution is some-what silly in light of the ± 0.2°C accuracy. */ @@ -253,14 +261,14 @@ class SensirionSHT4x : public Sensor { /** * @brief Destroy the SensirionSHT4x object - no action needed. */ - ~SensirionSHT4x(); + ~SensirionSHT4x() override = default; /** * @brief Report the I2C address of the SHT4x - which is always 0x44. * * @return Text describing how the sensor is attached to the mcu. */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -272,12 +280,9 @@ class SensirionSHT4x : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; /** * @copydoc Sensor::sleep() @@ -285,7 +290,7 @@ class SensirionSHT4x : public Sensor { * If opted for, we run the SHT4x's internal heater for 1s before going to * sleep. */ - bool sleep(void) override; + bool sleep() override; private: /** @@ -293,7 +298,7 @@ class SensirionSHT4x : public Sensor { */ bool _useHeater; /** - * @brief Internal reference the the Adafruit BME object + * @brief Internal instance of the Adafruit SHT4x object */ Adafruit_SHT4x sht4x_internal; /** @@ -325,23 +330,13 @@ class SensirionSHT4x_Humidity : public Variable { explicit SensirionSHT4x_Humidity( SensirionSHT4x* parentSense, const char* uuid = "", const char* varCode = SHT4X_HUMIDITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)SHT4X_HUMIDITY_VAR_NUM, - (uint8_t)SHT4X_HUMIDITY_RESOLUTION, SHT4X_HUMIDITY_VAR_NAME, + : Variable(parentSense, SHT4X_HUMIDITY_VAR_NUM, + SHT4X_HUMIDITY_RESOLUTION, SHT4X_HUMIDITY_VAR_NAME, SHT4X_HUMIDITY_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new SensirionSHT4x_Humidity object. - * - * @note This must be tied with a parent SensirionSHT4x before it can be - * used. - */ - SensirionSHT4x_Humidity() - : Variable((uint8_t)SHT4X_HUMIDITY_VAR_NUM, - (uint8_t)SHT4X_HUMIDITY_RESOLUTION, SHT4X_HUMIDITY_VAR_NAME, - SHT4X_HUMIDITY_UNIT_NAME, SHT4X_HUMIDITY_DEFAULT_CODE) {} /** * @brief Destroy the SensirionSHT4x_Humidity object - no action needed. */ - ~SensirionSHT4x_Humidity() {} + ~SensirionSHT4x_Humidity() override = default; }; @@ -367,25 +362,14 @@ class SensirionSHT4x_Temp : public Variable { explicit SensirionSHT4x_Temp(SensirionSHT4x* parentSense, const char* uuid = "", const char* varCode = SHT4X_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)SHT4X_TEMP_VAR_NUM, - (uint8_t)SHT4X_TEMP_RESOLUTION, SHT4X_TEMP_VAR_NAME, - SHT4X_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new SensirionSHT4x_Temp object. - * - * @note This must be tied with a parent SensirionSHT4x before it can be - * used. - */ - SensirionSHT4x_Temp() - : Variable((uint8_t)SHT4X_TEMP_VAR_NUM, (uint8_t)SHT4X_TEMP_RESOLUTION, - SHT4X_TEMP_VAR_NAME, SHT4X_TEMP_UNIT_NAME, - SHT4X_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, SHT4X_TEMP_VAR_NUM, SHT4X_TEMP_RESOLUTION, + SHT4X_TEMP_VAR_NAME, SHT4X_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the SensirionSHT4x_Temp object - no action needed. */ - ~SensirionSHT4x_Temp() {} + ~SensirionSHT4x_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_SENSIRIONSHT4X_H_ -// cSpell:ignore preconfigured +// cSpell:words preconfigured diff --git a/src/sensors/TEConnectivityMS5837.cpp b/src/sensors/TEConnectivityMS5837.cpp new file mode 100644 index 000000000..c21735e8b --- /dev/null +++ b/src/sensors/TEConnectivityMS5837.cpp @@ -0,0 +1,337 @@ +/** + * @file TEConnectivityMS5837.cpp + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * + * @brief Implements the TEConnectivityMS5837 class. + */ + +#include "TEConnectivityMS5837.h" + + +// Primary implementation constructor using uint8_t model +TEConnectivityMS5837::TEConnectivityMS5837(TwoWire* theI2C, int8_t powerPin, + uint8_t model, + uint8_t measurementsToAverage, + uint16_t overSamplingRatio, + float fluidDensity, + float airPressure) + : Sensor("TEConnectivityMS5837", MS5837_NUM_VARIABLES, + MS5837_WARM_UP_TIME_MS, MS5837_STABILIZATION_TIME_MS, + MS5837_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage, + MS5837_INC_CALC_VARIABLES), + MS5837_internal(theI2C != nullptr ? theI2C : &Wire), + _wire(theI2C != nullptr ? theI2C : &Wire), + _model(model), + _fluidDensity(fluidDensity), + _airPressure(airPressure), + _overSamplingRatio(overSamplingRatio) { + setMaxRetries(MS5837_DEFAULT_MEASUREMENT_RETRIES); +} + +// Delegating constructors +TEConnectivityMS5837::TEConnectivityMS5837(TwoWire* theI2C, int8_t powerPin, + MS5837Model model, + uint8_t measurementsToAverage, + uint16_t overSamplingRatio, + float fluidDensity, + float airPressure) + : TEConnectivityMS5837(theI2C, powerPin, static_cast(model), + measurementsToAverage, overSamplingRatio, + fluidDensity, airPressure) {} + +TEConnectivityMS5837::TEConnectivityMS5837(int8_t powerPin, uint8_t model, + uint8_t measurementsToAverage, + uint16_t overSamplingRatio, + float fluidDensity, + float airPressure) + : TEConnectivityMS5837(&Wire, powerPin, model, measurementsToAverage, + overSamplingRatio, fluidDensity, airPressure) {} + +TEConnectivityMS5837::TEConnectivityMS5837(int8_t powerPin, MS5837Model model, + uint8_t measurementsToAverage, + uint16_t overSamplingRatio, + float fluidDensity, + float airPressure) + : TEConnectivityMS5837(&Wire, powerPin, static_cast(model), + measurementsToAverage, overSamplingRatio, + fluidDensity, airPressure) {} + + +String TEConnectivityMS5837::getSensorName() { + auto modelEnum = static_cast(_model); + switch (modelEnum) { + case MS5837Model::MS5837_02BA: return F("TEConnectivityMS5837_02BA"); + case MS5837Model::MS5837_30BA: return F("TEConnectivityMS5837_30BA"); + case MS5837Model::MS5803_01BA: return F("TEConnectivityMS5803_01BA"); + default: return F("TEConnectivityMS5837_Unknown"); + } +} + + +String TEConnectivityMS5837::getSensorLocation() { + return F("I2C_0x76"); +} + + +bool TEConnectivityMS5837::setup() { + bool success = + Sensor::setup(); // this will set pin modes and the setup status bit + + // This sensor needs power for setup! + bool wasOn = checkPowerOn(); + if (!wasOn) { powerUp(); } + waitForWarmUp(); + + // Set the sensor model and initialize the sensor + success &= MS5837_internal.begin(_model); + + // Validate that the pressure range is reasonable for the sensor model and + // change the model if possible based on the pressure sensitivity read from + // the sensor. + if (success && validateAndCorrectModel()) { + // If the model was changed, we need to re-initialize the sensor with + // the new model. + success &= MS5837_internal.reset(_model); + } + + if (success) { + // Set the fluid density for depth calculations + MS5837_internal.setDensity(_fluidDensity); + } + + // Turn the power back off if it had been turned on + if (!wasOn) { powerDown(); } + + if (!success) { + MS_DBG(getSensorNameAndLocation(), F("Failed to initialize sensor")); + // Set the status error bit (bit 7) + setStatusBit(ERROR_OCCURRED); + // UN-set the set-up bit (bit 0) since setup failed! + clearStatusBit(SETUP_SUCCESSFUL); + } + + return success; +} + + +bool TEConnectivityMS5837::wake() { + // Run the parent wake function + if (!Sensor::wake()) return false; + + bool success = true; + // Re-initialize the sensor communication, if the sensor was powered down + if (_powerPin >= 0) { + success = MS5837_internal.begin(_model); + // There's no need to validate the model or change the fluid density or + // other parameters. Those are not affected by power cycling the sensor. + } + if (!success) { + MS_DBG(getSensorNameAndLocation(), + F("Wake failed - sensor re-initialization failed")); + // Set the status error bit (bit 7) + setStatusBit(ERROR_OCCURRED); + // Make sure that the wake time and wake success bit (bit 4) are + // unset + _millisSensorActivated = 0; + clearStatusBit(WAKE_SUCCESSFUL); + } + + return success; +} + + +bool TEConnectivityMS5837::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // Validate configuration parameters + if (_fluidDensity <= 0.1 || _fluidDensity > 5.0) { + MS_DBG(F("Invalid fluid density:"), _fluidDensity, + F("g/cm³. Expected range: (0.1-5.0]")); + return finalizeMeasurementAttempt(false); + } + if (_airPressure < 500.0 || _airPressure > 1200.0) { + MS_DBG(F("Invalid air pressure:"), _airPressure, + F("mBar. Expected range: 500-1200")); + return finalizeMeasurementAttempt(false); + } + + // Read values from the sensor - returns 0 on success + // Map oversampling ratio to OSR value and validate + int OSR = -1; + switch (_overSamplingRatio) { + case 256: OSR = 8; break; + case 512: OSR = 9; break; + case 1024: OSR = 10; break; + case 2048: OSR = 11; break; + case 4096: OSR = 12; break; + case 8192: OSR = 13; break; + default: + MS_DBG(F("Invalid oversampling ratio:"), _overSamplingRatio, + F(". Valid values: 256, 512, 1024, 2048, 4096, 8192")); + return finalizeMeasurementAttempt(false); + } + MS_DBG(F(" Requesting"), OSR, F("bit OSR (oversampling ratio:"), + _overSamplingRatio, F(")")); + + float temp = MS_INVALID_VALUE; + float press = MS_INVALID_VALUE; + float depth = MS_INVALID_VALUE; + float alt = MS_INVALID_VALUE; + + // Read values from the sensor - returns 0 on success + int read_return = MS5837_internal.read(OSR); + bool success = read_return == 0; + if (success) { + // Get temperature in Celsius + temp = MS5837_internal.getTemperature(); + + // Get pressure in millibar + press = MS5837_internal.getPressure(); + } else { + MS_DBG(F(" Read failed, error:"), MS5837_internal.getLastError(), + F("Return value from read():"), read_return); + return finalizeMeasurementAttempt(false); + } + + // Validate the readings + float maxPressure = 0.0f; + switch (_model) { + case static_cast(MS5837Model::MS5803_01BA): + maxPressure = 1000.0f; + break; // 1 bar = 1000 mbar + case static_cast(MS5837Model::MS5837_02BA): + maxPressure = 2000.0f; + break; // 2 bar = 2000 mbar + case static_cast(MS5837Model::MS5837_30BA): + maxPressure = 30000.0f; + break; // 30 bar = 30000 mbar + default: maxPressure = 30000.0f; break; + } + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Pressure returns 0 when disconnected, which is highly unlikely to be + // a real value. + // Pressure range depends on the model; allow 5% over max pressure + if (!isnan(press) && press > 0.0f && press <= maxPressure * 1.05f) { + MS_DBG(F(" Pressure:"), press); + verifyAndAddMeasurementResult(MS5837_PRESSURE_VAR_NUM, press); + } else if (!isnan(press)) { + MS_DBG(F(" Pressure out of range:"), press); + success = false; + } else { + MS_DBG(F(" Pressure is NaN")); + success = false; + } + + // Temperature Range is -40°C to +85°C + if (!isnan(temp) && temp >= -40.0f && temp <= 85.0f) { + MS_DBG(F(" Temperature:"), temp); + verifyAndAddMeasurementResult(MS5837_TEMP_VAR_NUM, temp); + } else if (!isnan(temp)) { + MS_DBG(F(" Temperature out of range:"), temp); + success = false; + } else { + MS_DBG(F(" Temperature is NaN")); + success = false; + } + + if (success) { + // Calculate and store depth and altitude only if input temperature and + // pressure are valid + // If the temperature and pressure are valid - and we've already checked + // for reasonable air pressure and fluid density, then the altitude and + // depth will be valid. + + // Calculate altitude in meters using configured air pressure + alt = MS5837_internal.getAltitude(_airPressure); + MS_DBG(F(" Altitude:"), alt); + verifyAndAddMeasurementResult(MS5837_ALTITUDE_VAR_NUM, alt); + + // Calculate depth in meters + // Note: fluidDensity is set in the MS5837_internal object at setup and + // used in the getDepth() function. The fluidDensity is only set in the + // constructor and further setters and getters are not provided, so + // there's no reason to re-pass the value to the internal object here. + depth = MS5837_internal.getDepth(); + MS_DBG(F(" Depth:"), depth); + verifyAndAddMeasurementResult(MS5837_DEPTH_VAR_NUM, depth); + } else { + MS_DBG( + F(" Invalid readings, skipping depth and altitude calculations")); + } + + // Return success value when finished + return finalizeMeasurementAttempt(success); +} + + +bool TEConnectivityMS5837::validateAndCorrectModel() { + MS_DBG(F("Attempting to read SENS_T1 from PROM of sensor at address"), + String(MS5837_internal.getAddress(), HEX)); + + uint8_t address = MS5837_internal.getAddress(); + + // Verify I2C connectivity with a lightweight probe + _wire->beginTransmission(address); + if (_wire->endTransmission() != 0) { + MS_DBG(F(" I2C communication failed at 0x"), String(address, HEX)); + return false; // can't change the model since we can't communicate with + // the sensor at all + } + + // MS5837_CMD_READ_PROM = 0xA0 + // Read SENS_T1 from PROM 1 [0xA0 + (1*2)] + _wire->beginTransmission(address); + _wire->write(0xA0 + (1 * 2)); + if (_wire->endTransmission() != 0) { + MS_DBG(F("Failed to request SENS_T1 from PROM. Unable to validate " + "pressure range.")); + return false; // can't change the model since we can't request the + // calibration value + } + + uint8_t length = 2; + if (_wire->requestFrom(address, length) != length) { + MS_DBG(F("Failed to retrieve SENS_T1 from PROM. Unable to validate " + "pressure range.")); + return false; // can't change the model since we can't retrieve the + // calibration value + } + uint8_t highByte = _wire->read(); + uint8_t lowByte = _wire->read(); + uint16_t SENS_T1 = (highByte << 8) | lowByte; + MS_DBG(F("SENS_T1 value:"), SENS_T1); + + // Values from + // https://github.com/ArduPilot/ardupilot/pull/29122#issuecomment-2877269114 + const uint16_t MS5837_02BA_MAX_SENSITIVITY = 49000; + const uint16_t MS5837_02BA_30BA_SEPARATION = 37000; + const uint16_t MS5837_30BA_MIN_SENSITIVITY = 26000; + // PROM Word 1 represents the sensor's pressure sensitivity calibration + // NOTE: The calibrated pressure sensitivity value (SENS_T1) is **not** the + // same as the as the pressure range from the datasheet! + // Set _model according to the experimental pressure sensitivity thresholds + if (SENS_T1 > MS5837_30BA_MIN_SENSITIVITY && + SENS_T1 < MS5837_02BA_30BA_SEPARATION && + _model == static_cast(MS5837Model::MS5837_02BA)) { + MS_DBG( + F("SENS_T1 value indicates 30BA model, but model is set to 02BA")); + MS_DBG(F("Changing model to 30BA")); + _model = static_cast(MS5837Model::MS5837_30BA); + return true; // Return true to indicate that the model was changed + } else if (SENS_T1 > MS5837_02BA_30BA_SEPARATION && + SENS_T1 < MS5837_02BA_MAX_SENSITIVITY && + _model == static_cast(MS5837Model::MS5837_30BA)) { + MS_DBG( + F("SENS_T1 value indicates 02BA model, but model is set to 30BA")); + MS_DBG(F("Setting model to 02BA")); + _model = static_cast(MS5837Model::MS5837_02BA); + return true; // Return true to indicate that the model was changed + } + return false; // Model was not changed +} diff --git a/src/sensors/TEConnectivityMS5837.h b/src/sensors/TEConnectivityMS5837.h new file mode 100644 index 000000000..7cec6301d --- /dev/null +++ b/src/sensors/TEConnectivityMS5837.h @@ -0,0 +1,700 @@ +/** + * @file TEConnectivityMS5837.h + * @copyright Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino. + * This library is published under the BSD-3 license. + * @author Sara Geleskie Damiano + * + * @brief Contains the TEConnectivityMS5837 sensor subclass and the variable + * subclasses TEConnectivityMS5837_Temp, TEConnectivityMS5837_Pressure, + * TEConnectivityMS5837_Depth, and TEConnectivityMS5837_Altitude. + * + * These are for the TE Connectivity MS5837 pressure sensor. This sensor is + * commonly deployed in Blue Robotics Bar02/Bar30 pressure sensors for + * underwater/high-pressure applications and is commonly used for depth + * measurement. Rob Tillaart maintains the MS5837 Arduino library for + * communication with this sensor. + * + * This depends on the Rob Tillaart MS5837 library at + * https://github.com/RobTillaart/MS5837 + */ +/* clang-format off */ +/** + * @defgroup sensor_ms5837 TE Connectivity MS5837 + * Classes for the TE Connectivity MS5837 digital pressure sensor. + * + * @ingroup the_sensors + * + * @tableofcontents + * @m_footernavigation + * + * @section sensor_ms5837_intro Introduction + * + * The TE Connectivity MS5837 is a miniature digital pressure sensor designed for + * underwater and high-pressure applications. It is commonly deployed in Blue Robotics + * Bar02/Bar30 pressure sensors and is frequently used for depth measurement. + * The MS5837 comes in several different pressure ranges, with 2 bar and 30 bar being + * the most common for underwater applications. The sensor communicates via I2C at + * address 0x76. These sensors should be attached to a 1.7-3.6V power source and the + * power supply to the sensor can be stopped between measurements. + * + * @warning The I2C address (0x76) is the same as some configurations of the + * Measurement Specialties MS5803, Bosch BME280, BMP388, and BMP390 sensors! + * If you are also using one of those sensors, make sure that the address for + * that sensor does not conflict with the address of this sensor. + * + * @note This sensor supports both primary and secondary hardware I2C instances + * through TwoWire* constructor parameters. Software I2C is not supported. + * + * The lower level communication between the mcu and the MS5837 is handled by the + * [Rob Tillaart MS5837 library](https://github.com/RobTillaart/MS5837). + * + * @section sensor_ms5837_datasheet Sensor Datasheet + * + * Documentation for the sensor can be found at: + * https://www.te.com/en/product-CAT-BLPS0017.html + * + * Blue Robotics deployable versions: + * https://bluerobotics.com/store/sensors-sonars-cameras/sensors/bar02-sensor-r1-rp/ + * https://bluerobotics.com/store/sensors-sonars-cameras/sensors/bar30-sensor-r1/ + * + * @section sensor_ms5837_flags Build flags + * - ```-D MS5837_DEFAULT_MEASUREMENT_RETRIES=5``` + * - Changes the default number of measurement retries when a measurement + * fails. The default value is 5. Higher values provide better reliability but + * may increase total measurement time on sensor failures. + * - ```-D MS5837_DEFAULT_FLUID_DENSITY=0.99802f``` + * - Changes the default fluid density used for depth calculations. The + * default value is for water at 20°C. For seawater, use approximately 1.025f. + * For other fluids, consult density tables and enter the density in grams per + * cm³. + * - ```-D MS_SEA_LEVEL_PRESSURE_HPA=1013.25f``` + * - Changes the default air pressure used for altitude and depth + * calculations. The default value is standard atmospheric pressure at sea level + * (1013.25 mBar). Adjust based on local atmospheric conditions or altitude for + * more accurate depth measurements. + * - The same sea level pressure flag is used for BMP3xx, BME280, and + * MS5837 sensors. + * + * @section sensor_ms5837_ctor Sensor Constructor + * {{ @ref TEConnectivityMS5837::TEConnectivityMS5837 }} + * + * ___ + * @section sensor_ms5837_examples Example Code + * The TE Connectivity MS5837 is used in the @menulink{te_connectivity_ms5837} example. + * + * @menusnip{te_connectivity_ms5837} + */ +/* clang-format on */ + +// Header Guards +#ifndef SRC_SENSORS_TECONNECTIVITYMS5837_H_ +#define SRC_SENSORS_TECONNECTIVITYMS5837_H_ + +// Include the library config before anything else +#include "ModSensorConfig.h" + +// Include the debugging config +#include "ModSensorDebugConfig.h" + +// Define the print label[s] for the debugger +#ifdef MS_TECONNECTIVITYMS5837_DEBUG +#define MS_DEBUGGING_STD "TEConnectivityMS5837" +#endif + +// Include the debugger +#include "ModSensorDebugger.h" +// Undefine the debugger label[s] +#undef MS_DEBUGGING_STD + +// Include other in-library and external dependencies +#include "VariableBase.h" +#include "SensorBase.h" +#include + +/** @ingroup sensor_ms5837 */ +/**@{*/ + +/** + * @anchor sensor_ms5837_config + * @name Sensor Configuration + * Build-time configuration for the MS5837 + */ +/**@{*/ +#if !defined(MS5837_DEFAULT_MEASUREMENT_RETRIES) || defined(DOXYGEN) +/** + * @brief Default number of measurement retries + * + * The default number of times to retry a measurement when it fails. Higher + * values provide better reliability but may increase total measurement time on + * sensor failures. This can be set at runtime for individual sensors and the + * default can be overridden at compile time with `-D + * MS5837_DEFAULT_MEASUREMENT_RETRIES=value` + */ +#define MS5837_DEFAULT_MEASUREMENT_RETRIES 5 +#endif + +// Static assert to validate measurement retries is reasonable +static_assert(MS5837_DEFAULT_MEASUREMENT_RETRIES >= 0 && + MS5837_DEFAULT_MEASUREMENT_RETRIES <= 20, + "MS5837_DEFAULT_MEASUREMENT_RETRIES must be between 0 and 20 " + "(reasonable measurement retry range)"); + +#if !defined(MS5837_DEFAULT_FLUID_DENSITY) || defined(DOXYGEN) +/** + * @brief Default fluid density for depth calculations (grams/cm³) + * + * Water at 20°C = 0.99802 g/cm³. For seawater, use approximately 1.025 g/cm³. + * For other fluids, consult density tables and enter the density in grams per + * cm³. This can be overridden at compile time with -D + * MS5837_DEFAULT_FLUID_DENSITY=value + */ +#define MS5837_DEFAULT_FLUID_DENSITY 0.99802f +#endif + +// Static assert to validate fluid density is reasonable +static_assert(MS5837_DEFAULT_FLUID_DENSITY > 0.1f && + MS5837_DEFAULT_FLUID_DENSITY <= 5.0f, + "MS5837_DEFAULT_FLUID_DENSITY must be between 0.1 and 5.0 g/cm³ " + "(reasonable fluid density range)"); + +#if !defined(MS5837_DEFAULT_OVERSAMPLING_RATIO) || defined(DOXYGEN) +/** + * @brief Default oversampling ratio for pressure and temperature measurements + * + * Higher oversampling ratios provide better resolution and noise reduction but + * increase measurement time. Valid values are: 256, 512, 1024, 2048, 4096, + * 8192. This can be overridden at compile time with + * `-D MS5837_DEFAULT_OVERSAMPLING_RATIO=value` + * + * @note The 13-bit (8192) sampling mode is not available on the MS5803. + */ +#define MS5837_DEFAULT_OVERSAMPLING_RATIO 4096 +#endif + +// Static assert to validate oversampling ratio is one of the valid values +static_assert( + MS5837_DEFAULT_OVERSAMPLING_RATIO == 256 || + MS5837_DEFAULT_OVERSAMPLING_RATIO == 512 || + MS5837_DEFAULT_OVERSAMPLING_RATIO == 1024 || + MS5837_DEFAULT_OVERSAMPLING_RATIO == 2048 || + MS5837_DEFAULT_OVERSAMPLING_RATIO == 4096 || + MS5837_DEFAULT_OVERSAMPLING_RATIO == 8192, + "MS5837_DEFAULT_OVERSAMPLING_RATIO must be one of: 256, 512, 1024, " + "2048, 4096, 8192 (valid MS5837 oversampling ratios)"); +/**@}*/ + +/** + * @anchor sensor_ms5837_var_counts + * @name Sensor Variable Counts + * The number of variables that can be returned by the MS5837 + */ +/**@{*/ +/// @brief Sensor::_numReturnedValues; the MS5837 can report 4 values. +#define MS5837_NUM_VARIABLES 4 +/// @brief Sensor::_incCalcValues; we calculate depth and altitude values. +#define MS5837_INC_CALC_VARIABLES 2 +/**@}*/ + +/** + * @anchor sensor_ms5837_timing + * @name Sensor Timing + * The sensor timing for a Blue Robotics MS5837 + */ +/**@{*/ +/// @brief Sensor::_warmUpTime_ms; the MS5837 warms up in 10ms. +#define MS5837_WARM_UP_TIME_MS 10 +/// @brief Sensor::_stabilizationTime_ms; the MS5837 is stable as soon as it +/// warms up (0ms stabilization). +#define MS5837_STABILIZATION_TIME_MS 0 +/** + * @brief Sensor::_measurementTime_ms; the MS5837 takes 20ms to complete a + * measurement. + * - Sensor takes about 0.5 / 1.1 / 2.1 / 4.1 / 8.22 / 16.44 ms to respond + * at oversampling ratios: 256 / 512 / 1024 / 2048 / 4096 / 8192, respectively. + * + * @note The 13-bit (8192) sampling mode is not available on the MS5803. + */ +#define MS5837_MEASUREMENT_TIME_MS 20 +/**@}*/ + +/** + * @anchor sensor_ms5837_temp + * @name Temperature + * The temperature variable from a TE Connectivity MS5837 + * - Range is -40°C to +85°C + * - Accuracy is ±2.0°C + * + * {{ @ref TEConnectivityMS5837_Temp::TEConnectivityMS5837_Temp }} + */ +/**@{*/ +/// @brief Minimum temperature in degrees Celsius. +#define MS5837_TEMP_MIN_C -40.0 +/// @brief Maximum temperature in degrees Celsius. +#define MS5837_TEMP_MAX_C 85.0 +/// @brief Decimal places in string representation; temperature should have 2 - +/// resolution is <0.01°C. +#define MS5837_TEMP_RESOLUTION 2 +/// @brief Sensor variable number; temperature is stored in sensorValues[0]. +#define MS5837_TEMP_VAR_NUM 0 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "temperature" +#define MS5837_TEMP_VAR_NAME "temperature" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); +/// "degreeCelsius" (°C) +#define MS5837_TEMP_UNIT_NAME "degreeCelsius" +/// @brief Default variable short code; "TEConnectivityMS5837Temp" +#define MS5837_TEMP_DEFAULT_CODE "TEConnectivityMS5837Temp" +/**@}*/ + +/** + * @anchor sensor_ms5837_pressure + * @name Pressure + * The pressure variable from a TE Connectivity MS5837 + * - Range depends on sensor model: + * - Bar02: 0 to 2 bar (0 to 2000 mbar) + * - Bar30: 0 to 30 bar (0 to 30000 mbar) + * - Accuracy: + * - Bar02: ±1.5mbar + * - Bar30: ±20mbar + * - Resolution is: (at oversampling ratios: 256 / 512 / 1024 / 2048 / + * 4096, respectively)) + * - Bar02: 0.13 / 0.084 / 0.054 / 0.036 / 0.024 mbar + * - Bar30: 1 / 0.6 / 0.4 / 0.3 / 0.2 mbar (where 1 mbar = 100 pascals) + * - @m_span{m-dim}@ref #MS5837_PRESSURE_RESOLUTION = 3@m_endspan + * + * {{ @ref TEConnectivityMS5837_Pressure::TEConnectivityMS5837_Pressure }} + */ +/**@{*/ +/// @brief Minimum pressure in millibar. +#define MS5837_PRESSURE_MIN_MBAR 0.0 +/// @brief Maximum pressure in millibar. +#define MS5837_PRESSURE_MAX_MBAR 3000.0 +/// @brief Decimal places in string representation; pressure should have 3. +#define MS5837_PRESSURE_RESOLUTION 3 +/// @brief Sensor variable number; pressure is stored in sensorValues[1]. +#define MS5837_PRESSURE_VAR_NUM 1 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "barometricPressure" +#define MS5837_PRESSURE_VAR_NAME "barometricPressure" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "millibar" +#define MS5837_PRESSURE_UNIT_NAME "millibar" +/// @brief Default variable short code; "TEConnectivityMS5837Pressure" +#define MS5837_PRESSURE_DEFAULT_CODE "TEConnectivityMS5837Pressure" +/**@}*/ + +/** + * @anchor sensor_ms5837_depth + * @name Depth + * The depth variable calculated from a TE Connectivity MS5837 + * - Calculated from pressure using the configured fluid density + * - Accuracy depends on pressure sensor accuracy and fluid density accuracy + * - Resolution is 1mm (0.001m) + * + * {{ @ref TEConnectivityMS5837_Depth::TEConnectivityMS5837_Depth }} + */ +/**@{*/ +/// @brief Decimal places in string representation; depth should have 3. +#define MS5837_DEPTH_RESOLUTION 3 +/// @brief Sensor variable number; depth is stored in sensorValues[2]. +#define MS5837_DEPTH_VAR_NUM 2 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "waterDepth" +#define MS5837_DEPTH_VAR_NAME "waterDepth" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "meter" +#define MS5837_DEPTH_UNIT_NAME "meter" +/// @brief Default variable short code; "TEConnectivityMS5837Depth" +#define MS5837_DEPTH_DEFAULT_CODE "TEConnectivityMS5837Depth" +/**@}*/ + +/** + * @anchor sensor_ms5837_altitude + * @name Altitude + * The altitude variable calculated from a TE Connectivity MS5837 + * - Calculated from barometric pressure using standard atmosphere equations + * - Accuracy depends on pressure sensor accuracy and reference air pressure + * - Resolution is 0.01m + * + * {{ @ref TEConnectivityMS5837_Altitude::TEConnectivityMS5837_Altitude }} + */ +/**@{*/ +/// @brief Decimal places in string representation; altitude should have 2. +#define MS5837_ALTITUDE_RESOLUTION 2 +/// @brief Sensor variable number; altitude is stored in sensorValues[3]. +#define MS5837_ALTITUDE_VAR_NUM 3 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "altitude" +#define MS5837_ALTITUDE_VAR_NAME "altitude" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "meter" +#define MS5837_ALTITUDE_UNIT_NAME "meter" +/// @brief Default variable short code; "TEConnectivityMS5837Altitude" +#define MS5837_ALTITUDE_DEFAULT_CODE "TEConnectivityMS5837Altitude" +/**@}*/ + +/** + * @brief Supported MS5837/MS5803 sensor models + * + * These enum values correspond to the **math model** values used in the Rob + * Tillaart MS5837 library. They are **not** equivalent to the "type" values + * defined in that library, which are not used in the MS5837 class. The math + * model values are used to set the correct calibration coefficients and + * calculations for the different sensor models, which have different pressure + * ranges and sensitivities. + * + * @ingroup sensor_ms5837 + */ +enum class MS5837Model : uint8_t { + MS5837_30BA = 0, ///< MS5837-30BA: 30 bar range sensor + MS5837_02BA = 1, ///< MS5837-02BA: 2 bar range sensor + MS5803_01BA = 2 ///< MS5803-01BA: 1 bar range sensor +}; + +/* clang-format off */ +/** + * @brief The Sensor sub-class for the + * [TE Connectivity MS5837 sensor](@ref sensor_ms5837). + * + * @ingroup sensor_ms5837 + */ +/* clang-format on */ +class TEConnectivityMS5837 : public Sensor { + public: + /** + * @brief Construct a new TEConnectivityMS5837 object using the default + * Hardware Wire instance. + * + * @param powerPin The pin on the mcu controlling power to the MS5837 + * Use -1 if it is continuously powered. + * - The MS5837 requires a 1.7 - 3.6V power source + * @param model The model of MS5837 sensor. + * - 0 for 30 bar range sensors (MS5837-30BA) + * - 1 for 2 bar range sensors (MS5837-02BA) + * - 2 for 1 bar range sensors (MS5803-01BA) + * @param measurementsToAverage The number of measurements to take and + * average before giving a "final" result from the sensor; optional with a + * default value of 1. + * @param overSamplingRatio The oversampling ratio for pressure and + * temperature measurements; optional with default value from + * MS5837_DEFAULT_OVERSAMPLING_RATIO. Valid values: 256, 512, 1024, 2048, + * 4096, 8192. + * @param fluidDensity The density of the fluid for depth calculations + * (grams/cm³); optional with default value from + * MS5837_DEFAULT_FLUID_DENSITY. + * @param airPressure The air pressure for altitude/depth calculations + * (mBar); optional with default value from MS_SEA_LEVEL_PRESSURE_HPA. + * + * @warning This can be used for the MS5803-01BA sensor, but **only** for + * that exact model of MS5803. For any other MS5803 model, use the + * MeasSpecMS5803 class instead of this class. + */ + explicit TEConnectivityMS5837( + int8_t powerPin, uint8_t model, uint8_t measurementsToAverage = 1, + uint16_t overSamplingRatio = MS5837_DEFAULT_OVERSAMPLING_RATIO, + float fluidDensity = MS5837_DEFAULT_FLUID_DENSITY, + float airPressure = MS_SEA_LEVEL_PRESSURE_HPA); + /** + * @brief Construct a new TEConnectivityMS5837 object using a secondary + * *hardware* I2C instance. + * + * @copydetails TEConnectivityMS5837::TEConnectivityMS5837(int8_t, uint8_t, + * uint8_t, uint16_t, float, float) + * + * @param theI2C A TwoWire instance for I2C communication. Due to the + * limitations of the Arduino core, only a hardware I2C instance can be + * used. For an AVR board, there is only one I2C instance possible and this + * form of the constructor should not be used. For a SAMD board, this can + * be used if a secondary I2C port is created on one of the extra SERCOMs. + */ + TEConnectivityMS5837( + TwoWire* theI2C, int8_t powerPin, uint8_t model, + uint8_t measurementsToAverage = 1, + uint16_t overSamplingRatio = MS5837_DEFAULT_OVERSAMPLING_RATIO, + float fluidDensity = MS5837_DEFAULT_FLUID_DENSITY, + float airPressure = MS_SEA_LEVEL_PRESSURE_HPA); + + /** + * @brief Construct a new TEConnectivityMS5837 object using the default + * Hardware Wire instance with enum model type. + * + * @param powerPin The pin on the mcu controlling power to the MS5837 + * Use -1 if it is continuously powered. + * - The MS5837 requires a 1.7 - 3.6V power source + * @param model The model of MS5837 sensor using enum type. + * @param measurementsToAverage The number of measurements to take and + * average before giving a "final" result from the sensor; optional with a + * default value of 1. + * @param overSamplingRatio The oversampling ratio for pressure and + * temperature measurements; optional with default value from + * MS5837_DEFAULT_OVERSAMPLING_RATIO. Valid values: 256, 512, 1024, 2048, + * 4096, 8192. + * @param fluidDensity The density of the fluid for depth calculations + * (grams/cm³); optional with default value from + * MS5837_DEFAULT_FLUID_DENSITY. + * @param airPressure The air pressure for altitude/depth calculations + * (mBar); optional with default value from MS_SEA_LEVEL_PRESSURE_HPA. + * + * @warning This can be used for the MS5803-01BA sensor, but **only** for + * that exact model of MS5803. For any other MS5803 model, use the + * MeasSpecMS5803 class instead of this class. + */ + explicit TEConnectivityMS5837( + int8_t powerPin, MS5837Model model, uint8_t measurementsToAverage = 1, + uint16_t overSamplingRatio = MS5837_DEFAULT_OVERSAMPLING_RATIO, + float fluidDensity = MS5837_DEFAULT_FLUID_DENSITY, + float airPressure = MS_SEA_LEVEL_PRESSURE_HPA); + /** + * @brief Construct a new TEConnectivityMS5837 object using a secondary + * *hardware* I2C instance. + * + * @copydetails TEConnectivityMS5837::TEConnectivityMS5837(int8_t, + * MS5837Model, uint8_t, uint16_t, float, float) + * + * @param theI2C A TwoWire instance for I2C communication. Due to the + * limitations of the Arduino core, only a hardware I2C instance can be + * used. For an AVR board, there is only one I2C instance possible and this + * form of the constructor should not be used. For a SAMD board, this can + * be used if a secondary I2C port is created on one of the extra SERCOMs. + */ + TEConnectivityMS5837( + TwoWire* theI2C, int8_t powerPin, MS5837Model model, + uint8_t measurementsToAverage = 1, + uint16_t overSamplingRatio = MS5837_DEFAULT_OVERSAMPLING_RATIO, + float fluidDensity = MS5837_DEFAULT_FLUID_DENSITY, + float airPressure = MS_SEA_LEVEL_PRESSURE_HPA); + /** + * @brief Destroy the TEConnectivityMS5837 object + */ + ~TEConnectivityMS5837() override = default; + + /** + * @brief Do any one-time preparations needed before the sensor will be able + * to take readings. + * + * This sets the pin modes for #_powerPin and I2C, initializes the MS5837, + * and updates the #_sensorStatus. The MS5837 must be powered for setup. + * + * @return True if the setup was successful. + */ + bool setup() override; + + /** + * @brief Wake the sensor and re-establish communication. + * + * This re-runs the MS5837_internal begin method to re-establish I2C + * communication, re-read the sensor calibration constants, and ensure that + * the sensor itself has loaded the calibration PROM into its internal + * register. This is required after every power cycle of the sensor. + * + * @return True if the wake was successful. + */ + bool wake() override; + + String getSensorName() override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; + + private: + /** + * @brief Private internal MS5837 object instance. + */ + MS5837 MS5837_internal; + /** + * @brief An internal reference to the hardware Wire instance. + */ + TwoWire* _wire; // Hardware Wire + /** + * @brief The model of the MS5837. + * + * @note This is stored as a uint8_t for easier comparison with the math + * model values used in the Rob Tillaart MS5837 library. + */ + uint8_t _model; + /** + * @brief The fluid density for depth calculations (grams/cm³). + */ + float _fluidDensity; + /** + * @brief The air pressure for altitude/depth calculations (mBar). + */ + float _airPressure; + /** + * @brief The oversampling ratio for pressure and temperature measurements. + */ + uint16_t _overSamplingRatio; + + /** + * @brief Validates the configured sensor model against hardware and + * corrects it if a mismatch is detected. + * + * This method reads the SENS_T1 calibration value from the sensor's PROM + * and compares it against known sensitivity thresholds to determine if the + * configured model matches the actual hardware. If a mismatch is detected + * and the correct model can be determined, the model configuration is + * automatically updated. + * + * @note This will only change the configuration if a valid SENS_T1 value is + * returned, one of the MS5837 models is currently configured, and the + * SENS_T1 value indicates the other MS5837 model based on experimentally + * derived sensitivity thresholds. If the SENS_T1 cannot be retrieved, the + * value is out of the expected range for both models, or a MS5803 is + * configured, no changes will be made. + * + * The thresholds used for determining whether to change the model + * configuration are taken from the Blue Robotics MS5837 library and are + * based on experimental results posted here: + * https://github.com/ArduPilot/ardupilot/pull/29122#issuecomment-2877269114 + * + * @return True if the model value was changed based on the returned SENS_T1 + * value, false otherwise. + */ + bool validateAndCorrectModel(); +}; + + +/* clang-format off */ +/** + * @brief The Variable sub-class used for the + * [temperature output](@ref sensor_ms5837_temp) from a + * [TE Connectivity MS5837 digital pressure sensor](@ref sensor_ms5837). + * + * @ingroup sensor_ms5837 + */ +/* clang-format on */ +class TEConnectivityMS5837_Temp : public Variable { + public: + /** + * @brief Construct a new TEConnectivityMS5837_Temp object. + * + * @param parentSense The parent TEConnectivityMS5837 providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TEConnectivityMS5837Temp". + */ + explicit TEConnectivityMS5837_Temp( + TEConnectivityMS5837* parentSense, const char* uuid = "", + const char* varCode = MS5837_TEMP_DEFAULT_CODE) + : Variable(parentSense, MS5837_TEMP_VAR_NUM, MS5837_TEMP_RESOLUTION, + MS5837_TEMP_VAR_NAME, MS5837_TEMP_UNIT_NAME, varCode, uuid) { + } + /** + * @brief Destroy the TEConnectivityMS5837_Temp object - no action needed. + */ + ~TEConnectivityMS5837_Temp() override = default; +}; + + +/* clang-format off */ +/** + * @brief The Variable sub-class used for the + * [pressure output](@ref sensor_ms5837_pressure) from a + * [TE Connectivity MS5837 digital pressure sensor](@ref sensor_ms5837). + * + * @ingroup sensor_ms5837 + */ +/* clang-format on */ +class TEConnectivityMS5837_Pressure : public Variable { + public: + /** + * @brief Construct a new TEConnectivityMS5837_Pressure object. + * + * @param parentSense The parent TEConnectivityMS5837 providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TEConnectivityMS5837Pressure". + */ + explicit TEConnectivityMS5837_Pressure( + TEConnectivityMS5837* parentSense, const char* uuid = "", + const char* varCode = MS5837_PRESSURE_DEFAULT_CODE) + : Variable(parentSense, MS5837_PRESSURE_VAR_NUM, + MS5837_PRESSURE_RESOLUTION, MS5837_PRESSURE_VAR_NAME, + MS5837_PRESSURE_UNIT_NAME, varCode, uuid) {} + /** + * @brief Destroy the TEConnectivityMS5837_Pressure object - no action + * needed. + */ + ~TEConnectivityMS5837_Pressure() override = default; +}; + + +/* clang-format off */ +/** + * @brief The Variable sub-class used for the + * [depth output](@ref sensor_ms5837_depth) calculated from a + * [TE Connectivity MS5837 digital pressure sensor](@ref sensor_ms5837). + * + * @ingroup sensor_ms5837 + */ +/* clang-format on */ +class TEConnectivityMS5837_Depth : public Variable { + public: + /** + * @brief Construct a new TEConnectivityMS5837_Depth object. + * + * @param parentSense The parent TEConnectivityMS5837 providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TEConnectivityMS5837Depth". + */ + explicit TEConnectivityMS5837_Depth( + TEConnectivityMS5837* parentSense, const char* uuid = "", + const char* varCode = MS5837_DEPTH_DEFAULT_CODE) + : Variable(parentSense, MS5837_DEPTH_VAR_NUM, MS5837_DEPTH_RESOLUTION, + MS5837_DEPTH_VAR_NAME, MS5837_DEPTH_UNIT_NAME, varCode, + uuid) {} + /** + * @brief Destroy the TEConnectivityMS5837_Depth object - no action needed. + */ + ~TEConnectivityMS5837_Depth() override = default; +}; + + +/* clang-format off */ +/** + * @brief The Variable sub-class used for the + * [altitude output](@ref sensor_ms5837_altitude) calculated from a + * [TE Connectivity MS5837 digital pressure sensor](@ref sensor_ms5837). + * + * @ingroup sensor_ms5837 + */ +/* clang-format on */ +class TEConnectivityMS5837_Altitude : public Variable { + public: + /** + * @brief Construct a new TEConnectivityMS5837_Altitude object. + * + * @param parentSense The parent TEConnectivityMS5837 providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TEConnectivityMS5837Altitude". + */ + explicit TEConnectivityMS5837_Altitude( + TEConnectivityMS5837* parentSense, const char* uuid = "", + const char* varCode = MS5837_ALTITUDE_DEFAULT_CODE) + : Variable(parentSense, MS5837_ALTITUDE_VAR_NUM, + MS5837_ALTITUDE_RESOLUTION, MS5837_ALTITUDE_VAR_NAME, + MS5837_ALTITUDE_UNIT_NAME, varCode, uuid) {} + /** + * @brief Destroy the TEConnectivityMS5837_Altitude object - no action + * needed. + */ + ~TEConnectivityMS5837_Altitude() override = default; +}; +/**@}*/ +#endif // SRC_SENSORS_TECONNECTIVITYMS5837_H_ diff --git a/src/sensors/TIADS1x15.cpp b/src/sensors/TIADS1x15.cpp index 0f4bdc3fe..a3e12dae6 100644 --- a/src/sensors/TIADS1x15.cpp +++ b/src/sensors/TIADS1x15.cpp @@ -13,100 +13,443 @@ #include "TIADS1x15.h" -#include +#include -// The constructor - need the power pin the data pin, and gain if non standard -TIADS1x15::TIADS1x15(int8_t powerPin, uint8_t adsChannel, float gain, - uint8_t i2cAddress, uint8_t measurementsToAverage) - : Sensor("TIADS1x15", TIADS1X15_NUM_VARIABLES, TIADS1X15_WARM_UP_TIME_MS, - TIADS1X15_STABILIZATION_TIME_MS, TIADS1X15_MEASUREMENT_TIME_MS, - powerPin, -1, measurementsToAverage, TIADS1X15_INC_CALC_VARIABLES), - _adsChannel(adsChannel), - _gain(gain), - _i2cAddress(i2cAddress) {} -// Destructor -TIADS1x15::~TIADS1x15() {} +// ============================================================================ +// TIADS1x15Reader Constructors +// ============================================================================ + +// Constructor with TwoWire instance +TIADS1x15Reader::TIADS1x15Reader(TwoWire* theI2C, float voltageMultiplier, + adsGain_t adsGain, uint8_t i2cAddress, + float adsSupplyVoltage, uint16_t adsDataRate) + : AnalogVoltageReader(voltageMultiplier, adsSupplyVoltage), + _wire(theI2C != nullptr ? theI2C : &Wire), + _i2cAddress(i2cAddress), + _adsGain(adsGain), + _adsDataRate(adsDataRate) { + // Clamp supply voltage to valid ADS1x15 range: 0.0V to 5.5V per datasheet + // NOTE: This clamp is intentionally silent — Serial/MS_DBG is NOT safe to + // call during construction (the Serial object may not be initialized yet on + // Arduino targets). Use setSupplyVoltage() at runtime for logged clamping. + if (_supplyVoltage < 0.0f) { + _supplyVoltage = 0.0f; + } else if (_supplyVoltage > 5.5f) { + _supplyVoltage = 5.5f; + } + // ADS initialization is deferred to begin() function for safe I2C + // communication +} + +// Constructor using default Wire instance +TIADS1x15Reader::TIADS1x15Reader(float voltageMultiplier, adsGain_t adsGain, + uint8_t i2cAddress, float adsSupplyVoltage, + uint16_t adsDataRate) + : TIADS1x15Reader(&Wire, voltageMultiplier, adsGain, i2cAddress, + adsSupplyVoltage, adsDataRate) {} -String TIADS1x15::getSensorLocation(void) { +// ============================================================================ +// TIADS1x15Reader Functions +// ============================================================================ + +bool TIADS1x15Reader::begin() { + // Initialize the per-instance ADS driver with stored configuration + // ADS Library default settings: + // - TI ADS1115 (16 bit) + // - single-shot mode (powers down between conversions) + // - 128 samples per second (8ms conversion time) + // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) + // - TI ADS1015 (12 bit) + // - single-shot mode (powers down between conversions) + // - 1600 samples per second (625µs conversion time) + // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) + + // Configure the ADS with stored settings + _ads.setGain(_adsGain); + _ads.setDataRate(_adsDataRate); + + // Initialize I2C communication with the ADS + bool success = _ads.begin(_i2cAddress, _wire); + + if (success) { + MS_DBG(F("ADS1x15 initialized successfully at address 0x"), + String(_i2cAddress, HEX)); + } else { + MS_DBG(F("ADS1x15 initialization failed at address 0x"), + String(_i2cAddress, HEX)); + } + + return success; +} + +String TIADS1x15Reader::getAnalogLocation(int8_t analogChannel, + int8_t analogReferenceChannel) { + String sensorLocation; #ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); + sensorLocation += F("ADS1115_0x"); #else - String sensorLocation = F("ADS1015_0x"); + sensorLocation += F("ADS1015_0x"); #endif sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("_Channel"); - sensorLocation += String(_adsChannel); + if (isValidDifferentialPair(analogChannel, analogReferenceChannel)) { + sensorLocation += F("_Diff_"); + sensorLocation += String(analogChannel); + sensorLocation += F("_"); + sensorLocation += String(analogReferenceChannel); + } else { + sensorLocation += F("_Channel"); + sensorLocation += String(analogChannel); + } return sensorLocation; } +bool TIADS1x15Reader::readVoltageSingleEnded(int8_t analogChannel, + float& resultValue) { + bool success = false; + int16_t adcCounts = MS_INVALID_VALUE; + float adcVoltage = MS_INVALID_VALUE; + float scaledResult = MS_INVALID_VALUE; + + // Use the per-instance ADS driver (gain configured in constructor) + + // Verify I2C connectivity with a lightweight probe + if (!probeI2C()) { return false; } + + // Read Analog to Digital Converter (ADC) + // Validate ADS1x15 channel range for single-ended measurements + if (analogChannel < 0 || analogChannel > 3) { + MS_DBG(F(" Invalid ADS1x15 channel "), analogChannel, + F(", valid range is 0-3")); + return false; + } + // Taking this reading includes the 8ms conversion delay. + // Measure the ADC raw count + adcCounts = _ads.readADC_SingleEnded(analogChannel); + // Convert ADC raw counts value to voltage (V) + adcVoltage = _ads.computeVolts(adcCounts); + MS_DBG(F(" _ads.readADC_SingleEnded("), analogChannel, F("):"), adcCounts, + F(" voltage:"), adcVoltage); + + // Verify the range based on both PGA full-scale range and supply voltage + // For single-ended measurements, the valid range is constrained by: + // 1. PGA full-scale range (±FSR based on gain setting) + // 2. Supply voltage limits (approximately -0.3V to supply+0.3V) + // 3. Absolute maximum of 5.5V per datasheet + + float pgaFullScaleRange = _ads.getFsRange(); + float minValidVoltage = -0.3f; // Per datasheet + + // Take the minimum of PGA FSR and supply-based limit + float maxValidVoltage = pgaFullScaleRange; + float supplyBasedMax = _supplyVoltage + 0.3f; + if (supplyBasedMax < maxValidVoltage) { maxValidVoltage = supplyBasedMax; } + + // Apply absolute maximum per datasheet + if (maxValidVoltage > 5.5f) { maxValidVoltage = 5.5f; } -bool TIADS1x15::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float calibResult = -9999; + MS_DBG(F(" ADS supply voltage:"), _supplyVoltage, F("V")); + MS_DBG(F(" PGA full-scale range:"), pgaFullScaleRange, F("V")); + MS_DBG(F(" Valid voltage range:"), minValidVoltage, F("V to"), + maxValidVoltage, F("V")); - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + if (adcVoltage <= maxValidVoltage && adcVoltage >= minValidVoltage) { + // Apply the voltage multiplier scaling, with a default multiplier of 1 + scaledResult = adcVoltage * _voltageMultiplier; + MS_DBG(F(" scaledResult:"), scaledResult); + resultValue = scaledResult; + success = true; + } else { + MS_DBG(F(" ADC voltage "), adcVoltage, F("V out of valid range")); + resultValue = MS_INVALID_VALUE; + } + + return success; +} + +bool TIADS1x15Reader::readVoltageDifferential(int8_t analogChannel, + int8_t analogReferenceChannel, + float& resultValue) { + bool success = false; + int16_t adcCounts = MS_INVALID_VALUE; + float adcVoltage = MS_INVALID_VALUE; + float scaledResult = MS_INVALID_VALUE; + + // Use the per-instance ADS driver (configured in constructor) + // Verify I2C connectivity with a lightweight probe + if (!probeI2C()) { return false; } + + // Validate differential channel combination + if (!isValidDifferentialPair(analogChannel, analogReferenceChannel)) { + MS_DBG(F(" Unsupported differential channel combination: "), + analogChannel, F("-"), analogReferenceChannel); + MS_DBG(F(" Use canonical ordered pairs: 0-1, 0-3, 1-3, or 2-3")); + return false; + } + + // Read differential voltage based on channel configuration + // NOTE: Only canonical ordered pairs are supported (lower channel number + // first) to ensure consistent polarity. Pairs like (1,0) are NOT supported + // - use (0,1) instead. + if (analogChannel == 0 && analogReferenceChannel == 1) { + adcCounts = _ads.readADC_Differential_0_1(); + } else if (analogChannel == 0 && analogReferenceChannel == 3) { + adcCounts = _ads.readADC_Differential_0_3(); + } else if (analogChannel == 1 && analogReferenceChannel == 3) { + adcCounts = _ads.readADC_Differential_1_3(); + } else if (analogChannel == 2 && analogReferenceChannel == 3) { + adcCounts = _ads.readADC_Differential_2_3(); + } else { + // Should never reach here; isValidDifferentialPair must have been + // widened without updating this dispatch table. + MS_DBG(F( + " Internal error: unhandled differential pair after validation")); + return false; + } + + // Convert counts to voltage + adcVoltage = _ads.computeVolts(adcCounts); + MS_DBG(F(" Differential ADC counts:"), adcCounts, F(" voltage:"), + adcVoltage); + + // Validate range - for differential measurements, use PGA full-scale range + // Based on gain setting rather than supply voltage + float fullScaleVoltage = _ads.getFsRange(); + float minValidVoltage = -fullScaleVoltage; + float maxValidVoltage = fullScaleVoltage; + + MS_DBG(F(" ADS gain setting determines full-scale range")); + MS_DBG(F(" Valid differential voltage range:"), minValidVoltage, F("V to"), + maxValidVoltage, F("V")); + + if (adcVoltage <= maxValidVoltage && adcVoltage >= minValidVoltage) { + scaledResult = adcVoltage * _voltageMultiplier; + MS_DBG(F(" scaledResult:"), scaledResult); + resultValue = scaledResult; + success = true; + } else { + MS_DBG(F(" Differential voltage out of valid range")); + resultValue = MS_INVALID_VALUE; + } + + return success; +} + +// Validation function for differential channel pairs +bool TIADS1x15Reader::isValidDifferentialPair(int8_t channel1, + int8_t channel2) { + // Only canonical ordered pairs are valid (lower channel number first) + // This ensures consistent polarity: channel1 is positive, channel2 is + // negative Valid combinations are: 0-1, 0-3, 1-3, or 2-3 (in that order + // only) + if (channel1 >= channel2) return false; // Reject reversed or equal pairs + + return (channel1 == 0 && (channel2 == 1 || channel2 == 3)) || + (channel1 == 1 && channel2 == 3) || (channel1 == 2 && channel2 == 3); +} + +// Setter and getter methods for ADS gain +void TIADS1x15Reader::setADSGain(adsGain_t adsGain) { + // Update the per-instance driver with new gain setting + _ads.setGain(adsGain); + // Keep cached value in sync + _adsGain = adsGain; +} -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. +adsGain_t TIADS1x15Reader::getADSGain() { + return _ads.getGain(); +} + +// Setter and getter methods for ADS data rate +void TIADS1x15Reader::setADSDataRate(uint16_t adsDataRate) { + // Update the per-instance driver with new data rate setting + _ads.setDataRate(adsDataRate); + // Keep cached value in sync + _adsDataRate = adsDataRate; +} + +uint16_t TIADS1x15Reader::getADSDataRate() { + return _ads.getDataRate(); +} + +// Override setSupplyVoltage in TIADS1x15Reader to validate ADS range +void TIADS1x15Reader::setSupplyVoltage(float supplyVoltage) { + // Validate supply voltage range: 0.0V to 5.5V per datasheet + if (supplyVoltage < 0.0f) { + MS_DBG(F("ADS supply voltage "), supplyVoltage, + F("V is below minimum, clamping to 0.0V")); + _supplyVoltage = 0.0f; + } else if (supplyVoltage > 5.5f) { + MS_DBG(F("ADS supply voltage "), supplyVoltage, + F("V exceeds maximum, clamping to 5.5V")); + _supplyVoltage = 5.5f; + } else { + _supplyVoltage = supplyVoltage; + } +} + +float TIADS1x15Reader::calculateAnalogResolutionVolts() { + // Determine ADC resolution based on model +#ifndef MS_USE_ADS1015 + uint8_t resolutionBits = 16; // ADS1115 is 16-bit +#else + uint8_t resolutionBits = 12; // ADS1015 is 12-bit +#endif + + // Get the current PGA full-scale range from the gain setting + float pgaFullScaleRange = _ads.getFsRange(); + + // For single-ended measurements, the actual usable range is limited by: + // 1. PGA full-scale range (gain-dependent) + // 2. Supply voltage + 0.3V (per datasheet) + // 3. Absolute maximum 5.5V (per datasheet) + float effectiveFullScale = pgaFullScaleRange; + float supplyBasedMax = _supplyVoltage + 0.3f; + if (supplyBasedMax < effectiveFullScale) { + effectiveFullScale = supplyBasedMax; + } + if (effectiveFullScale > 5.5f) { effectiveFullScale = 5.5f; } + + // Calculate the total number of ADC codes + // For single-ended measurements, only positive codes are used: 2^(N-1) + uint32_t totalCodes = 1UL << (resolutionBits - 1); + + // Voltage resolution is the effective full scale range divided by positive + // code count + float resolutionVolts = effectiveFullScale / static_cast(totalCodes); + + MS_DBG(F("ADS resolution calculation:")); #ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version + MS_DBG(F(" ADC model: ADS1115 (16-bit)")); #else - Adafruit_ADS1015 ads; // Use this for the 12-bit version + MS_DBG(F(" ADC model: ADS1015 (12-bit)")); #endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - // Bump the gain up to 1x = +/- 4.096V range - ads.setGain(GAIN_ONE); - // Begin ADC - ads.begin(_i2cAddress); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the ADC raw count - adcCounts = ads.readADC_SingleEnded(_adsChannel); - // Convert ADC raw counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_SingleEnded("), _adsChannel, F("):"), - adcVoltage); - - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - // Skip results out of range - // Apply the gain calculation, with a default gain of 10 V/V Gain - calibResult = adcVoltage * _gain; - MS_DBG(F(" calibResult:"), calibResult); - } else { // set invalid voltages back to -9999 - adcVoltage = -9999; + MS_DBG(F(" PGA full scale range: "), pgaFullScaleRange, F("V")); + MS_DBG(F(" Supply voltage: "), _supplyVoltage, F("V")); + MS_DBG(F(" Effective full scale: "), effectiveFullScale, F("V")); + MS_DBG(F(" Resolution bits: "), resolutionBits); + MS_DBG(F(" Total codes: "), totalCodes); + MS_DBG(F(" Voltage resolution: "), resolutionVolts, F("V/LSB")); + + return resolutionVolts; +} + +bool TIADS1x15Reader::probeI2C() { + _wire->beginTransmission(_i2cAddress); + if (_wire->endTransmission() != 0) { + MS_DBG(F(" I2C communication failed at 0x"), String(_i2cAddress, HEX)); + return false; + } + return true; +} + +// ============================================================================ +// TIADS1x15 Functions +// ============================================================================ + +// The constructor - need the power pin the data pin, and voltage multiplier if +// non standard +TIADS1x15::TIADS1x15(int8_t powerPin, int8_t adsChannel, + int8_t analogReferenceChannel, + uint8_t measurementsToAverage, + TIADS1x15Reader* analogVoltageReader) + : Sensor("TIADS1x15", TIADS1X15_NUM_VARIABLES, TIADS1X15_WARM_UP_TIME_MS, + TIADS1X15_STABILIZATION_TIME_MS, TIADS1X15_MEASUREMENT_TIME_MS, + powerPin, adsChannel, measurementsToAverage, + TIADS1X15_INC_CALC_VARIABLES), + _analogReferenceChannel(analogReferenceChannel), + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new TIADS1x15Reader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) { + // NOTE: We DO NOT validate the channel numbers and pairings in this + // constructor! We CANNOT print a warning here about invalid channel + // because the Serial object may not be initialized yet, and we don't want + // to cause a crash. The readVoltageSingleEnded and readVoltageDifferential + // functions will handle validation and return false if the channel + // configuration is invalid, but we can't do that here in the constructor +} + +// Destructor +TIADS1x15::~TIADS1x15() { + // Clean up the ADS base object if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + _analogVoltageReader = nullptr; + } +} + +String TIADS1x15::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + return _analogVoltageReader->getAnalogLocation(_dataPin, + _analogReferenceChannel); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } +} + + +bool TIADS1x15::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); } } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); + } + + return sensorSetupSuccess && analogVoltageReaderSuccess; +} + +bool TIADS1x15::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); } - verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult); + float resultValue = MS_INVALID_VALUE; + bool success = false; - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - return true; + // Use differential or single-ended reading based on configuration + if (_analogVoltageReader->isValidDifferentialPair( + _dataPin, _analogReferenceChannel)) { + success = _analogVoltageReader->readVoltageDifferential( + _dataPin, _analogReferenceChannel, resultValue); } else { - return false; + if (_analogReferenceChannel >= 0) { + // Differential was requested but pair is invalid - fail the + // measurement + MS_DBG(F(" Error: Invalid differential pair ("), _dataPin, F("-"), + _analogReferenceChannel, F("). Measurement failed.")); + success = false; + } else { + // No reference channel specified, use single-ended + success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + resultValue); + } + } + + if (success) { + verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, resultValue); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } + +// cspell:words GAIN_TWOTHIRDS diff --git a/src/sensors/TIADS1x15.h b/src/sensors/TIADS1x15.h index a7e473084..e40740759 100644 --- a/src/sensors/TIADS1x15.h +++ b/src/sensors/TIADS1x15.h @@ -48,8 +48,9 @@ * on the ADS1x15. The ADS1x15 requires an input voltage of 2.0-5.5V, but *this library * always assumes the ADS is powered with 3.3V*. * - * @note ModularSensors only supports connecting the ADS1x15 to primary hardware I2C instance. - * Connecting the ADS to a secondary hardware or software I2C instance is *not* supported! + * @note ModularSensors supports connecting the ADS1x15 to primary hardware I2C instance + * or to a secondary hardware I2C instance using the TwoWire parameter in the constructor. + * Software I2C is *not* supported! * * Communication with the ADS1x15 depends on the * [soligen2010 fork of the Adafruit ADS1015 library](https://github.com/soligen2010/Adafruit_ADS1X15). @@ -59,15 +60,22 @@ * use the fork instead. * * @section analog_ads1x15_specs Specifications - * @note *In all cases, we assume that the ADS1x15 is powered at 3.3V and set the ADC's internal gain to 1x. + * @note *In all cases, we assume that the ADS1x15 is powered at 3.3V by default with configurable internal gain settings. * - * This divides the bit resolution over the range of 0-4.096V. + * The default gain setting is 1x (GAIN_ONE) which provides a PGA full-scale range (±4.096V). + * In single-ended mode the actual ceiling is min(Full-Scale Range (FSR), VDD + 0.3V) — typically 3.6V at 3.3V supply. * - Response time: < 1ms * - Resample time: 860 samples per second (~1.2ms) * - Range: - * - Range is determined by supply voltage - No more than VDD + 0.3 V r 5.5 V - * (whichever is smaller) must be applied to this device. - * - 0 - 3.6V [when ADC is powered at 3.3V] + * - Single-ended measurements: Limited by supply voltage (VDD + 0.3V max, absolute max 5.5V) + * - 0 - 3.6V [when ADC is powered at 3.3V] + * - Differential measurements: Limited by PGA full-scale range (gain-dependent) + * - GAIN_TWOTHIRDS = ±6.144V PGA full-scale range + * - GAIN_ONE = ±4.096V PGA full-scale range + * - GAIN_TWO = ±2.048V PGA full-scale range + * - GAIN_FOUR = ±1.024V PGA full-scale range + * - GAIN_EIGHT = ±0.512V PGA full-scale range + * - GAIN_SIXTEEN = ±0.256V PGA full-scale range * - Accuracy: * - 16-bit ADC (ADS1115): < 0.25% (gain error), <0.25 LSB (offset error) * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): < 0.15% (gain error), <3 LSB (offset error) @@ -112,8 +120,8 @@ * If you are working with an EnviroDIY Mayfly, the easiest voltage divider to * connect is the Grove voltage divider sold by seeed studio. The grove voltage * divider is a simple voltage divider designed to measure high external - * voltages on a low voltage ADC. This module employs a variable gain via two - * pairs of voltage dividers, and a unity gain amplification to reduce output + * voltages on a low voltage ADC. This module employs a variable voltage multiplier + * via two pairs of voltage dividers, and a unity gain amplification to reduce output * impedance of the module. * * @section sensor_ads1x15_datasheet Sensor Datasheet @@ -159,6 +167,8 @@ // Include other in-library and external dependencies #include "VariableBase.h" #include "SensorBase.h" +#include "AnalogVoltageReader.h" +#include /** @ingroup sensor_ads1x15 */ /**@{*/ @@ -175,16 +185,6 @@ #define TIADS1X15_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_ads1x15_config - * @name Configuration Defines - * Defines to help configure the address of the ADD - */ -/**@{*/ -/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 -/**@}*/ - /** * @anchor sensor_ads1x15_timing * @name Sensor Timing @@ -213,29 +213,28 @@ * @anchor sensor_ads1x15_volt * @name Voltage * The volt variable from a TI ADS1x15 analog-to-digital converter (ADC) - * - Range: - * - without voltage divider: 0 - 3.6V [when ADC is powered at 3.3V] - * - 1/gain = 3x: 0.3 ~ 12.9V - * - 1/gain = 10x: 1 ~ 43V + * - Range (with no external voltage divider): + * - 0 - min(4.096V, supply voltage + 0.3V) * - Accuracy: * - 16-bit ADC (ADS1115): < 0.25% (gain error), <0.25 LSB (offset error) * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): < 0.15% * (gain error), <3 LSB (offset error) - * - Resolution: + * - Resolution (based on ADC's 4.096V internal reference with 1x gain and no + * external voltage divider): * - 16-bit ADC (ADS1115): * - @m_span{m-dim}@ref #TIADS1X15_RESOLUTION = 4@m_endspan - * - without voltage divider: 0.125 mV - * - 1/gain = 3x: 0.375 mV - * - 1/gain = 10x: 1.25 mV + * - 0.125 mV * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): * - @m_span{m-dim}@ref #TIADS1X15_RESOLUTION = 1@m_endspan - * - without voltage divider: 2 mV - * - 1/gain = 3x: 6 mV - * - 1/gain = 10x: 20 mV * + * - 2 mV * * {{ @ref TIADS1x15_Voltage::TIADS1x15_Voltage }} */ /**@{*/ +/// @brief Minimum voltage in volts. +#define TIADS1X15_MIN_V -6.144 +/// @brief Maximum voltage in volts. +#define TIADS1X15_MAX_V 6.144 /// Variable number; voltage is stored in sensorValues[0]. #define TIADS1X15_VAR_NUM 0 /// @brief Variable name in @@ -249,14 +248,219 @@ #define TIADS1X15_DEFAULT_CODE "extVoltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1. +/// @brief Decimal places in string representation; voltage should have 1. #define TIADS1X15_RESOLUTION 1 +/// @brief Default data rate for ADS1015 (1600 SPS) +#define TIADS1X15_DEFAULT_DATA_RATE RATE_ADS1015_1600SPS #else -/// @brief Decimals places in string representation; voltage should have 4. +/// @brief Decimal places in string representation; voltage should have 4. #define TIADS1X15_RESOLUTION 4 +/// @brief Default data rate for ADS1115 (128 SPS) +#define TIADS1X15_DEFAULT_DATA_RATE RATE_ADS1115_128SPS #endif /**@}*/ +/** + * @brief TI ADS1x15 base class that inherits from AnalogVoltageReader + * + * This class provides ADS1x15-specific analog functionality on top of + * the generic AnalogVoltageReader class. It handles ADS configuration, + * I2C communication, and differential/single-ended measurement modes. + */ +class TIADS1x15Reader : public AnalogVoltageReader { + public: + + /** + * @brief Construct a new TIADS1x15Reader object + * + * @param theI2C A TwoWire instance for I2C communication. Due to the + * speed and sensitivity requirements of the ADS1x15, only hardware I2C is + * supported. For AVR boards, this will be Wire. For SAMD boards, this + * can be Wire, Wire1, Wire2, etc.. + * @param voltageMultiplier The voltage multiplier for any voltage dividers + * @param adsGain The internal gain setting of the ADS1x15 + * @param i2cAddress The I2C address of the ADS 1x15 + * @param adsSupplyVoltage The power supply voltage for the ADS1x15 in volts + * @param adsDataRate The data rate for the ADS1x15 (samples per second) + */ + explicit TIADS1x15Reader( + TwoWire* theI2C, float voltageMultiplier = 1.0f, + adsGain_t adsGain = GAIN_ONE, + uint8_t i2cAddress = MS_DEFAULT_ADS1X15_ADDRESS, + float adsSupplyVoltage = OPERATING_VOLTAGE, + uint16_t adsDataRate = TIADS1X15_DEFAULT_DATA_RATE); + + /** + * @brief Construct a new TIADS1x15Reader object using the default hardware + * Wire instance. + * + * @param voltageMultiplier The voltage multiplier for any voltage dividers + * @param adsGain The internal gain setting of the ADS1x15 + * @param i2cAddress The I2C address of the ADS 1x15 + * @param adsSupplyVoltage The power supply voltage for the ADS1x15 in volts + * @param adsDataRate The data rate for the ADS1x15 (samples per second) + */ + explicit TIADS1x15Reader( + float voltageMultiplier = 1.0f, adsGain_t adsGain = GAIN_ONE, + uint8_t i2cAddress = MS_DEFAULT_ADS1X15_ADDRESS, + float adsSupplyVoltage = OPERATING_VOLTAGE, + uint16_t adsDataRate = TIADS1X15_DEFAULT_DATA_RATE); + + /** + * @brief Destroy the TIADS1x15Reader object + */ + ~TIADS1x15Reader() override = default; + + /** + * @brief Initialize the ADS1x15 analog voltage reading system + * + * This function performs hardware initialization that cannot be safely + * done in the constructor, including I2C communication with the ADS1x15 + * device to configure gain and data rate settings. + * + * @return True if the initialization was successful, false otherwise + */ + bool begin() override; + + /** + * @brief Read a single-ended voltage measurement from the ADS1x15 + * + * @param analogChannel The ADS channel of interest (0-3, physical channel + * only). Negative or invalid channel numbers are not clamped and will + * cause the reading to fail and emit a warning. + * @param resultValue Reference to store the resulting voltage measurement + * @return True if the voltage reading was successful + */ + bool readVoltageSingleEnded(int8_t analogChannel, + float& resultValue) override; + + /** + * @brief Read a differential voltage measurement from the ADS1x15 + * + * @param analogChannel The primary analog channel for differential + * measurement. Negative or invalid channel numbers or pairings between the + * analogChannel and analogReferenceChannel are not clamped and will cause + * the reading to fail and emit a warning. + * @param analogReferenceChannel The secondary (reference) analog channel + * for differential measurement. Negative or invalid channel numbers or + * pairings between the analogChannel and analogReferenceChannel are not + * clamped and will cause the reading to fail and emit a warning. + * @param resultValue Reference to store the resulting voltage measurement + * @return True if the voltage reading was successful and within valid range + */ + bool readVoltageDifferential(int8_t analogChannel, + int8_t analogReferenceChannel, + float& resultValue) override; + + String getAnalogLocation(int8_t analogChannel, + int8_t analogReferenceChannel) override; + + /** + * @brief Set the internal gain setting for the ADS1x15 + * + * @param adsGain The internal gain setting (GAIN_TWOTHIRDS, GAIN_ONE, + * GAIN_TWO, GAIN_FOUR, GAIN_EIGHT, GAIN_SIXTEEN) + */ + void setADSGain(adsGain_t adsGain); + + /** + * @brief Get the internal gain setting for the ADS1x15 + * + * @return The internal gain setting + */ + adsGain_t getADSGain(); + + /** + * @brief Set the data rate for the ADS1x15 + * + * @param adsDataRate The data rate setting (samples per second) + */ + void setADSDataRate(uint16_t adsDataRate); + + /** + * @brief Get the data rate for the ADS1x15 + * + * @return The data rate setting (samples per second) + */ + uint16_t getADSDataRate(); + + /** + * @brief Check if the two channels form a valid differential pair + * + * @param channel1 First channel (0-3, physical ADS channel indices only) + * @param channel2 Second channel (0-3, physical ADS channel indices only) + * @return True if the combination is valid (0-1, 0-3, 1-3, or 2-3) + * + * @note Channel parameters use int8_t, consistent with the rest of the + * ModularSensors channel conventions. Negative values indicate invalid + * channels. + */ + static bool isValidDifferentialPair(int8_t channel1, int8_t channel2); + + /** + * @brief Set the supply voltage for the ADS1x15 + * + * @param supplyVoltage The supply voltage in volts + * + * @note Valid range is 0.0V to 5.5V per ADS1x15 datasheet. Values outside + * this range will be clamped with debug logging. + */ + void setSupplyVoltage(float supplyVoltage) override; + + /** + * @brief Calculate the analog resolution in volts for the ADS1x15 + * + * For ADS1x15, this calculates the voltage resolution based on the current + * gain setting, supply voltage, and ADC bit resolution. The resolution + * depends on: + * - ADC model: 12-bit (ADS1015) or 16-bit (ADS1115) + * - Gain setting: determines PGA full-scale range + * - Supply voltage: limits actual usable range for single-ended + * measurements + * + * The effective full scale range is the minimum of: + * - PGA full-scale range (gain-dependent) + * - Supply voltage + 0.3V (for single-ended measurements) + * - Absolute maximum 5.5V per datasheet + * + * @return The analog resolution in volts per LSB + */ + float calculateAnalogResolutionVolts() override; + + protected: + /** + * @brief An internal reference to the hardware Wire instance. + */ + TwoWire* _wire = nullptr; + /** + * @brief Internal reference to the I2C address of the TI-ADS1x15 + */ + uint8_t _i2cAddress = MS_DEFAULT_ADS1X15_ADDRESS; + /** + * @brief The internal gain setting for the ADS1x15 + */ + adsGain_t _adsGain = GAIN_ONE; + /** + * @brief The data rate setting for the ADS1x15 + */ + uint16_t _adsDataRate = TIADS1X15_DEFAULT_DATA_RATE; + /** + * @brief Per-instance ADS1x15 driver to maintain separate I2C state + */ +#ifndef MS_USE_ADS1015 + Adafruit_ADS1115 _ads; // 16-bit version +#else + Adafruit_ADS1015 _ads; // 12-bit version +#endif + + /** + * @brief Probe I2C connectivity to the ADS device + * + * @return true if device responds, false if communication failed + */ + bool probeI2C(); +}; + /* clang-format off */ /** * @brief The Sensor sub-class for the @@ -268,11 +472,8 @@ class TIADS1x15 : public Sensor { public: /** - * @brief Construct a new External Voltage object - need the power pin and - * the data channel on the ADS1x15. - * - * The gain value, I2C address, and number of measurements to average are - * optional. If nothing is given a 1x gain is used. + * @brief Construct a new TIADS1x15 object for single-ended or differential + * voltage measurements * * @note ModularSensors only supports connecting the ADS1x15 to the primary * hardware I2C instance defined in the Arduino core. Connecting the ADS to @@ -280,46 +481,65 @@ class TIADS1x15 : public Sensor { * * @param powerPin The pin on the mcu controlling power to the sensor * Use -1 if it is continuously powered. - * @param adsChannel The ADS channel of interest (0-3). - * @param gain The gain multiplier, if a voltage divider is used. - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) + * @param adsChannel The ADS channel of interest (0-3, physical channel + * only). For differential measurements, this is the first (positive) + * channel. + * @param analogReferenceChannel The second (reference/negative) ADS channel + * for differential measurement (0-3, physical channel only). Valid pairs + * are: 0-1, 0-3, 1-3, or 2-3. Use -1 (default) for single-ended + * measurements. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. + * @param analogVoltageReader Pointer to TIADS1x15Reader object for ADS + * functionality. If nullptr (default), creates a new TIADS1x15Reader with + * default settings. */ - TIADS1x15(int8_t powerPin, uint8_t adsChannel, float gain = 1, - uint8_t i2cAddress = ADS1115_ADDRESS, - uint8_t measurementsToAverage = 1); + TIADS1x15(int8_t powerPin, int8_t adsChannel, + int8_t analogReferenceChannel = -1, + uint8_t measurementsToAverage = 1, + TIADS1x15Reader* analogVoltageReader = nullptr); /** - * @brief Destroy the External Voltage object + * @brief Destroy the TIADS1x15 object */ - ~TIADS1x15(); + ~TIADS1x15() override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + TIADS1x15(const TIADS1x15&) = delete; + TIADS1x15& operator=(const TIADS1x15&) = delete; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete move constructor and move assignment operator + TIADS1x15(TIADS1x15&&) = delete; + TIADS1x15& operator=(TIADS1x15&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: /** - * @brief Internal reference to the ADS channel number of the device - * attached to the TI-ADS1x15 + * @brief The second (reference) pin for differential voltage measurements. + * + * For single-ended measurements: -1 (not used) + * For differential measurements: the second ADS channel (0-3) + * + * @note The primary pin is stored as Sensor::_dataPin. */ - uint8_t _adsChannel; + int8_t _analogReferenceChannel = -1; + /** - * @brief Internal reference to the gain setting for the TI-ADS1x15 + * @brief Pointer to the TIADS1x15Reader object providing ADS functionality */ - float _gain; + TIADS1x15Reader* _analogVoltageReader = nullptr; + /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 + * @brief Whether this object owns the _analogVoltageReader pointer and + * should delete it */ - uint8_t _i2cAddress; + bool _ownsAnalogVoltageReader = false; }; /** @@ -355,23 +575,12 @@ class TIADS1x15_Voltage : public Variable { */ explicit TIADS1x15_Voltage(TIADS1x15* parentSense, const char* uuid = "", const char* varCode = TIADS1X15_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TIADS1X15_VAR_NUM, - (uint8_t)TIADS1X15_RESOLUTION, TIADS1X15_VAR_NAME, - TIADS1X15_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TIADS1x15_Voltage object. - * - * @note This must be tied with a parent TIADS1x15 before it can be - * used. - */ - TIADS1x15_Voltage() - : Variable((uint8_t)TIADS1X15_VAR_NUM, (uint8_t)TIADS1X15_RESOLUTION, - TIADS1X15_VAR_NAME, TIADS1X15_UNIT_NAME, - TIADS1X15_DEFAULT_CODE) {} + : Variable(parentSense, TIADS1X15_VAR_NUM, TIADS1X15_RESOLUTION, + TIADS1X15_VAR_NAME, TIADS1X15_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the TIADS1x15_Voltage object - no action needed. */ - ~TIADS1x15_Voltage() {} + ~TIADS1x15_Voltage() override = default; }; /** @@ -383,4 +592,7 @@ class TIADS1x15_Voltage : public Variable { typedef TIADS1x15_Voltage ExternalVoltage_Volt; /**@}*/ + #endif // SRC_SENSORS_TIADS1X15_H_ + +// cSpell:words GAIN_TWOTHIRDS diff --git a/src/sensors/TIINA219.cpp b/src/sensors/TIINA219.cpp index 6957c1def..030d0b2d6 100644 --- a/src/sensors/TIINA219.cpp +++ b/src/sensors/TIINA219.cpp @@ -17,29 +17,32 @@ TIINA219::TIINA219(TwoWire* theI2C, int8_t powerPin, uint8_t i2cAddressHex, : Sensor("TIINA219", INA219_NUM_VARIABLES, INA219_WARM_UP_TIME_MS, INA219_STABILIZATION_TIME_MS, INA219_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage), + ina219_phy(i2cAddressHex), _i2cAddressHex(i2cAddressHex), - _i2c(theI2C) {} + _i2c(theI2C != nullptr ? theI2C : &Wire) {} TIINA219::TIINA219(int8_t powerPin, uint8_t i2cAddressHex, uint8_t measurementsToAverage) : Sensor("TIINA219", INA219_NUM_VARIABLES, INA219_WARM_UP_TIME_MS, INA219_STABILIZATION_TIME_MS, INA219_MEASUREMENT_TIME_MS, powerPin, -1, measurementsToAverage, INA219_INC_CALC_VARIABLES), + ina219_phy(i2cAddressHex), _i2cAddressHex(i2cAddressHex), _i2c(&Wire) {} -// Destructor -TIINA219::~TIINA219() {} -String TIINA219::getSensorLocation(void) { - String address = F("I2C_0x"); +String TIINA219::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool TIINA219::setup(void) { +bool TIINA219::setup() { bool wasOn; - Sensor::setup(); // this will set pin modes and the setup status bit + bool setupSuccess = + Sensor::setup(); // this will set pin modes and the setup status bit // This sensor needs power for setup! delay(10); @@ -49,67 +52,78 @@ bool TIINA219::setup(void) { waitForWarmUp(); } - ina219_phy.begin(_i2c); + bool success = setupSuccess && ina219_phy.begin(_i2c); // Turn the power back off it it had been turned on if (!wasOn) { powerDown(); } - return true; + if (!success) { + // Set the status error bit (bit 7) + setStatusBit(ERROR_OCCURRED); + // UN-set the set-up bit (bit 0) since setup failed! + clearStatusBit(SETUP_SUCCESSFUL); + } + return success; } -bool TIINA219::wake(void) { +bool TIINA219::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; // Begin/Init needs to be rerun after every power-up to set the calibration // coefficient for the INA219 (see p21 of datasheet) - ina219_phy.begin(_i2c); + bool success = ina219_phy.begin(_i2c); + if (!success) { + // Set the status error bit (bit 7) + setStatusBit(ERROR_OCCURRED); + // Make sure that the wake time and wake success bit (bit 4) are + // unset + _millisSensorActivated = 0; + clearStatusBit(WAKE_SUCCESSFUL); + } - return true; + return success; } -bool TIINA219::addSingleMeasurementResult(void) { - bool success = false; +bool TIINA219::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Initialize float variables - float current_mA = -9999; - float busV_V = -9999; - float power_mW = -9999; + bool success = false; + float current_mA = MS_INVALID_VALUE; + float busV_V = MS_INVALID_VALUE; + float power_mW = MS_INVALID_VALUE; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - // Read values - current_mA = ina219_phy.getCurrent_mA(); - if (isnan(current_mA)) current_mA = -9999; - busV_V = ina219_phy.getBusVoltage_V(); - if (isnan(busV_V)) busV_V = -9999; - power_mW = ina219_phy.getPower_mW(); - if (isnan(power_mW)) power_mW = -9999; + // Read values + current_mA = ina219_phy.getCurrent_mA(); + success = ina219_phy.success(); + busV_V = ina219_phy.getBusVoltage_V(); + success &= ina219_phy.success(); + power_mW = ina219_phy.getPower_mW(); + success &= ina219_phy.success(); - success = true; + // Only success if I2C read succeeded and none of the values are NaN + success = success && !isnan(current_mA) && !isnan(busV_V) && + !isnan(power_mW); - MS_DBG(F(" Current [mA]:"), current_mA); - MS_DBG(F(" Bus Voltage [V]:"), busV_V); - MS_DBG(F(" Power [mW]:"), power_mW); - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); - } - - verifyAndAddMeasurementResult(INA219_CURRENT_MA_VAR_NUM, current_mA); - verifyAndAddMeasurementResult(INA219_BUS_VOLTAGE_VAR_NUM, busV_V); - verifyAndAddMeasurementResult(INA219_POWER_MW_VAR_NUM, power_mW); + MS_DBG(F(" Current [mA]:"), current_mA); + MS_DBG(F(" Bus Voltage [V]:"), busV_V); + MS_DBG(F(" Power [mW]:"), power_mW); + if (success) { + verifyAndAddMeasurementResult(INA219_CURRENT_MA_VAR_NUM, current_mA); + verifyAndAddMeasurementResult(INA219_BUS_VOLTAGE_VAR_NUM, busV_V); + verifyAndAddMeasurementResult(INA219_POWER_MW_VAR_NUM, power_mW); + } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } + +// cSpell:ignore TIINA219 diff --git a/src/sensors/TIINA219.h b/src/sensors/TIINA219.h index e96426aff..5b38e2331 100644 --- a/src/sensors/TIINA219.h +++ b/src/sensors/TIINA219.h @@ -6,7 +6,7 @@ * @author Written By: Neil Hancock * Edited by Sara Geleskie Damiano * - * @brief Contains the TIINA219 sensor subclass and the variale subclasses + * @brief Contains the TIINA219 sensor subclass and the variable subclasses * TIINA219_Current, TIINA219_Voltage, and TIINA219_Power. * * These are for the Texas Instruments INA219 current/voltage sensor. @@ -24,7 +24,7 @@ * @tableofcontents * @m_footernavigation * - * @section sensor_ina219_intro Intruduction + * @section sensor_ina219_intro Introduction * * The [TI INA219](http://www.ti.com/product/INA219) is a bi-directional, * high-side, current/power monitor that communicates with the board via I2C. @@ -34,7 +34,7 @@ * of this sensor can be increased to increase sensitivity (at the expense of * range) but this library assumes the maximum range. * - * Commuincation between the INA219 and the mcu is managed by the + * Communications between the INA219 and the mcu are managed by the * [Adafruit INA219 Library](https://github.com/adafruit/Adafruit_INA219) * * @note Software I2C is *not* supported for the INA219. @@ -86,6 +86,16 @@ /** @ingroup sensor_ina219 */ /**@{*/ +/** + * @anchor sensor_ina219_config + * @name Configuration Defines + * Defines to set the address of the INA219. + */ +/**@{*/ +/// @brief The default address of the INA219 +#define INA219_ADDRESS_BASE 0x40 +/**@}*/ + /** * @anchor sensor_ina219_var_counts * @name Sensor Variable Counts @@ -98,16 +108,6 @@ #define INA219_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_ina219_config - * @name Configuration Defines - * Defines to set the address of the INA219. - */ -/**@{*/ -/// @brief The default address of the INA219 -#define INA219_ADDRESS_BASE 0x40 -/**@}*/ - /** * @anchor sensor_ina219_timing * @name Sensor Timing @@ -119,7 +119,7 @@ /** * @brief Sensor::_stabilizationTime_ms; the INA219 is stable after 4000ms. * - * Stable numbers can be acheived after 500ms, but waiting up to 4s gave more + * Stable numbers can be achieved after 500ms, but waiting up to 4s gave more * consistent numbers based on tests using INA219timingTest.ino */ #define INA219_STABILIZATION_TIME_MS 4000 @@ -141,16 +141,18 @@ * - Range is between +/-0.4 Amps and +/-3.2 Amps * - Absolute accuracy is range dependent, and approx 2LSB (R accuracy * unknown) + * - Resolution is 12-bit + * - 0.8mA using +/-3.2 Amp range + * - 0.1mA using +/-0.4 Amp range * * {{ @ref TIINA219_Current::TIINA219_Current }} */ /**@{*/ -/** - * @brief Decimals places in string representation; current should have 1. - * - resolution is 12-bit - * - 0.8mA using +/-3.2 Amp range - * - 0.1mA using +/-0.4 Amp range - */ +/// @brief Minimum current in amps (negative for reverse current). +#define INA219_CURRENT_MIN_A -3.2 +/// @brief Maximum current in amps. +#define INA219_CURRENT_MAX_A 3.2 +/// @brief Decimal places in string representation; current should have 1. #define INA219_CURRENT_MA_RESOLUTION 1 /// @brief Sensor variable number; current is stored in sensorValues[0]. #define INA219_CURRENT_MA_VAR_NUM 0 @@ -175,8 +177,12 @@ * {{ @ref TIINA219_Voltage::TIINA219_Voltage }} */ /**@{*/ -/// @brief Decimals places in string representation; bus voltage should have 4 - -/// resolution is 0.001V. +/// @brief Minimum bus voltage in volts. +#define INA219_BUS_VOLTAGE_MIN_V 0 +/// @brief Maximum bus voltage in volts. +#define INA219_BUS_VOLTAGE_MAX_V 26 +/// @brief Decimal places in string representation; bus voltage should have 3 - +/// resolution is 0.004V. #define INA219_BUS_VOLTAGE_RESOLUTION 3 /// @brief Sensor variable number; bus voltage is stored in sensorValues[1]. #define INA219_BUS_VOLTAGE_VAR_NUM 1 @@ -199,7 +205,11 @@ * {{ @ref TIINA219_Power::TIINA219_Power }} */ /**@{*/ -/// @brief Decimals places in string representation; power draw should have 2 - +/// @brief Minimum power in milliwatts. +#define INA219_POWER_MW_MIN_MW 0 +/// @brief Maximum power in milliwatts. +#define INA219_POWER_MW_MAX_MW 102400 +/// @brief Decimal places in string representation; power draw should have 2 - /// resolution is 0.01mW. #define INA219_POWER_MW_RESOLUTION 2 /// @brief Sensor variable number; power draw is stored in sensorValues[2]. @@ -238,7 +248,7 @@ class TIINA219 : public Sensor { * Use -1 if it is continuously powered. * - The INA219 requires input voltage of 3.0-5.5V, which can be turned off * between measurements. - * @param i2cAddressHex The I2C address of the BME280; can be any number + * @param i2cAddressHex The I2C address of the INA219; can be any number * between 0x40 and 0x4F. The default value is 0x40. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a @@ -255,7 +265,7 @@ class TIINA219 : public Sensor { * Use -1 if it is continuously powered. * - The INA219 requires input voltage of 3.0-5.5V, which can be turned off * between measurements. - * @param i2cAddressHex The I2C address of the BME280; can be any number + * @param i2cAddressHex The I2C address of the INA219; can be any number * between 0x40 and 0x4F. The default value is 0x40. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a @@ -267,7 +277,7 @@ class TIINA219 : public Sensor { /** * @brief Destroy the TI INA219 object */ - ~TIINA219(); + ~TIINA219() override = default; /** * @brief Wake the sensor up and read the calibration coefficient from it. @@ -279,7 +289,7 @@ class TIINA219 : public Sensor { * * @return True if the wake function completed successfully. */ - bool wake(void) override; + bool wake() override; /** * @brief Do any one-time preparations needed before the sensor will be able * to take readings. @@ -290,16 +300,11 @@ class TIINA219 : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + bool setup() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + String getSensorLocation() override; + + bool addSingleMeasurementResult() override; private: /** @@ -339,24 +344,13 @@ class TIINA219_Current : public Variable { explicit TIINA219_Current( TIINA219* parentSense, const char* uuid = "", const char* varCode = INA219_CURRENT_MA_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INA219_CURRENT_MA_VAR_NUM, - (uint8_t)INA219_CURRENT_MA_RESOLUTION, - INA219_CURRENT_MA_VAR_NAME, INA219_CURRENT_MA_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new TIINA219_Current object. - * - * @note This must be tied with a parent TIINA219 before it can be used. - */ - TIINA219_Current() - : Variable((uint8_t)INA219_CURRENT_MA_VAR_NUM, - (uint8_t)INA219_CURRENT_MA_RESOLUTION, - INA219_CURRENT_MA_VAR_NAME, INA219_CURRENT_MA_UNIT_NAME, - INA219_CURRENT_MA_DEFAULT_CODE) {} + : Variable(parentSense, INA219_CURRENT_MA_VAR_NUM, + INA219_CURRENT_MA_RESOLUTION, INA219_CURRENT_MA_VAR_NAME, + INA219_CURRENT_MA_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the TIINA219_Current object - no action needed. */ - ~TIINA219_Current() {} + ~TIINA219_Current() override = default; }; @@ -382,24 +376,13 @@ class TIINA219_Voltage : public Variable { explicit TIINA219_Voltage( TIINA219* parentSense, const char* uuid = "", const char* varCode = INA219_BUS_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INA219_BUS_VOLTAGE_VAR_NUM, - (uint8_t)INA219_BUS_VOLTAGE_RESOLUTION, - INA219_BUS_VOLTAGE_VAR_NAME, INA219_BUS_VOLTAGE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new TIINA219_Voltage object. - * - * @note This must be tied with a parent TIINA219 before it can be used. - */ - TIINA219_Voltage() - : Variable((uint8_t)INA219_BUS_VOLTAGE_VAR_NUM, - (uint8_t)INA219_BUS_VOLTAGE_RESOLUTION, - INA219_BUS_VOLTAGE_VAR_NAME, INA219_BUS_VOLTAGE_UNIT_NAME, - INA219_BUS_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, INA219_BUS_VOLTAGE_VAR_NUM, + INA219_BUS_VOLTAGE_RESOLUTION, INA219_BUS_VOLTAGE_VAR_NAME, + INA219_BUS_VOLTAGE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the TIINA219_Voltage object - no action needed. */ - ~TIINA219_Voltage() {} + ~TIINA219_Voltage() override = default; }; /** @@ -433,24 +416,15 @@ class TIINA219_Power : public Variable { */ explicit TIINA219_Power(TIINA219* parentSense, const char* uuid = "", const char* varCode = INA219_POWER_MW_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)INA219_POWER_MW_VAR_NUM, - (uint8_t)INA219_POWER_MW_RESOLUTION, - INA219_POWER_MW_VAR_NAME, INA219_POWER_MW_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new TIINA219_Power object. - * - * @note This must be tied with a parent TIINA219 before it can be used. - */ - TIINA219_Power() - : Variable((uint8_t)INA219_POWER_MW_VAR_NUM, - (uint8_t)INA219_POWER_MW_RESOLUTION, - INA219_POWER_MW_VAR_NAME, INA219_POWER_MW_UNIT_NAME, - INA219_POWER_MW_DEFAULT_CODE) {} + : Variable(parentSense, INA219_POWER_MW_VAR_NUM, + INA219_POWER_MW_RESOLUTION, INA219_POWER_MW_VAR_NAME, + INA219_POWER_MW_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the TIINA219_Power object - no action needed. */ - ~TIINA219_Power() {} + ~TIINA219_Power() override = default; }; /**@}*/ #endif // SRC_SENSORS_TIINA219_H_ + +// cSpell:words TIINA219 diff --git a/src/sensors/TallyCounterI2C.cpp b/src/sensors/TallyCounterI2C.cpp index 9e043c2d6..7e3d0b634 100644 --- a/src/sensors/TallyCounterI2C.cpp +++ b/src/sensors/TallyCounterI2C.cpp @@ -18,18 +18,18 @@ TallyCounterI2C::TallyCounterI2C(int8_t powerPin, uint8_t i2cAddressHex) TALLY_STABILIZATION_TIME_MS, TALLY_MEASUREMENT_TIME_MS, powerPin, -1, 1, TALLY_INC_CALC_VARIABLES), _i2cAddressHex(i2cAddressHex) {} -// Destructor -TallyCounterI2C::~TallyCounterI2C() {} -String TallyCounterI2C::getSensorLocation(void) { - String address = F("I2C_0x"); +String TallyCounterI2C::getSensorLocation() { + String address; + address.reserve(10); // Reserve for "I2C_0x" + 2 hex chars + address = F("I2C_0x"); address += String(_i2cAddressHex, HEX); return address; } -bool TallyCounterI2C::setup(void) { +bool TallyCounterI2C::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit @@ -43,13 +43,13 @@ bool TallyCounterI2C::setup(void) { // Make 5 attempts uint8_t ntries = 0; bool success = false; - uint8_t Stat = false; // Used to test for connectivity to Tally device + uint8_t status = 0; // Used to test for connectivity to Tally device while (!success && ntries < 5) { - Stat = counter_internal.begin(); + status = counter_internal.begin(); counter_internal.Sleep(); // Engage auto-sleep mode between event // counts counter_internal.Clear(); // Clear count to ensure valid first reading - if (Stat == 0) success = true; + if (status == 0) success = true; ntries++; } if (!success) { @@ -67,50 +67,35 @@ bool TallyCounterI2C::setup(void) { } -bool TallyCounterI2C::addSingleMeasurementResult(void) { - bool success = false; +bool TallyCounterI2C::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - // Initialize variables - int16_t events = -9999; // Number of events - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - - // Read values - // Read data from counter before clear - - events = counter_internal.Peek(); - if (isnan(events)) events = -9999; - - // Assume that if negative a failed response - // May also return a very negative temp when receiving a bad response - if (events < 0) { - MS_DBG(F("All values 0 or bad, assuming sensor non-response!")); - events = -9999; - } else { - success = true; - } - - // Clear count value - counter_internal.Clear(); + bool success = false; + int16_t events = MS_INVALID_VALUE; // Number of events - if (events < 0) - events = -9999; // If negative value results, return failure + // Read values + // Read data from counter before clear - MS_DBG(F(" Events:"), events); + events = counter_internal.Peek(); + // Assume that if negative, it indicates a failed response + // May also return a very negative value when receiving a bad response + if (events < 0) { + MS_DBG(getSensorNameAndLocation(), + F("returned negative value, assuming sensor non-response!")); + events = MS_INVALID_VALUE; } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + verifyAndAddMeasurementResult(TALLY_EVENTS_VAR_NUM, events); + success = true; } - verifyAndAddMeasurementResult(TALLY_EVENTS_VAR_NUM, events); + // Clear count value regardless of read success + counter_internal.Clear(); - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + MS_DBG(F(" Events:"), events); - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/TallyCounterI2C.h b/src/sensors/TallyCounterI2C.h index c603f1b41..d8c3b0669 100644 --- a/src/sensors/TallyCounterI2C.h +++ b/src/sensors/TallyCounterI2C.h @@ -96,6 +96,16 @@ /** @ingroup sensor_tally */ /**@{*/ +/** + * @anchor sensor_tally_config + * @name Configuration Defines + * Defines to set the address of the Tally event counter. + */ +/**@{*/ +/// @brief The default address of the Tally +#define TALLY_ADDRESS_BASE 0x33 +/**@}*/ + /** * @anchor sensor_tally_var_counts * @name Sensor Variable Counts @@ -108,16 +118,6 @@ #define TALLY_INC_CALC_VARIABLES 0 /**@}*/ -/** - * @anchor sensor_tally_config - * @name Configuration Defines - * Defines to set the address of the Tally event counter. - */ -/**@{*/ -/// @brief The default address of the Tally -#define TALLY_ADDRESS_BASE 0x33 -/**@}*/ - /** * @anchor sensor_tally_timing * @name Sensor Timing @@ -144,10 +144,14 @@ * - For wind, we often use [Inspeed WS2R Version II Reed Switch Anemometer] * (https://www.store.inspeed.com/Inspeed-Version-II-Reed-Switch-Anemometer-Sensor-Only-WS2R.htm) * + * We do not set a specific maximum for this variable. + * * {{ @ref TallyCounterI2C_Events::TallyCounterI2C_Events }} */ /**@{*/ -/// @brief Decimals places in string representation; events are an integer +/// @brief Minimum number of events. +#define TALLY_EVENTS_MIN_COUNT 0 +/// @brief Decimal places in string representation; events are an integer /// should be 0 - resolution is 1 event. #define TALLY_EVENTS_RESOLUTION 0 /// @brief Sensor variable number; events is stored in sensorValues[0]. @@ -203,7 +207,7 @@ class TallyCounterI2C : public Sensor { /** * @brief Destroy the Tally Counter object */ - ~TallyCounterI2C(); + ~TallyCounterI2C() override = default; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -215,18 +219,11 @@ class TallyCounterI2C : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; - /** - * @copydoc Sensor::getSensorLocation() - */ + bool setup() override; - String getSensorLocation(void) override; + String getSensorLocation() override; - // bool startSingleMeasurement(void) override; // for forced mode - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; private: /** @@ -264,23 +261,13 @@ class TallyCounterI2C_Events : public Variable { explicit TallyCounterI2C_Events( TallyCounterI2C* parentSense, const char* uuid = "", const char* varCode = TALLY_EVENTS_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TALLY_EVENTS_VAR_NUM, - (uint8_t)TALLY_EVENTS_RESOLUTION, TALLY_EVENTS_VAR_NAME, - TALLY_EVENTS_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TallyCounterI2C_Events object. - * - * @note This must be tied with a parent TallyCounterI2C before it can be - * used. - */ - TallyCounterI2C_Events() - : Variable((uint8_t)TALLY_EVENTS_VAR_NUM, - (uint8_t)TALLY_EVENTS_RESOLUTION, TALLY_EVENTS_VAR_NAME, - TALLY_EVENTS_UNIT_NAME, TALLY_EVENTS_DEFAULT_CODE) {} + : Variable(parentSense, TALLY_EVENTS_VAR_NUM, TALLY_EVENTS_RESOLUTION, + TALLY_EVENTS_VAR_NAME, TALLY_EVENTS_UNIT_NAME, varCode, + uuid) {} /** - * @brief Destroy the BoschBME280_Temp object - no action needed. + * @brief Destroy the TallyCounterI2C_Events object - no action needed. */ - ~TallyCounterI2C_Events() {} + ~TallyCounterI2C_Events() override = default; }; /**@}*/ #endif // SRC_SENSORS_TallyCounterI2C_H_ diff --git a/src/sensors/TurnerCyclops.cpp b/src/sensors/TurnerCyclops.cpp index d7dd41a8f..b704d4ce6 100644 --- a/src/sensors/TurnerCyclops.cpp +++ b/src/sensors/TurnerCyclops.cpp @@ -10,111 +10,107 @@ #include "TurnerCyclops.h" -#include +#include "TIADS1x15.h" // The constructor - need the power pin, the data pin, and the calibration info -TurnerCyclops::TurnerCyclops(int8_t powerPin, uint8_t adsChannel, +TurnerCyclops::TurnerCyclops(int8_t powerPin, int8_t analogChannel, float conc_std, float volt_std, float volt_blank, - uint8_t i2cAddress, uint8_t measurementsToAverage) + uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("TurnerCyclops", CYCLOPS_NUM_VARIABLES, CYCLOPS_WARM_UP_TIME_MS, CYCLOPS_STABILIZATION_TIME_MS, CYCLOPS_MEASUREMENT_TIME_MS, - powerPin, -1, measurementsToAverage, CYCLOPS_INC_CALC_VARIABLES), - _adsChannel(adsChannel), + powerPin, analogChannel, measurementsToAverage, + CYCLOPS_INC_CALC_VARIABLES), _conc_std(conc_std), _volt_std(volt_std), _volt_blank(volt_blank), - _i2cAddress(i2cAddress) {} + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new TIADS1x15Reader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} + // Destructor -TurnerCyclops::~TurnerCyclops() {} - - -String TurnerCyclops::getSensorLocation(void) { -#ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); -#else - String sensorLocation = F("ADS1015_0x"); -#endif - sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("_Channel"); - sensorLocation += String(_adsChannel); - return sensorLocation; +TurnerCyclops::~TurnerCyclops() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String TurnerCyclops::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + // Set the reference channel to -1 for a single-ended sensor + return _analogVoltageReader->getAnalogLocation(_dataPin, -1); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } } -bool TurnerCyclops::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float calibResult = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. -#ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version -#else - Adafruit_ADS1015 ads; // Use this for the 12-bit version -#endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - // Bump the gain up to 1x = +/- 4.096V range - // Sensor return range is 0-2.5V, but the next gain option is 2x which - // only allows up to 2.048V - ads.setGain(GAIN_ONE); - // Begin ADC - ads.begin(_i2cAddress); - - // Print out the calibration curve - MS_DBG(F(" Input calibration Curve:"), _volt_std, F("V at"), _conc_std, - F(". "), _volt_blank, F("V blank.")); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the ADC raw count - adcCounts = ads.readADC_SingleEnded(_adsChannel); - // Convert ADC raw counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_SingleEnded("), _adsChannel, F("):"), - adcVoltage); - - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - // Skip results out of range - // Apply the unique calibration curve for the given sensor - calibResult = (_conc_std / (_volt_std - _volt_blank)) * - (adcVoltage - _volt_blank); - MS_DBG(F(" calibResult:"), calibResult); - } else { // set invalid voltages back to -9999 - adcVoltage = -9999; +bool TurnerCyclops::setup() { + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); } } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); } - verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult); - verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage); + return sensorSetupSuccess && analogVoltageReaderSuccess; +} + + +bool TurnerCyclops::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } + + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } + + // Print out the calibration curve + MS_DBG(F(" Input calibration Curve:"), _volt_std, F("V at"), _conc_std, + F(". "), _volt_blank, F("V blank.")); + if (fabsf(_volt_std - _volt_blank) < CYCLOPS_CALIBRATION_EPSILON) { + MS_DBG(F("Invalid calibration: point voltage equals blank voltage")); + return finalizeMeasurementAttempt(false); + } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the single-ended analog voltage using the AnalogVoltageReader + // interface. + // NOTE: All implementations of the AnalogVoltageReader class validate both + // the input channel and the resulting voltage, so we can trust that a + // successful read will give us a valid voltage value to work with. + bool success = _analogVoltageReader->readVoltageSingleEnded(_dataPin, + adcVoltage); + if (success) { + // Apply the unique calibration curve for the given sensor + float calibResult = (_conc_std / (_volt_std - _volt_blank)) * + (adcVoltage - _volt_blank); + MS_DBG(F(" calibResult:"), calibResult); + verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult); + verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage); - if (adcVoltage < 3.6 && adcVoltage > -0.3) { - return true; } else { - return false; + MS_DBG(F(" Failed to get valid voltage from analog reader")); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/TurnerCyclops.h b/src/sensors/TurnerCyclops.h index 71d5b8cb0..abf384551 100644 --- a/src/sensors/TurnerCyclops.h +++ b/src/sensors/TurnerCyclops.h @@ -9,8 +9,6 @@ * TurnerCyclops_Turbidity and TurnerCyclops_Voltage. * * These are used for the Turner Scientific Cyclops-7F. - * - * This depends on the Adafruit ADS1X15 v2.x library */ /* clang-format off */ /** @@ -101,9 +99,28 @@ * possible. All gain settings and voltage dividers should be in place for * the calibration. * + * The units to use for the calibration point depend on the parameter being measured, + * as listed in the table below. + * + * | ID | Variable | Units | + * | --- | ---------------------------- | ----------------------------------- | + * | U | TurnerCyclops_CDOM | parts per billion (ppb) | + * | C | TurnerCyclops_Chlorophyll | micrograms per Liter (µg/L) | + * | D | TurnerCyclops_RedChlorophyll | micrograms per Liter (µg/L) | + * | F | TurnerCyclops_Fluorescein | parts per billion (ppb) | + * | O | TurnerCyclops_CrudeOil | parts per billion (ppb) | + * | G | TurnerCyclops_BTEX | parts per million (ppm) | + * | B | TurnerCyclops_Brighteners | parts per billion (ppb) | + * | P | TurnerCyclops_Phycocyanin | parts per billion (ppb) | + * | E | TurnerCyclops_Phycoerythrin | parts per billion (ppb) | + * | A | TurnerCyclops_PTSA | parts per billion (ppb) | + * | R | TurnerCyclops_Rhodamine | parts per billion (ppb) | + * | L | TurnerCyclops_Tryptophan | parts per billion (ppb) | + * | T | TurnerCyclops_Turbidity | nephelometric turbidity units (NTU) | + * * Before applying any calibration, the analog output from the Cyclops-7F * must be converted into a high resolution digital signal. See the - * [ADS1115 page](@ref analog_group) for details on the conversion. + * [analog group](@ref analog_group) for details on supported ADC backends. * * @section sensor_cyclops_datasheet Sensor Datasheet * - [Main Information Page](https://www.turnerdesigns.com/cyclops-7f-submersible-fluorometer) @@ -111,8 +128,8 @@ * - [Manual](http://docs.turnerdesigns.com/t2/doc/manuals/998-2100.pdf) * * @section sensor_cyclops_flags Build flags - * - ```-D MS_USE_ADS1015``` - * - switches from the 16-bit ADS1115 to the 12 bit ADS1015 + * - `-D CYCLOPS_CALIBRATION_EPSILON=x.xf` + * - Sets the tolerance for validating the calibration values * * @section sensor_cyclops_ctor Sensor Constructor * {{ @ref TurnerCyclops::TurnerCyclops }} @@ -149,10 +166,36 @@ #include "VariableBase.h" #include "SensorBase.h" +// Forward declaration +class AnalogVoltageReader; + // Sensor Specific Defines /** @ingroup sensor_cyclops */ /**@{*/ +/** + * @anchor sensor_cyclops_config + * @name Configuration Parameters + * Configuration parameters for the Turner Cyclops-7F sensor + */ +/**@{*/ +#if !defined(CYCLOPS_CALIBRATION_EPSILON) || defined(DOXYGEN) +/** + * @brief Minimum voltage difference threshold for calibration validation + * + * This epsilon value is used to validate that the calibration standard voltage + * and blank voltage are sufficiently different to provide a meaningful + * calibration. If the absolute difference between these voltages is less than + * this threshold, the calibration is considered invalid. + * + * @note This should be tuned to match the expected precision of the sensor + * and ADC system. A value of 1e-4f (0.0001V or 0.1mV) is appropriate for + * most high-precision ADC configurations. + */ +#define CYCLOPS_CALIBRATION_EPSILON 1e-4f +#endif +/**@}*/ + /** * @anchor sensor_cyclops_var_counts * @name Sensor Variable Counts @@ -175,16 +218,6 @@ #define CYCLOPS_INC_CALC_VARIABLES 1 /**@}*/ -/** - * @anchor sensor_cyclops_config - * @name Configuration Defines - * Defines to help configure the address of the ADD used by the Cyclops - */ -/**@{*/ -/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 -/**@}*/ - /** * @anchor sensor_cyclops_timing * @name Sensor Timing @@ -222,10 +255,10 @@ /// Variable number; the primary variable is stored in sensorValues[0]. #define CYCLOPS_VAR_NUM 0 #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; 1. +/// @brief Decimal places in string representation; 1. #define CYCLOPS_RESOLUTION 1 #else -/// @brief Decimals places in string representation; 5. +/// @brief Decimal places in string representation; 5. #define CYCLOPS_RESOLUTION 5 #endif /**@}*/ @@ -246,6 +279,10 @@ * {{ @ref TurnerCyclops_Voltage::TurnerCyclops_Voltage }} */ /**@{*/ +/// @brief Minimum voltage; 0 V +#define CYCLOPS_VOLTAGE_MIN_V 0 +/// @brief Maximum voltage; 3.6 V (when using ADS1x15 powered at 3.3V) +#define CYCLOPS_VOLTAGE_MAX_V 3.6 /// Variable number; voltage is stored in sensorValues[1]. #define CYCLOPS_VOLTAGE_VAR_NUM 1 /// @brief Variable name in @@ -259,18 +296,276 @@ #define CYCLOPS_VOLTAGE_DEFAULT_CODE "CyclopsVoltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1. +/// @brief Decimal places in string representation; voltage should have 1. /// - Resolution: /// - 16-bit ADC (ADS1115): 0.125 mV #define CYCLOPS_VOLTAGE_RESOLUTION 1 #else -/// @brief Decimals places in string representation; voltage should have 4. +/// @brief Decimal places in string representation; voltage should have 4. /// - Resolution: /// - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): 2 mV #define CYCLOPS_VOLTAGE_RESOLUTION 4 #endif /**@}*/ +/** + * @anchor sensor_cyclops_chlorophyll + * @name Chlorophyll (Blue Excitation) + * The chlorophyll variable from a Turner Cyclops-7F configured for blue + * excitation + * - Range is 0 to 500 μg/L + * - Detection limit is 0.03 μg/L + */ +/**@{*/ +/// @brief Minimum chlorophyll concentration; 0 μg/L +#define CYCLOPS_CHLOROPHYLL_MIN_UGPL 0 +/// @brief Maximum chlorophyll concentration; 500 μg/L +#define CYCLOPS_CHLOROPHYLL_MAX_UGPL 500 +/// @brief Variable name in ODM2 controlled vocabulary; +/// "chlorophyllFluorescence" +#define CYCLOPS_CHLOROPHYLL_VAR_NAME "chlorophyllFluorescence" +/// @brief Variable unit name in ODM2 controlled vocabulary; "microgramPerLiter" +#define CYCLOPS_CHLOROPHYLL_UNIT_NAME "microgramPerLiter" +/// @brief Default variable short code; "CyclopsChlorophyll" +#define CYCLOPS_CHLOROPHYLL_DEFAULT_CODE "CyclopsChlorophyll" +/**@}*/ + +/** + * @anchor sensor_cyclops_rhodamine + * @name Rhodamine WT + * The rhodamine variable from a Turner Cyclops-7F configured for Rhodamine WT + * - Range is 0 to 1,000 ppb + * - Detection limit is 0.01 ppb + */ +/**@{*/ +/// @brief Minimum rhodamine concentration; 0 ppb +#define CYCLOPS_RHODAMINE_MIN_PPB 0 +/// @brief Maximum rhodamine concentration; 1,000 ppb +#define CYCLOPS_RHODAMINE_MAX_PPB 1000 +/// @brief Variable name in ODM2 controlled vocabulary; "RhodamineFluorescence" +#define CYCLOPS_RHODAMINE_VAR_NAME "RhodamineFluorescence" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_RHODAMINE_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsRhodamine" +#define CYCLOPS_RHODAMINE_DEFAULT_CODE "CyclopsRhodamine" +/**@}*/ + +/** + * @anchor sensor_cyclops_fluorescein + * @name Fluorescein + * The fluorescein variable from a Turner Cyclops-7F configured for fluorescein + * - Range is 0 to 500 ppb + * - Detection limit is 0.01 ppb + */ +/**@{*/ +/// @brief Minimum fluorescein concentration; 0 ppb +#define CYCLOPS_FLUORESCEIN_MIN_PPB 0 +/// @brief Maximum fluorescein concentration; 500 ppb +#define CYCLOPS_FLUORESCEIN_MAX_PPB 500 +/// @brief Variable name in ODM2 controlled vocabulary; "fluorescein" +#define CYCLOPS_FLUORESCEIN_VAR_NAME "fluorescein" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_FLUORESCEIN_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsFluorescein" +#define CYCLOPS_FLUORESCEIN_DEFAULT_CODE "CyclopsFluorescein" +/**@}*/ + +/** + * @anchor sensor_cyclops_phycocyanin + * @name Phycocyanin + * The phycocyanin variable from a Turner Cyclops-7F configured for phycocyanin + * - Range is 0 to 4,500 ppbPC + * - Detection limit is 2 ppbPC + */ +/**@{*/ +/// @brief Minimum phycocyanin concentration; 0 ppb +#define CYCLOPS_PHYCOCYANIN_MIN_PPB 0 +/// @brief Maximum phycocyanin concentration; 4,500 ppb +#define CYCLOPS_PHYCOCYANIN_MAX_PPB 4500 +/// @brief Variable name in ODM2 controlled vocabulary; +/// "blue_GreenAlgae_Cyanobacteria_Phycocyanin" +#define CYCLOPS_PHYCOCYANIN_VAR_NAME "blue_GreenAlgae_Cyanobacteria_Phycocyanin" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_PHYCOCYANIN_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsPhycocyanin" +#define CYCLOPS_PHYCOCYANIN_DEFAULT_CODE "CyclopsPhycocyanin" +/**@}*/ + +/** + * @anchor sensor_cyclops_phycoerythrin + * @name Phycoerythrin + * The phycoerythrin variable from a Turner Cyclops-7F configured for + * phycoerythrin + * - Range is 0 to 750 ppbPE + * - Detection limit is 0.1 ppbPE + */ +/**@{*/ +/// @brief Minimum phycoerythrin concentration; 0 ppb +#define CYCLOPS_PHYCOERYTHRIN_MIN_PPB 0 +/// @brief Maximum phycoerythrin concentration; 750 ppb +#define CYCLOPS_PHYCOERYTHRIN_MAX_PPB 750 +/// @brief Variable name in ODM2 controlled vocabulary; "phycoerythrin" +#define CYCLOPS_PHYCOERYTHRIN_VAR_NAME "phycoerythrin" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_PHYCOERYTHRIN_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsPhycoerythrin" +#define CYCLOPS_PHYCOERYTHRIN_DEFAULT_CODE "CyclopsPhycoerythrin" +/**@}*/ + +/** + * @anchor sensor_cyclops_cdom + * @name CDOM/fDOM + * The CDOM/fDOM variable from a Turner Cyclops-7F configured for CDOM + * - Range is 0 to 1,500 ppb (or 0 to 3,000 ppb depending on configuration) + * - Detection limit is 0.1 ppb (or 0.5 ppb) + */ +/**@{*/ +/// @brief Minimum CDOM concentration; 0 ppb +#define CYCLOPS_CDOM_MIN_PPB 0 +/// @brief Maximum CDOM concentration; 3,000 ppb +#define CYCLOPS_CDOM_MAX_PPB 3000 +/// @brief Variable name in ODM2 controlled vocabulary; +/// "fluorescenceDissolvedOrganicMatter" +#define CYCLOPS_CDOM_VAR_NAME "fluorescenceDissolvedOrganicMatter" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_CDOM_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsCDOM" +#define CYCLOPS_CDOM_DEFAULT_CODE "CyclopsCDOM" +/**@}*/ + +/** + * @anchor sensor_cyclops_crudeoil + * @name Crude Oil + * The crude oil variable from a Turner Cyclops-7F configured for crude oil + * - Range is 0 to 1,500 ppb + * - Detection limit is 0.2 ppb + */ +/**@{*/ +/// @brief Minimum crude oil concentration; 0 ppb +#define CYCLOPS_CRUDE_OIL_MIN_PPB 0 +/// @brief Maximum crude oil concentration; 1,500 ppb +#define CYCLOPS_CRUDE_OIL_MAX_PPB 1500 +/// @brief Variable name in ODM2 controlled vocabulary; +/// "petroleumHydrocarbonTotal" +#define CYCLOPS_CRUDE_OIL_VAR_NAME "petroleumHydrocarbonTotal" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_CRUDE_OIL_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsCrudeOil" +#define CYCLOPS_CRUDE_OIL_DEFAULT_CODE "CyclopsCrudeOil" +/**@}*/ + +/** + * @anchor sensor_cyclops_brighteners + * @name Optical Brighteners + * The optical brighteners variable from a Turner Cyclops-7F configured for + * optical brighteners + * + * @todo Find and define minimum and maximum optical brightener measurement + * range on a Turner Cyclops-7F. + */ +/**@{*/ +/// @brief Variable name in ODM2 controlled vocabulary; "opticalBrighteners" +#define CYCLOPS_BRIGHTENERS_VAR_NAME "opticalBrighteners" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_BRIGHTENERS_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsOpticalBrighteners" +#define CYCLOPS_BRIGHTENERS_DEFAULT_CODE "CyclopsOpticalBrighteners" +/**@}*/ + +/** + * @anchor sensor_cyclops_turbidity + * @name Turbidity + * The turbidity variable from a Turner Cyclops-7F configured for turbidity + * - Range is 0 to 1,500 NTU + * - Detection limit is 0.05 NTU + */ +/**@{*/ +/// @brief Minimum turbidity; 0 NTU +#define CYCLOPS_TURBIDITY_MIN_NTU 0 +/// @brief Maximum turbidity; 1,500 NTU +#define CYCLOPS_TURBIDITY_MAX_NTU 1500 +/// @brief Variable name in ODM2 controlled vocabulary; "Turbidity" +#define CYCLOPS_TURBIDITY_VAR_NAME "Turbidity" +/// @brief Variable unit name in ODM2 controlled vocabulary; +/// "nephelometricTurbidityUnit" +#define CYCLOPS_TURBIDITY_UNIT_NAME "nephelometricTurbidityUnit" +/// @brief Default variable short code; "CyclopsTurbidity" +#define CYCLOPS_TURBIDITY_DEFAULT_CODE "CyclopsTurbidity" +/**@}*/ + +/** + * @anchor sensor_cyclops_ptsa + * @name PTSA + * The PTSA variable from a Turner Cyclops-7F configured for PTSA + * + * @todo Find and define minimum and maximum PTSA measurement range on a Turner + * Cyclops-7F. + */ +/**@{*/ +/// @brief Variable name in ODM2 controlled vocabulary; "ptsa" +#define CYCLOPS_PTSA_VAR_NAME "ptsa" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_PTSA_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsPTSA" +#define CYCLOPS_PTSA_DEFAULT_CODE "CyclopsPTSA" +/**@}*/ + +/** + * @anchor sensor_cyclops_btex + * @name BTEX + * The BTEX variable from a Turner Cyclops-7F configured for BTEX + * + * @todo Find and define minimum and maximum BTEX measurement range on a Turner + * Cyclops-7F. + */ +/**@{*/ +/// @brief Variable name in ODM2 controlled vocabulary; "btex" +#define CYCLOPS_BTEX_VAR_NAME "btex" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerMillion" +#define CYCLOPS_BTEX_UNIT_NAME "partPerMillion" +/// @brief Default variable short code; "CyclopsBTEX" +#define CYCLOPS_BTEX_DEFAULT_CODE "CyclopsBTEX" +/**@}*/ + +/** + * @anchor sensor_cyclops_tryptophan + * @name Tryptophan + * The tryptophan variable from a Turner Cyclops-7F configured for tryptophan + * + * @todo Find and define minimum and maximum tryptophan measurement range on a + * Turner Cyclops-7F. + */ +/**@{*/ +/// @brief Variable name in ODM2 controlled vocabulary; "tryptophan" +#define CYCLOPS_TRYPTOPHAN_VAR_NAME "tryptophan" +/// @brief Variable unit name in ODM2 controlled vocabulary; "partPerBillion" +#define CYCLOPS_TRYPTOPHAN_UNIT_NAME "partPerBillion" +/// @brief Default variable short code; "CyclopsTryptophan" +#define CYCLOPS_TRYPTOPHAN_DEFAULT_CODE "CyclopsTryptophan" +/**@}*/ + +/** + * @anchor sensor_cyclops_redchlorophyll + * @name Chlorophyll (Red Excitation) + * The chlorophyll variable from a Turner Cyclops-7F configured for red + * excitation (high CDOM environments) + * - Range is 0 to 500 μg/L + * - Detection limit is 0.3 μg/L + */ +/**@{*/ +/// @brief Minimum red chlorophyll concentration; 0 μg/L +#define CYCLOPS_RED_CHLOROPHYLL_MIN_UGPL 0 +/// @brief Maximum red chlorophyll concentration; 500 μg/L +#define CYCLOPS_RED_CHLOROPHYLL_MAX_UGPL 500 +/// @brief Variable name in ODM2 controlled vocabulary; +/// "chlorophyllFluorescence" +#define CYCLOPS_RED_CHLOROPHYLL_VAR_NAME "chlorophyllFluorescence" +/// @brief Variable unit name in ODM2 controlled vocabulary; "microgramPerLiter" +#define CYCLOPS_RED_CHLOROPHYLL_UNIT_NAME "microgramPerLiter" +/// @brief Default variable short code; "CyclopsRedChlorophyll" +#define CYCLOPS_RED_CHLOROPHYLL_DEFAULT_CODE "CyclopsRedChlorophyll" +/**@}*/ + /* clang-format off */ /** * @brief The Sensor sub-class for the @@ -281,80 +576,76 @@ /* clang-format on */ class TurnerCyclops : public Sensor { public: - // The constructor - need the power pin, the ADS1X15 data channel, and the - // calibration info - /* clang-format off */ /** * @brief Construct a new Turner Cyclops object - need the power pin, the - * ADS1X15 data channel, and the calibration info. + * analog data channel, and the calibration info. * - * @note ModularSensors only supports connecting the ADS1x15 to the primary - * hardware I2C instance defined in the Arduino core. Connecting the ADS to - * a secondary hardware or software I2C instance is *not* supported! + * By default, this constructor will internally create a default + * AnalogVoltageReader implementation for voltage readings, but a pointer to + * a custom AnalogVoltageReader object can be passed in if desired. * * @param powerPin The pin on the mcu controlling power to the Cyclops-7F * Use -1 if it is continuously powered. - * - The ADS1x15 requires an input voltage of 2.0-5.5V, but this library - * assumes the ADS is powered with 3.3V. * - The Cyclops-7F itself requires a 3-15V power supply, which can be * turned off between measurements. - * @param adsChannel The analog data channel _on the TI ADS1115_ that the - * Cyclops is connected to (0-3). + * @param analogChannel The analog data channel or processor pin for voltage + * measurements. The significance of the channel number depends on the + * specific AnalogVoltageReader implementation used for voltage readings. + * For example, with the TI ADS1x15, this would be the ADC channel (0-3) + * that the sensor is connected to. Negative or invalid channel numbers are + * not clamped and will cause the reading to fail and emit a warning. * @param conc_std The concentration of the standard used for a 1-point * sensor calibration. The concentration units should be the same as the * final measuring units. - * | ID | Variable | Units | - * | --- | ---------------------------- | ----------------------------------- | - * | C | TurnerCyclops_Chlorophyll | micrograms per Liter (µg/L) | - * | R | TurnerCyclops_Rhodamine | parts per billion (ppb) | - * | F | TurnerCyclops_Fluorescein | parts per billion (ppb) | - * | P | TurnerCyclops_Phycocyanin | parts per billion (ppb) | - * | E | TurnerCyclops_Phycoerythrin | parts per billion (ppb) | - * | U | TurnerCyclops_CDOM | parts per billion (ppb) | - * | O | TurnerCyclops_CrudeOil | parts per billion (ppb) | - * | B | TurnerCyclops_Brighteners | parts per billion (ppb) | - * | T | TurnerCyclops_Turbidity | nephelometric turbidity units (NTU) | - * | A | TurnerCyclops_PTSA | parts per billion (ppb) | - * | G | TurnerCyclops_BTEX | parts per million (ppm) | - * | L | TurnerCyclops_Tryptophan | parts per billion (ppb) | - * | D | TurnerCyclops_RedChlorophyll | micrograms per Liter (µg/L) | * @param volt_std The voltage (in volts) measured for the conc_std. This * voltage should be the final voltage *after* accounting for any voltage * dividers or gain settings. * @param volt_blank The voltage (in volts) measured for a blank. This * voltage should be the final voltage *after* accounting for any voltage * dividers or gain settings. - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses a TI ADS1115 or ADS1015. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. + * + * @warning In library versions 0.37.0 and earlier, a different constructor + * was used that the I2C address of the ADS1x15 was an optional input + * parameter which came *before* the optional input parameter for the number + * of measurements to average. The input parameter for the I2C address has + * been *removed* and the input for the number of measurements to average + * has been moved up in the order! Please update your code to prevent a + * compiler error or a silent reading error. */ - /* clang-format on */ - TurnerCyclops(int8_t powerPin, uint8_t adsChannel, float conc_std, + TurnerCyclops(int8_t powerPin, int8_t analogChannel, float conc_std, float volt_std, float volt_blank, - uint8_t i2cAddress = ADS1115_ADDRESS, - uint8_t measurementsToAverage = 1); + uint8_t measurementsToAverage = 1, + AnalogVoltageReader* analogVoltageReader = nullptr); /** * @brief Destroy the Turner Cyclops object */ - ~TurnerCyclops(); + ~TurnerCyclops() override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + TurnerCyclops(const TurnerCyclops&) = delete; + TurnerCyclops& operator=(const TurnerCyclops&) = delete; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + // Delete move constructor and move assignment operator + TurnerCyclops(TurnerCyclops&&) = delete; + TurnerCyclops& operator=(TurnerCyclops&&) = delete; + + String getSensorLocation() override; + + bool setup() override; + + bool addSingleMeasurementResult() override; private: - /** - * @brief Internal reference to the ADS channel number of the Turner Cyclops - */ - uint8_t _adsChannel; /** * @brief The concentration of the standard used for a 1-point sensor * calibration. The concentration units should be the same as the final @@ -373,10 +664,11 @@ class TurnerCyclops : public Sensor { * settings. */ float _volt_blank; - /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 - */ - uint8_t _i2cAddress; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -408,25 +700,13 @@ class TurnerCyclops_Voltage : public Variable { explicit TurnerCyclops_Voltage( TurnerCyclops* parentSense, const char* uuid = "", const char* varCode = CYCLOPS_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)CYCLOPS_VOLTAGE_VAR_NUM, - (uint8_t)CYCLOPS_VOLTAGE_RESOLUTION, - CYCLOPS_VOLTAGE_VAR_NAME, CYCLOPS_VOLTAGE_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new TurnerCyclops_Voltage object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Voltage() - : Variable((uint8_t)CYCLOPS_VOLTAGE_VAR_NUM, - (uint8_t)CYCLOPS_VOLTAGE_RESOLUTION, - CYCLOPS_VOLTAGE_VAR_NAME, CYCLOPS_VOLTAGE_UNIT_NAME, - CYCLOPS_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, CYCLOPS_VOLTAGE_VAR_NUM, + CYCLOPS_VOLTAGE_RESOLUTION, CYCLOPS_VOLTAGE_VAR_NAME, + CYCLOPS_VOLTAGE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Voltage object - no action needed. */ - ~TurnerCyclops_Voltage() {} + ~TurnerCyclops_Voltage() override = default; }; @@ -466,25 +746,15 @@ class TurnerCyclops_Chlorophyll : public Variable { */ explicit TurnerCyclops_Chlorophyll( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsChlorophyll") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "chlorophyllFluorescence", - "microgramPerLiter", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Chlorophyll object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Chlorophyll() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "chlorophyllFluorescence", "microgramPerLiter", - "CyclopsChlorophyll") {} + const char* varCode = CYCLOPS_CHLOROPHYLL_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_CHLOROPHYLL_VAR_NAME, CYCLOPS_CHLOROPHYLL_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Chlorophyll variable object - no action * needed. */ - ~TurnerCyclops_Chlorophyll() {} + ~TurnerCyclops_Chlorophyll() override = default; }; @@ -521,27 +791,17 @@ class TurnerCyclops_Rhodamine : public Variable { * @param varCode A short code to help identify the variable in files; * optional with a default value of "CyclopsRhodamine". */ - explicit TurnerCyclops_Rhodamine(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsRhodamine") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "RhodamineFluorescence", - "partPerBillion", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Rhodamine object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Rhodamine() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "RhodamineFluorescence", "partPerBillion", - "CyclopsRhodamine") {} + explicit TurnerCyclops_Rhodamine( + TurnerCyclops* parentSense, const char* uuid = "", + const char* varCode = CYCLOPS_RHODAMINE_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_RHODAMINE_VAR_NAME, CYCLOPS_RHODAMINE_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Rhodamine variable object - no action * needed. */ - ~TurnerCyclops_Rhodamine() {} + ~TurnerCyclops_Rhodamine() override = default; }; @@ -580,24 +840,15 @@ class TurnerCyclops_Fluorescein : public Variable { */ explicit TurnerCyclops_Fluorescein( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsFluorescein") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "fluorescein", "partPerBillion", + const char* varCode = CYCLOPS_FLUORESCEIN_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_FLUORESCEIN_VAR_NAME, CYCLOPS_FLUORESCEIN_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Fluorescein object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Fluorescein() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "fluorescein", "partPerBillion", "CyclopsFluorescein") {} /** * @brief Destroy the Turner Cyclops Fluorescein variable object - no action * needed. */ - ~TurnerCyclops_Fluorescein() {} + ~TurnerCyclops_Fluorescein() override = default; }; @@ -637,26 +888,15 @@ class TurnerCyclops_Phycocyanin : public Variable { */ explicit TurnerCyclops_Phycocyanin( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsPhycocyanin") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, - "blue_GreenAlgae_Cyanobacteria_Phycocyanin", - "partPerBillion", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Phycocyanin object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Phycocyanin() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "blue_GreenAlgae_Cyanobacteria_Phycocyanin", - "partPerBillion", "CyclopsPhycocyanin") {} + const char* varCode = CYCLOPS_PHYCOCYANIN_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_PHYCOCYANIN_VAR_NAME, CYCLOPS_PHYCOCYANIN_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Phycocyanin variable object - no action * needed. */ - ~TurnerCyclops_Phycocyanin() {} + ~TurnerCyclops_Phycocyanin() override = default; }; @@ -696,24 +936,15 @@ class TurnerCyclops_Phycoerythrin : public Variable { */ explicit TurnerCyclops_Phycoerythrin( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsPhycoerythrin") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "phycoerythrin", - "partPerBillion", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Phycoerythrin object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Phycoerythrin() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "phycoerythrin", "partPerBillion", "CyclopsPhycoerythrin") {} + const char* varCode = CYCLOPS_PHYCOERYTHRIN_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_PHYCOERYTHRIN_VAR_NAME, + CYCLOPS_PHYCOERYTHRIN_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Phycoerythrin variable object - no * action needed. */ - ~TurnerCyclops_Phycoerythrin() {} + ~TurnerCyclops_Phycoerythrin() override = default; }; @@ -755,27 +986,16 @@ class TurnerCyclops_CDOM : public Variable { * optional with a default value of "CyclopsCDOM". */ explicit TurnerCyclops_CDOM(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsCDOM") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, - "fluorescenceDissolvedOrganicMatter", "partPerBillion", - varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_CDOM object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_CDOM() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "fluorescenceDissolvedOrganicMatter", "partPerBillion", - "CyclopsCDOM") {} + const char* uuid = "", + const char* varCode = CYCLOPS_CDOM_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_CDOM_VAR_NAME, CYCLOPS_CDOM_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the Turner Cyclops CDOM variable object - no action * needed. */ - ~TurnerCyclops_CDOM() {} + ~TurnerCyclops_CDOM() override = default; }; @@ -814,27 +1034,17 @@ class TurnerCyclops_CrudeOil : public Variable { * @param varCode A short code to help identify the variable in files; * optional with a default value of "CyclopsCrudeOil". */ - explicit TurnerCyclops_CrudeOil(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsCrudeOil") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "petroleumHydrocarbonTotal", - "partPerBillion", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_CrudeOil object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_CrudeOil() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "petroleumHydrocarbonTotal", "partPerBillion", - "CyclopsCrudeOil") {} + explicit TurnerCyclops_CrudeOil( + TurnerCyclops* parentSense, const char* uuid = "", + const char* varCode = CYCLOPS_CRUDE_OIL_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_CRUDE_OIL_VAR_NAME, CYCLOPS_CRUDE_OIL_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops CrudeOil variable object - no action * needed. */ - ~TurnerCyclops_CrudeOil() {} + ~TurnerCyclops_CrudeOil() override = default; }; @@ -876,24 +1086,14 @@ class TurnerCyclops_Brighteners : public Variable { */ explicit TurnerCyclops_Brighteners( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsOpticalBrighteners") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "opticalBrighteners", - "partPerBillion", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Brighteners object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Brighteners() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "opticalBrighteners", "partPerBillion", - "CyclopsOpticalBrighteners") {} + const char* varCode = CYCLOPS_BRIGHTENERS_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_BRIGHTENERS_VAR_NAME, CYCLOPS_BRIGHTENERS_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Brighteners object - no action needed. */ - ~TurnerCyclops_Brighteners() {} + ~TurnerCyclops_Brighteners() override = default; }; @@ -930,27 +1130,17 @@ class TurnerCyclops_Turbidity : public Variable { * @param varCode A short code to help identify the variable in files; * optional with a default value of "CyclopsTurbidity". */ - explicit TurnerCyclops_Turbidity(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsTurbidity") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "Turbidity", - "nephelometricTurbidityUnit", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Turbidity object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Turbidity() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "Turbidity", "nephelometricTurbidityUnit", - "CyclopsTurbidity") {} + explicit TurnerCyclops_Turbidity( + TurnerCyclops* parentSense, const char* uuid = "", + const char* varCode = CYCLOPS_TURBIDITY_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_TURBIDITY_VAR_NAME, CYCLOPS_TURBIDITY_UNIT_NAME, + varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Turbidity variable object - no action * needed. */ - ~TurnerCyclops_Turbidity() {} + ~TurnerCyclops_Turbidity() override = default; }; @@ -989,25 +1179,16 @@ class TurnerCyclops_PTSA : public Variable { * optional with a default value of "CyclopsPTSA". */ explicit TurnerCyclops_PTSA(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsPTSA") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "ptsa", "partPerBillion", - varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_PTSA object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_PTSA() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "ptsa", "partPerBillion", "CyclopsPTSA") {} + const char* uuid = "", + const char* varCode = CYCLOPS_PTSA_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_PTSA_VAR_NAME, CYCLOPS_PTSA_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the Turner Cyclops PTSA variable object - no action * needed. */ - ~TurnerCyclops_PTSA() {} + ~TurnerCyclops_PTSA() override = default; }; @@ -1046,25 +1227,16 @@ class TurnerCyclops_BTEX : public Variable { * optional with a default value of "CyclopsBTEX". */ explicit TurnerCyclops_BTEX(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsBTEX") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "btex", "partPerMillion", - varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_BTEX object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_BTEX() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "btex", "partPerMillion", "CyclopsBTEX") {} + const char* uuid = "", + const char* varCode = CYCLOPS_BTEX_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_BTEX_VAR_NAME, CYCLOPS_BTEX_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the Turner Cyclops BTEX variable object - no action * needed. */ - ~TurnerCyclops_BTEX() {} + ~TurnerCyclops_BTEX() override = default; }; @@ -1101,26 +1273,17 @@ class TurnerCyclops_Tryptophan : public Variable { * @param varCode A short code to help identify the variable in files; * optional with a default value of "CyclopsTryptophan". */ - explicit TurnerCyclops_Tryptophan(TurnerCyclops* parentSense, - const char* uuid = "", - const char* varCode = "CyclopsTryptophan") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "tryptophan", "partPerBillion", + explicit TurnerCyclops_Tryptophan( + TurnerCyclops* parentSense, const char* uuid = "", + const char* varCode = CYCLOPS_TRYPTOPHAN_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_TRYPTOPHAN_VAR_NAME, CYCLOPS_TRYPTOPHAN_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_Tryptophan object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_Tryptophan() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "tryptophan", "partPerBillion", "CyclopsTryptophan") {} /** * @brief Destroy the Turner Cyclops Tryptophan variable object - no action * needed. */ - ~TurnerCyclops_Tryptophan() {} + ~TurnerCyclops_Tryptophan() override = default; }; @@ -1160,28 +1323,18 @@ class TurnerCyclops_RedChlorophyll : public Variable { */ explicit TurnerCyclops_RedChlorophyll( TurnerCyclops* parentSense, const char* uuid = "", - const char* varCode = "CyclopsRedChlorophyll") - : Variable(parentSense, (uint8_t)CYCLOPS_VAR_NUM, - (uint8_t)CYCLOPS_RESOLUTION, "chlorophyllFluorescence", - "microgramPerLiter", varCode, uuid) {} - /** - * @brief Construct a new TurnerCyclops_RedChlorophyll object. - * - * @note This must be tied with a parent TurnerCyclops before it can be - * used. - */ - TurnerCyclops_RedChlorophyll() - : Variable((uint8_t)CYCLOPS_VAR_NUM, (uint8_t)CYCLOPS_RESOLUTION, - "chlorophyllFluorescence", "microgramPerLiter", - "CyclopsRedChlorophyll") {} + const char* varCode = CYCLOPS_RED_CHLOROPHYLL_DEFAULT_CODE) + : Variable(parentSense, CYCLOPS_VAR_NUM, CYCLOPS_RESOLUTION, + CYCLOPS_RED_CHLOROPHYLL_VAR_NAME, + CYCLOPS_RED_CHLOROPHYLL_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the Turner Cyclops Red Chlorophyll variable object - no * action needed. */ - ~TurnerCyclops_RedChlorophyll() {} + ~TurnerCyclops_RedChlorophyll() override = default; }; /**@}*/ #endif // SRC_SENSORS_TURNERCYCLOPS_H_ -// cSpell:ignore fluorophores BTEX PTSA Pyrenetetrasulfonic Tetrasodium -// cSpell:ignore Ethylbenzene Prozyme sensor_cyclops_calib +// cSpell:words fluorophores BTEX PTSA Pyrenetetrasulfonic Tetrasodium +// cSpell:words Ethylbenzene Prozyme sensor_cyclops_calib UGPL diff --git a/src/sensors/TurnerTurbidityPlus.cpp b/src/sensors/TurnerTurbidityPlus.cpp index 19999664d..f710e7ef4 100644 --- a/src/sensors/TurnerTurbidityPlus.cpp +++ b/src/sensors/TurnerTurbidityPlus.cpp @@ -9,41 +9,47 @@ #include "TurnerTurbidityPlus.h" +#include "TIADS1x15.h" // The constructor - need the power pin, the data pin, and the calibration info TurnerTurbidityPlus::TurnerTurbidityPlus( - int8_t powerPin, int8_t wiperTriggerPin, ttp_adsDiffMux_t adsDiffMux, - float conc_std, float volt_std, float volt_blank, uint8_t i2cAddress, - adsGain_t PGA_gain, uint8_t measurementsToAverage, - float voltageDividerFactor) + int8_t powerPin, int8_t wiperTriggerPin, int8_t analogChannel, + int8_t analogReferenceChannel, float conc_std, float volt_std, + float volt_blank, uint8_t measurementsToAverage, + AnalogVoltageReader* analogVoltageReader) : Sensor("TurnerTurbidityPlus", TURBIDITY_PLUS_NUM_VARIABLES, TURBIDITY_PLUS_WARM_UP_TIME_MS, TURBIDITY_PLUS_STABILIZATION_TIME_MS, - TURBIDITY_PLUS_MEASUREMENT_TIME_MS, powerPin, -1, + TURBIDITY_PLUS_MEASUREMENT_TIME_MS, powerPin, analogChannel, measurementsToAverage), _wiperTriggerPin(wiperTriggerPin), - _adsDiffMux(adsDiffMux), _conc_std(conc_std), _volt_std(volt_std), _volt_blank(volt_blank), - _i2cAddress(i2cAddress), - _PGA_gain(PGA_gain), - _voltageDividerFactor(voltageDividerFactor) {} + _analogReferenceChannel(analogReferenceChannel), + // If no analog voltage reader was provided, create a default one + _analogVoltageReader(analogVoltageReader == nullptr + ? new TIADS1x15Reader() + : analogVoltageReader), + _ownsAnalogVoltageReader(analogVoltageReader == nullptr) {} + // Destructor -TurnerTurbidityPlus::~TurnerTurbidityPlus() {} - - -String TurnerTurbidityPlus::getSensorLocation(void) { -#ifndef MS_USE_ADS1015 - String sensorLocation = F("ADS1115_0x"); -#else - String sensorLocation = F("ADS1015_0x"); -#endif - sensorLocation += String(_i2cAddress, HEX); - sensorLocation += F("_adsDiffMux"); - sensorLocation += String(_adsDiffMux); - return sensorLocation; +TurnerTurbidityPlus::~TurnerTurbidityPlus() { + // Clean up the analog voltage reader if we created it + if (_ownsAnalogVoltageReader && _analogVoltageReader != nullptr) { + delete _analogVoltageReader; + } +} + + +String TurnerTurbidityPlus::getSensorLocation() { + if (_analogVoltageReader != nullptr) { + return _analogVoltageReader->getAnalogLocation(_dataPin, + _analogReferenceChannel); + } else { + return String(F("Unknown_AnalogVoltageReader")); + } } void TurnerTurbidityPlus::runWiper() { @@ -52,22 +58,36 @@ void TurnerTurbidityPlus::runWiper() { // without pausing for ~540ms between them. MS_DBG(F("Turn TurbidityPlus wiper on"), getSensorLocation()); digitalWrite(_wiperTriggerPin, LOW); - delay(50); + delay(TURBIDITY_PLUS_WIPER_TRIGGER_PULSE_MS); digitalWrite(_wiperTriggerPin, HIGH); // It takes ~7.5 sec for a rotation to complete. Wait for that to finish // before continuing, otherwise the sensor will get powered off before wipe // completes, and any reading taken during wiper cycle is invalid. - delay(8000); + delay(TURBIDITY_PLUS_WIPER_ROTATION_WAIT_MS); MS_DBG(F("TurbidityPlus wiper cycle should be finished")); } -bool TurnerTurbidityPlus::setup(void) { +bool TurnerTurbidityPlus::setup() { // Set up the wiper trigger pin, which is active-LOW. pinMode(_wiperTriggerPin, OUTPUT); - return Sensor::setup(); + bool sensorSetupSuccess = Sensor::setup(); + bool analogVoltageReaderSuccess = false; + + if (_analogVoltageReader != nullptr) { + analogVoltageReaderSuccess = _analogVoltageReader->begin(); + if (!analogVoltageReaderSuccess) { + MS_DBG(getSensorNameAndLocation(), + F("Analog voltage reader initialization failed")); + } + } else { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader to initialize")); + } + + return sensorSetupSuccess && analogVoltageReaderSuccess; } -bool TurnerTurbidityPlus::wake(void) { +bool TurnerTurbidityPlus::wake() { // Set the wiper trigger pin mode. // Reset this on every wake because pins are set to tri-state on sleep pinMode(_wiperTriggerPin, OUTPUT); @@ -77,106 +97,60 @@ bool TurnerTurbidityPlus::wake(void) { return Sensor::wake(); } -void TurnerTurbidityPlus::powerDown(void) { +void TurnerTurbidityPlus::powerDown() { // Set the wiper trigger pin LOW to avoid power drain. digitalWrite(_wiperTriggerPin, LOW); - return Sensor::powerDown(); + Sensor::powerDown(); } -void TurnerTurbidityPlus::powerUp(void) { +void TurnerTurbidityPlus::powerUp() { // Set the wiper trigger pin HIGH to prepare for wiping. digitalWrite(_wiperTriggerPin, HIGH); - return Sensor::powerUp(); + Sensor::powerUp(); } -bool TurnerTurbidityPlus::addSingleMeasurementResult(void) { - // Variables to store the results in - int16_t adcCounts = -9999; - float adcVoltage = -9999; - float calibResult = -9999; - - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - MS_DBG(getSensorNameAndLocation(), F("is reporting:")); - -// Create an auxiliary ADD object -// We create and set up the ADC object here so that each sensor using -// the ADC may set the gain appropriately without effecting others. -#ifndef MS_USE_ADS1015 - Adafruit_ADS1115 ads; // Use this for the 16-bit version -#else - Adafruit_ADS1015 ads; // Use this for the 12-bit version -#endif - // ADS Library default settings: - // - TI1115 (16 bit) - // - single-shot mode (powers down between conversions) - // - 128 samples per second (8ms conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - // - TI1015 (12 bit) - // - single-shot mode (powers down between conversions) - // - 1600 samples per second (625µs conversion time) - // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) - - ads.setGain(_PGA_gain); - // Begin ADC - ads.begin(_i2cAddress); - - // Print out the calibration curve - MS_DBG(F(" Input calibration Curve:"), _volt_std, F("V at"), _conc_std, - F(". "), _volt_blank, F("V blank.")); - - // Read Analog to Digital Converter (ADC) - // Taking this reading includes the 8ms conversion delay. - // Measure the voltage differential across the two voltage pins - switch (_adsDiffMux) { - case DIFF_MUX_0_1: { - adcCounts = ads.readADC_Differential_0_1(); - break; - } - case DIFF_MUX_0_3: { - adcCounts = ads.readADC_Differential_0_3(); - break; - } - case DIFF_MUX_1_3: { - adcCounts = ads.readADC_Differential_1_3(); - break; - } - case DIFF_MUX_2_3: { - adcCounts = ads.readADC_Differential_2_3(); - break; - } - } - // Convert ADC counts value to voltage (V) - adcVoltage = ads.computeVolts(adcCounts); - MS_DBG(F(" ads.readADC_Differential("), _adsDiffMux, F("):"), - String(adcVoltage, 3)); - - // The ADS1X15 outputs a max value corresponding to Vcc + 0.3V - if (adcVoltage < 5.3 && adcVoltage > -0.3) { - // Skip results out of range - // Apply the unique calibration curve for the given sensor - calibResult = (_conc_std / (_volt_std - _volt_blank)) * - (adcVoltage - _volt_blank); - MS_DBG(F(" calibResult:"), String(calibResult, 3)); - } else { // set invalid voltages back to -9999 - adcVoltage = -9999; - } - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); - } +bool TurnerTurbidityPlus::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } - verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, calibResult); - verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM, adcVoltage); + // Check if we have a valid analog voltage reader + if (_analogVoltageReader == nullptr) { + MS_DBG(getSensorNameAndLocation(), + F("No analog voltage reader available")); + return finalizeMeasurementAttempt(false); + } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); + // Print out the calibration curve + MS_DBG(F(" Input calibration Curve:"), _volt_std, F("V at"), _conc_std, + F(". "), _volt_blank, F("V blank.")); + if (fabsf(_volt_std - _volt_blank) < TURBIDITY_PLUS_CALIBRATION_EPSILON) { + MS_DBG(F("Invalid calibration: point voltage equals blank voltage")); + return finalizeMeasurementAttempt(false); + } - if (adcVoltage < 5.3 && adcVoltage > -0.3) { - return true; + float adcVoltage = MS_INVALID_VALUE; + + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + + // Read the differential voltage using the AnalogVoltageReader interface. + // NOTE: All implementations of the AnalogVoltageReader class validate both + // the input channel and the resulting voltage, so we can trust that a + // successful read will give us a valid voltage value to work with. + bool success = _analogVoltageReader->readVoltageDifferential( + _dataPin, _analogReferenceChannel, adcVoltage); + + if (success) { + // Apply the unique calibration curve for the given sensor + float calibResult = (_conc_std / (_volt_std - _volt_blank)) * + (adcVoltage - _volt_blank); + MS_DBG(F(" calibResult:"), String(calibResult, 3)); + verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM, + adcVoltage); + verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, calibResult); } else { - return false; + MS_DBG(F(" Failed to read differential voltage from analog reader")); } + + // Return success value when finished + return finalizeMeasurementAttempt(success); } diff --git a/src/sensors/TurnerTurbidityPlus.h b/src/sensors/TurnerTurbidityPlus.h index 2420f7006..c754441f6 100644 --- a/src/sensors/TurnerTurbidityPlus.h +++ b/src/sensors/TurnerTurbidityPlus.h @@ -10,8 +10,6 @@ * subclasses TurnerTurbidityPlus_Turbidity and TurnerTurbidityPlus_Voltage. * * These are used for the Turner Turbidity Plus. - * - * This depends on the Adafruit ADS1X15 v2.x library. */ /** * @defgroup sensor_turbidity_plus Turner Turbidity Plus @@ -27,16 +25,20 @@ * [Datasheet](http://docs.turnerdesigns.com/t2/doc/brochures/S-0210.pdf) * * @section sensor_turbidity_plus_flags Build flags - * - ```-D MS_USE_ADS1015``` - * - switches from the 16-bit ADS1115 to the 12 bit ADS1015 + * - ```-D TURBIDITY_PLUS_WIPER_TRIGGER_PULSE_MS=x``` + * - Changes the wiper trigger pulse duration from 50 ms to x ms + * - ```-D TURBIDITY_PLUS_WIPER_ROTATION_WAIT_MS=x``` + * - Changes the wiper rotation wait time from 8000 ms to x ms + * - ```-D TURBIDITY_PLUS_CALIBRATION_EPSILON=x``` + * - Changes the calibration validation epsilon from 1e-4 to x * * @section sensor_turbidity_plus_ctor Sensor Constructor * {{ @ref TurnerTurbidityPlus::TurnerTurbidityPlus }} * * ___ * @section sensor_turbidity_plus_examples Example Code - * The Alphasense CO2 sensor is used in the @menulink{turner_turbidity_plus} - * example. + * The Turner Turbidity Plus sensor is used in the + * @menulink{turner_turbidity_plus} example. * * @menusnip{turner_turbidity_plus} */ @@ -64,11 +66,40 @@ // Include other in-library and external dependencies #include "VariableBase.h" #include "SensorBase.h" -#include + +// Forward declaration +class AnalogVoltageReader; /** @ingroup sensor_turbidity_plus */ /**@{*/ +/** + * @anchor sensor_turbidity_plus_config + * @name Configuration Defines + * Defines to set the timing configuration of the Turner Turbidity Plus sensor. + */ +/**@{*/ +#if !defined(TURBIDITY_PLUS_WIPER_TRIGGER_PULSE_MS) || defined(DOXYGEN) +/** + * @brief Wiper trigger pulse duration in milliseconds + */ +#define TURBIDITY_PLUS_WIPER_TRIGGER_PULSE_MS 50 +#endif +#if !defined(TURBIDITY_PLUS_WIPER_ROTATION_WAIT_MS) || defined(DOXYGEN) +/** + * @brief Wait time for wiper rotation to complete in milliseconds + */ +#define TURBIDITY_PLUS_WIPER_ROTATION_WAIT_MS 8000 +#endif +#if !defined(TURBIDITY_PLUS_CALIBRATION_EPSILON) || defined(DOXYGEN) +/** + * @brief Epsilon value for calibration validation to detect invalid calibration + * curves + */ +#define TURBIDITY_PLUS_CALIBRATION_EPSILON 1e-4f +#endif +/**@}*/ + /** * @anchor sensor_turbidity_plus_var_counts * @name Sensor Variable Counts @@ -84,25 +115,6 @@ #define TURBIDITY_PLUS_INC_CALC_VARIABLES 1 /**@}*/ -/** - * @anchor sensor__turbidity_plus_config - * @name Configuration Defines - * Defines to set the address of the ADD. - */ -/**@{*/ -/** - * @brief Enum for the pins used for differential voltages. - */ -typedef enum : uint16_t { - DIFF_MUX_0_1, ///< differential across pins 0 and 1 - DIFF_MUX_0_3, ///< differential across pins 0 and 3 - DIFF_MUX_1_3, ///< differential across pins 1 and 3 - DIFF_MUX_2_3 ///< differential across pins 2 and 3 -} ttp_adsDiffMux_t; -/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) -#define ADS1115_ADDRESS 0x48 -/**@}*/ - /** * @anchor sensor_turbidity_plus_timing * @name Sensor Timing @@ -130,6 +142,10 @@ typedef enum : uint16_t { * The primary turbidity output from an Turbidity Plus */ /**@{*/ +/// @brief Minimum turbidity; 0 NTU +#define TURBIDITY_PLUS_MIN_NTU 0 +/// @brief Maximum turbidity; 3000 NTU (detection limit 0.5 NTU) +#define TURBIDITY_PLUS_MAX_NTU 3000 /// Variable number; the primary variable is stored in sensorValues[0]. #define TURBIDITY_PLUS_VAR_NUM 0 /// @brief Variable name in [ODM2 controlled @@ -143,10 +159,10 @@ typedef enum : uint16_t { /// @brief Default variable short code; "TurnerTurbidity" #define TURBIDITY_PLUS_DEFAULT_CODE "TurnerTurbidity" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; 1. +/// @brief Decimal places in string representation; 1. #define TURBIDITY_PLUS_RESOLUTION 1 #else -/// @brief Decimals places in string representation; 5. +/// @brief Decimal places in string representation; 5. #define TURBIDITY_PLUS_RESOLUTION 5 #endif /**@}*/ @@ -167,6 +183,10 @@ typedef enum : uint16_t { * {{ @ref TurnerTurbidityPlus_Voltage::TurnerTurbidityPlus_Voltage }} */ /**@{*/ +/// @brief Minimum voltage when using ADS1x15 at 3.3V; 0.0V +#define TURBIDITY_PLUS_VOLTAGE_MIN_V 0.0 +/// @brief Maximum voltage when using ADS1x15 at 3.3V; 3.6V +#define TURBIDITY_PLUS_VOLTAGE_MAX_V 3.6 /// Variable number; voltage is stored in sensorValues[1]. #define TURBIDITY_PLUS_VOLTAGE_VAR_NUM 1 /// @brief Variable name in [ODM2 controlled @@ -178,14 +198,14 @@ typedef enum : uint16_t { /// @brief Default variable short code; "TurbidityPlusVoltage" #define TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE "TurbidityPlusVoltage" #ifdef MS_USE_ADS1015 -/// @brief Decimals places in string representation; voltage should have 1. +/// @brief Decimal places in string representation; voltage should have 1. /// - Resolution: -/// - 16-bit ADC (ADS1115): 0.125 mV +/// - 12-bit ADC (ADS1015): 2 mV #define TURBIDITY_PLUS_VOLTAGE_RESOLUTION 1 #else -/// @brief Decimals places in string representation; voltage should have 4. +/// @brief Decimal places in string representation; voltage should have 4. /// - Resolution: -/// - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): 2 mV +/// - 16-bit ADC (ADS1115): 0.125 mV #define TURBIDITY_PLUS_VOLTAGE_RESOLUTION 4 #endif /**@}*/ @@ -197,25 +217,28 @@ typedef enum : uint16_t { */ class TurnerTurbidityPlus : public Sensor { public: - // The constructor - need the power pin, the ADS1X15 data channel, and the - // calibration info /** * @brief Construct a new Turner Turbidity Plus object - need the power pin, - * the ADS1X15 data channel, and the calibration info. + * the analog data and reference channels, and the calibration info. * - * @note ModularSensors only supports connecting the ADS1x15 to the primary - * hardware I2C instance defined in the Arduino core. Connecting the ADS to - * a secondary hardware or software I2C instance is *not* supported! + * By default, this constructor will internally create a default + * AnalogVoltageReader implementation for voltage readings, but a pointer to + * a custom AnalogVoltageReader object can be passed in if desired. * * @param powerPin The pin on the mcu controlling power to the Turbidity * Plus Use -1 if it is continuously powered. - * - The ADS1x15 requires an input voltage of 2.0-5.5V - * - The Turbidity Plus itself requires a 3-15V power supply, which can be - * turned off between measurements. + * - The Turbidity Plus requires a 3-15V power supply, which can be turned + * off between measurements. * @param wiperTriggerPin The pin on the mcu that triggers the sensor's * wiper. - * @param adsDiffMux Which two pins _on the TI ADS1115_ that will measure - * differential voltage. See #ttp_adsDiffMux_t + * @param analogChannel The primary analog channel for differential + * measurement. Negative or invalid channel numbers or pairings between the + * analogChannel and analogReferenceChannel are not clamped and will cause + * the reading to fail and emit a warning. + * @param analogReferenceChannel The secondary (reference) analog channel + * for differential measurement. Negative or invalid channel numbers or + * pairings between the analogChannel and analogReferenceChannel are not + * clamped and will cause the reading to fail and emit a warning. * @param conc_std The concentration of the standard used for a 1-point * sensor calibration. The concentration units should be the same as the * final measuring units. @@ -224,42 +247,62 @@ class TurnerTurbidityPlus : public Sensor { * dividers or gain settings. * @param volt_blank The voltage (in volts) measured for a blank. This * voltage should be the final voltage *after* accounting for any voltage - * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR - * = GND) - * @param PGA_gain The programmable gain amplification to set on the - * ADS 1x15, default is GAIN_ONE (0-4.096V). + * dividers or gain settings. * @param measurementsToAverage The number of measurements to take and * average before giving a "final" result from the sensor; optional with a * default value of 1. - * @param voltageDividerFactor For 3.3V processors like the Mayfly, The - * Turner's 0-5V output signal must be shifted down to a maximum of 3.3V. - * This can be done either either with a level-shifting chip (e.g. Adafruit - * BSS38), OR by connecting the Turner's output signal via a voltage - * divider. This voltageDividerFactor is used for the latter case: e.g., a - * divider that uses 2 matched resistors will halve the voltage reading and - * requires a voltageDividerFactor of 2. The default value is 1. + * @param analogVoltageReader Pointer to an AnalogVoltageReader object for + * voltage measurements. Pass nullptr (the default) to have the constructor + * internally create and own an analog voltage reader. For backward + * compatibility, the default reader uses a TI ADS1115 or ADS1015. If a + * non-null pointer is supplied, the caller retains ownership and must + * ensure its lifetime exceeds that of this object. + * + * @attention For 3.3V processors like the Mayfly, The Turner's 0-5V output + * signal must be shifted down to a maximum of 3.3V. This can be done either + * with a level-shifting chip (e.g. Adafruit BSS138), OR by connecting the + * Turner's output signal via a voltage divider. By default, the + * TurnerTurbidityPlus object does **NOT** include any level-shifting or + * voltage dividers. To have a voltage divider applied correctly, you must + * supply a pointer to a custom AnalogVoltageReader object that applies the + * voltage divider to the raw voltage readings. For example, if you are + * using a simple voltage divider with two equal resistors, you would need + * to use an AnalogVoltageReader object that multiplies the raw voltage + * readings by 2 to account for the halving of the signal by the voltage + * divider. + * + * @warning In library versions 0.37.0 and earlier, a different constructor + * was used that required an enum object instead of two different analog + * channel inputs for the differential voltage measurement. If you are using + * code from a previous version of the library, make sure to update your + * code to use the new constructor and provide the correct analog channel + * inputs for the differential voltage measurement. */ TurnerTurbidityPlus(int8_t powerPin, int8_t wiperTriggerPin, - ttp_adsDiffMux_t adsDiffMux, float conc_std, - float volt_std, float volt_blank, - uint8_t i2cAddress = ADS1115_ADDRESS, - adsGain_t PGA_gain = GAIN_ONE, - uint8_t measurementsToAverage = 1, - float voltageDividerFactor = 1); + int8_t analogChannel, int8_t analogReferenceChannel, + float conc_std, float volt_std, float volt_blank, + uint8_t measurementsToAverage = 1, + AnalogVoltageReader* analogVoltageReader = nullptr); /** * @brief Destroy the Turner Turbidity Plus object */ - ~TurnerTurbidityPlus(); + ~TurnerTurbidityPlus() override; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + // Delete copy constructor and copy assignment operator to prevent shallow + // copies + TurnerTurbidityPlus(const TurnerTurbidityPlus&) = delete; + TurnerTurbidityPlus& operator=(const TurnerTurbidityPlus&) = delete; + + // Delete move constructor and move assignment operator + TurnerTurbidityPlus(TurnerTurbidityPlus&&) = delete; + TurnerTurbidityPlus& operator=(TurnerTurbidityPlus&&) = delete; + + String getSensorLocation() override; /** * @brief Run one wiper cycle */ - void runWiper(void); + void runWiper(); /** * @brief Do any one-time preparations needed before the sensor will be able @@ -269,18 +312,15 @@ class TurnerTurbidityPlus : public Sensor { * * @return **bool** True if the setup was successful. */ - bool setup(void) override; + bool setup() override; - bool wake(void) override; + bool wake() override; - void powerUp(void) override; + void powerUp() override; - void powerDown(void) override; + void powerDown() override; - /** - * @copydoc Sensor::addSingleMeasurementResult() - */ - bool addSingleMeasurementResult(void) override; + bool addSingleMeasurementResult() override; private: /** @@ -288,11 +328,6 @@ class TurnerTurbidityPlus : public Sensor { * sensor's wiper. */ int8_t _wiperTriggerPin; - /** - * @brief Which two pins _on the TI ADS1115_ that will measure differential - * voltage from the Turbidity Plus. See #ttp_adsDiffMux_t - */ - ttp_adsDiffMux_t _adsDiffMux; /** * @brief The concentration of the standard used for a 1-point sensor * calibration. The concentration units should be the same as the final @@ -310,25 +345,18 @@ class TurnerTurbidityPlus : public Sensor { * be the final voltage *after* accounting for any voltage. */ float _volt_blank; + /** - * @brief Internal reference to the I2C address of the TI-ADS1x15 - */ - uint8_t _i2cAddress; - /** - * @brief The programmable gain amplification to set on the ADS 1x15, - * default is GAIN_DEFAULT (0). - */ - adsGain_t _PGA_gain; - /** - * @brief For 3.3V processors like the Mayfly, The Turner's 0-5V output - * signal must be shifted down to a maximum of 3.3V. This can be done either - * either with a level-shifting chip (e.g. Adafruit BSS38), OR by connecting - * the Turner's output signal via a voltage divider. This - * voltageDividerFactor is used for the latter case: e.g., a divider that - * uses 2 matched resistors will halve the voltage reading and requires a - * voltageDividerFactor of 2. The default value is 1. + * @brief The second (reference) pin for differential voltage measurements. + * + * @note The primary pin is stored as Sensor::_dataPin. */ - float _voltageDividerFactor; + int8_t _analogReferenceChannel = -1; + /// @brief Pointer to analog voltage reader + AnalogVoltageReader* _analogVoltageReader = nullptr; + /// @brief Flag to track if this object owns the analog voltage reader and + /// should delete it in the destructor + bool _ownsAnalogVoltageReader = false; }; @@ -358,26 +386,14 @@ class TurnerTurbidityPlus_Voltage : public Variable { explicit TurnerTurbidityPlus_Voltage( TurnerTurbidityPlus* parentSense, const char* uuid = "", const char* varCode = TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TURBIDITY_PLUS_VOLTAGE_VAR_NUM, - (uint8_t)TURBIDITY_PLUS_VOLTAGE_RESOLUTION, + : Variable(parentSense, TURBIDITY_PLUS_VOLTAGE_VAR_NUM, + TURBIDITY_PLUS_VOLTAGE_RESOLUTION, TURBIDITY_PLUS_VOLTAGE_VAR_NAME, TURBIDITY_PLUS_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TurnerTurbidityPlus_Voltage object. - * - * @note This must be tied with a parent TurnerTurbidityPlus before it can - * be used. - */ - TurnerTurbidityPlus_Voltage() - : Variable((uint8_t)TURBIDITY_PLUS_VOLTAGE_VAR_NUM, - (uint8_t)TURBIDITY_PLUS_VOLTAGE_RESOLUTION, - TURBIDITY_PLUS_VOLTAGE_VAR_NAME, - TURBIDITY_PLUS_VOLTAGE_UNIT_NAME, - TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE) {} /** * @brief Destroy the TurnerTurbidityPlus_Voltage object - no action needed. */ - ~TurnerTurbidityPlus_Voltage() {} + ~TurnerTurbidityPlus_Voltage() override = default; }; @@ -407,24 +423,14 @@ class TurnerTurbidityPlus_Turbidity : public Variable { explicit TurnerTurbidityPlus_Turbidity( TurnerTurbidityPlus* parentSense, const char* uuid = "", const char* varCode = TURBIDITY_PLUS_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)TURBIDITY_PLUS_VAR_NUM, - (uint8_t)TURBIDITY_PLUS_RESOLUTION, TURBIDITY_PLUS_VAR_NAME, + : Variable(parentSense, TURBIDITY_PLUS_VAR_NUM, + TURBIDITY_PLUS_RESOLUTION, TURBIDITY_PLUS_VAR_NAME, TURBIDITY_PLUS_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new TurnerTurbidityPlus_Turbidity object. - * - * @note This must be tied with a parent TurnerTurbidityPlus before it can - * be used. - */ - TurnerTurbidityPlus_Turbidity() - : Variable((uint8_t)TURBIDITY_PLUS_VAR_NUM, - (uint8_t)TURBIDITY_PLUS_RESOLUTION, TURBIDITY_PLUS_VAR_NAME, - TURBIDITY_PLUS_UNIT_NAME, TURBIDITY_PLUS_DEFAULT_CODE) {} /** * @brief Destroy the TurnerTurbidityPlus_Turbidity object - no action * needed. */ - ~TurnerTurbidityPlus_Turbidity() {} + ~TurnerTurbidityPlus_Turbidity() override = default; }; /**@}*/ #endif // SRC_SENSORS_TURNERTURBIDITYPLUS_H_ diff --git a/src/sensors/VegaPuls21.h b/src/sensors/VegaPuls21.h index faf7c3de6..fb712965d 100644 --- a/src/sensors/VegaPuls21.h +++ b/src/sensors/VegaPuls21.h @@ -113,7 +113,11 @@ * {{ @ref VegaPuls21_Stage::VegaPuls21_Stage }} */ /**@{*/ -/// @brief Decimals places in string representation; stage in meters should have +/// @brief Minimum stage; 0 m +#define VEGAPULS21_STAGE_MIN_M 0 +/// @brief Maximum stage; 20 m (measuring range) +#define VEGAPULS21_STAGE_MAX_M 20 +/// @brief Decimal places in string representation; stage in meters should have /// 3 - resolution is 1mm. #define VEGAPULS21_STAGE_RESOLUTION 3 /// @brief Sensor variable number; stage is stored in sensorValues[0]. @@ -139,7 +143,11 @@ * {{ @ref VegaPuls21_Distance::VegaPuls21_Distance }} */ /**@{*/ -/// @brief Decimals places in string representation; distance in meters should +/// @brief Minimum distance; 0.25 m (minimum measuring distance) +#define VEGAPULS21_DISTANCE_MIN_M 0.25 +/// @brief Maximum distance; 20 m (measuring range) +#define VEGAPULS21_DISTANCE_MAX_M 20 +/// @brief Decimal places in string representation; distance in meters should /// have 3 - resolution is 1mm. #define VEGAPULS21_DISTANCE_RESOLUTION 3 /// @brief Sensor variable number; stage is stored in sensorValues[1]. @@ -164,7 +172,11 @@ * {{ @ref VegaPuls21_Temp::VegaPuls21_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum process temperature; -40°C +#define VEGAPULS21_TEMP_MIN_C -40 +/// @brief Maximum process temperature; 80°C +#define VEGAPULS21_TEMP_MAX_C 80 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define VEGAPULS21_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[2]. @@ -186,10 +198,13 @@ * @name Reliability * The reliability variable from a VEGAPULS C 21 * + * @todo Find and define minimum and maximum reliability measurement range from + * the VegaPuls21. + * * {{ @ref VegaPuls21_Reliability::VegaPuls21_Reliability }} */ /**@{*/ -/// @brief Decimals places in string representation; reliability should have 1 +/// @brief Decimal places in string representation; reliability should have 1 /// (resolution is 0.1 dB). #define VEGAPULS21_RELIABILITY_RESOLUTION 1 /// @brief Sensor variable number; reliability is stored in sensorValues[3] @@ -212,10 +227,13 @@ * The error code variable from a VEGAPULS C 21 * - Significance of error code values is unknown. * + * @todo Find and define minimum and maximum error code range from the + * VegaPuls21. + * * {{ @ref VegaPuls21_ErrorCode::VegaPuls21_ErrorCode }} */ /**@{*/ -/// @brief Decimals places in string representation; the error code has 0. +/// @brief Decimal places in string representation; the error code has 0. #define VEGAPULS21_ERRORCODE_RESOLUTION 0 /// @brief Sensor variable number; error code is stored in sensorValues[4] #define VEGAPULS21_ERRORCODE_VAR_NUM 4 @@ -294,11 +312,10 @@ class VegaPuls21 : public SDI12Sensors { VEGAPULS21_WARM_UP_TIME_MS, VEGAPULS21_STABILIZATION_TIME_MS, VEGAPULS21_MEASUREMENT_TIME_MS, VEGAPULS21_EXTRA_WAKE_TIME_MS, VEGAPULS21_INC_CALC_VARIABLES) {} - /** * @brief Destroy the VEGAPULS C 21 object */ - ~VegaPuls21() {} + ~VegaPuls21() override = default; }; @@ -326,25 +343,13 @@ class VegaPuls21_Stage : public Variable { explicit VegaPuls21_Stage( VegaPuls21* parentSense, const char* uuid = "", const char* varCode = VEGAPULS21_STAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)VEGAPULS21_STAGE_VAR_NUM, - (uint8_t)VEGAPULS21_STAGE_RESOLUTION, - VEGAPULS21_STAGE_VAR_NAME, VEGAPULS21_STAGE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new VegaPuls21_Stage object. - * - * @note This must be tied with a parent VegaPuls21 before it can be - * used. - */ - VegaPuls21_Stage() - : Variable((uint8_t)VEGAPULS21_STAGE_VAR_NUM, - (uint8_t)VEGAPULS21_STAGE_RESOLUTION, - VEGAPULS21_STAGE_VAR_NAME, VEGAPULS21_STAGE_UNIT_NAME, - VEGAPULS21_STAGE_DEFAULT_CODE) {} + : Variable(parentSense, VEGAPULS21_STAGE_VAR_NUM, + VEGAPULS21_STAGE_RESOLUTION, VEGAPULS21_STAGE_VAR_NAME, + VEGAPULS21_STAGE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the VegaPuls21_Stage object - no action needed. */ - ~VegaPuls21_Stage() {} + ~VegaPuls21_Stage() override = default; }; @@ -372,25 +377,13 @@ class VegaPuls21_Distance : public Variable { explicit VegaPuls21_Distance( VegaPuls21* parentSense, const char* uuid = "", const char* varCode = VEGAPULS21_DISTANCE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)VEGAPULS21_DISTANCE_VAR_NUM, - (uint8_t)VEGAPULS21_DISTANCE_RESOLUTION, - VEGAPULS21_DISTANCE_VAR_NAME, VEGAPULS21_DISTANCE_UNIT_NAME, - varCode, uuid) {} - /** - * @brief Construct a new VegaPuls21_Distance object. - * - * @note This must be tied with a parent VegaPuls21 before it can be - * used. - */ - VegaPuls21_Distance() - : Variable((uint8_t)VEGAPULS21_DISTANCE_VAR_NUM, - (uint8_t)VEGAPULS21_DISTANCE_RESOLUTION, - VEGAPULS21_DISTANCE_VAR_NAME, VEGAPULS21_DISTANCE_UNIT_NAME, - VEGAPULS21_DISTANCE_DEFAULT_CODE) {} + : Variable(parentSense, VEGAPULS21_DISTANCE_VAR_NUM, + VEGAPULS21_DISTANCE_RESOLUTION, VEGAPULS21_DISTANCE_VAR_NAME, + VEGAPULS21_DISTANCE_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the VegaPuls21_Distance object - no action needed. */ - ~VegaPuls21_Distance() {} + ~VegaPuls21_Distance() override = default; }; @@ -417,25 +410,13 @@ class VegaPuls21_Temp : public Variable { */ explicit VegaPuls21_Temp(VegaPuls21* parentSense, const char* uuid = "", const char* varCode = VEGAPULS21_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)VEGAPULS21_TEMP_VAR_NUM, - (uint8_t)VEGAPULS21_TEMP_RESOLUTION, - VEGAPULS21_TEMP_VAR_NAME, VEGAPULS21_TEMP_UNIT_NAME, varCode, - uuid) {} - /** - * @brief Construct a new VegaPuls21_Temp object. - * - * @note This must be tied with a parent VegaPuls21 before it can be - * used. - */ - VegaPuls21_Temp() - : Variable((uint8_t)VEGAPULS21_TEMP_VAR_NUM, - (uint8_t)VEGAPULS21_TEMP_RESOLUTION, - VEGAPULS21_TEMP_VAR_NAME, VEGAPULS21_TEMP_UNIT_NAME, - VEGAPULS21_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, VEGAPULS21_TEMP_VAR_NUM, + VEGAPULS21_TEMP_RESOLUTION, VEGAPULS21_TEMP_VAR_NAME, + VEGAPULS21_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the VegaPuls21_Temp object - no action needed. */ - ~VegaPuls21_Temp() {} + ~VegaPuls21_Temp() override = default; }; @@ -463,27 +444,15 @@ class VegaPuls21_Reliability : public Variable { explicit VegaPuls21_Reliability( VegaPuls21* parentSense, const char* uuid = "", const char* varCode = VEGAPULS21_RELIABILITY_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)VEGAPULS21_RELIABILITY_VAR_NUM, - (uint8_t)VEGAPULS21_RELIABILITY_RESOLUTION, + : Variable(parentSense, VEGAPULS21_RELIABILITY_VAR_NUM, + VEGAPULS21_RELIABILITY_RESOLUTION, VEGAPULS21_RELIABILITY_VAR_NAME, VEGAPULS21_RELIABILITY_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new VegaPuls21_Reliability object. - * - * @note This must be tied with a parent VegaPuls21 before it can be - * used. - */ - VegaPuls21_Reliability() - : Variable((uint8_t)VEGAPULS21_RELIABILITY_VAR_NUM, - (uint8_t)VEGAPULS21_RELIABILITY_RESOLUTION, - VEGAPULS21_RELIABILITY_VAR_NAME, - VEGAPULS21_RELIABILITY_UNIT_NAME, - VEGAPULS21_RELIABILITY_DEFAULT_CODE) {} /** * @brief Destroy the VegaPuls21_Reliability object - no action * needed. */ - ~VegaPuls21_Reliability() {} + ~VegaPuls21_Reliability() override = default; }; @@ -511,27 +480,15 @@ class VegaPuls21_ErrorCode : public Variable { explicit VegaPuls21_ErrorCode( VegaPuls21* parentSense, const char* uuid = "", const char* varCode = VEGAPULS21_ERRORCODE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)VEGAPULS21_ERRORCODE_VAR_NUM, - (uint8_t)VEGAPULS21_ERRORCODE_RESOLUTION, + : Variable(parentSense, VEGAPULS21_ERRORCODE_VAR_NUM, + VEGAPULS21_ERRORCODE_RESOLUTION, VEGAPULS21_ERRORCODE_VAR_NAME, VEGAPULS21_ERRORCODE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new VegaPuls21_ErrorCode object. - * - * @note This must be tied with a parent VegaPuls21 before it can be - * used. - */ - VegaPuls21_ErrorCode() - : Variable((uint8_t)VEGAPULS21_ERRORCODE_VAR_NUM, - (uint8_t)VEGAPULS21_ERRORCODE_RESOLUTION, - VEGAPULS21_ERRORCODE_VAR_NAME, - VEGAPULS21_ERRORCODE_UNIT_NAME, - VEGAPULS21_ERRORCODE_DEFAULT_CODE) {} /** * @brief Destroy the VegaPuls21_ErrorCode object - no action * needed. */ - ~VegaPuls21_ErrorCode() {} + ~VegaPuls21_ErrorCode() override = default; }; /**@}*/ #endif // SRC_SENSORS_VEGAPULS21_H_ diff --git a/src/sensors/YosemitechParent.cpp b/src/sensors/YosemitechParent.cpp index 3ff15d997..46294901b 100644 --- a/src/sensors/YosemitechParent.cpp +++ b/src/sensors/YosemitechParent.cpp @@ -24,40 +24,37 @@ YosemitechParent::YosemitechParent( _model(model), _modbusAddress(modbusAddress), _stream(stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} + _RS485EnablePin(enablePin) { + setSecondaryPowerPin(powerPin2); +} +// Delegating constructor YosemitechParent::YosemitechParent( byte modbusAddress, Stream& stream, int8_t powerPin, int8_t powerPin2, int8_t enablePin, uint8_t measurementsToAverage, yosemitechModel model, const char* sensName, uint8_t numVariables, uint32_t warmUpTime_ms, uint32_t stabilizationTime_ms, uint32_t measurementTime_ms, uint8_t incCalcValues) - : Sensor(sensName, numVariables, warmUpTime_ms, stabilizationTime_ms, - measurementTime_ms, powerPin, -1, measurementsToAverage, - incCalcValues), - _model(model), - _modbusAddress(modbusAddress), - _stream(&stream), - _RS485EnablePin(enablePin), - _powerPin2(powerPin2) {} -// Destructor -YosemitechParent::~YosemitechParent() {} + : YosemitechParent(modbusAddress, &stream, powerPin, powerPin2, enablePin, + measurementsToAverage, model, sensName, numVariables, + warmUpTime_ms, stabilizationTime_ms, measurementTime_ms, + incCalcValues) {} // The sensor installation location on the Mayfly -String YosemitechParent::getSensorLocation(void) { - String sensorLocation = F("modbus_0x"); +String YosemitechParent::getSensorLocation() { + String sensorLocation; + sensorLocation.reserve(12); // Reserve for "modbus_0x" + 2 hex chars + sensorLocation = F("modbus_0x"); if (_modbusAddress < 16) sensorLocation += "0"; sensorLocation += String(_modbusAddress, HEX); return sensorLocation; } -bool YosemitechParent::setup(void) { +bool YosemitechParent::setup() { bool retVal = Sensor::setup(); // this will set pin modes and the setup status bit if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - if (_powerPin2 >= 0) { pinMode(_powerPin2, OUTPUT); } #ifdef MS_YOSEMITECHPARENT_DEBUG_DEEP _ysensor.setDebugStream(&MS_SERIAL_OUTPUT); @@ -75,10 +72,12 @@ bool YosemitechParent::setup(void) { // The function to wake up a sensor // Different from the standard in that it waits for warm up and starts // measurements -bool YosemitechParent::wake(void) { +bool YosemitechParent::wake() { // Sensor::wake() checks if the power pin is on and sets the wake timestamp // and status bits. If it returns false, there's no reason to go on. if (!Sensor::wake()) return false; + // reset pin mode, in case the pins were tri-stated during sleep + if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } // Send the command to begin taking readings, trying up to 5 times bool success = false; @@ -123,7 +122,7 @@ bool YosemitechParent::wake(void) { // The function to put the sensor to sleep // Different from the standard in that it empties and flushes the stream and // stops measurements -bool YosemitechParent::sleep(void) { +bool YosemitechParent::sleep() { // empty then flush the buffer while (_stream->available()) { _stream->read(); } _stream->flush(); @@ -167,112 +166,39 @@ bool YosemitechParent::sleep(void) { } -// This turns on sensor power -void YosemitechParent::powerUp(void) { - if (_powerPin >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin, OUTPUT); - MS_DBG(F("Powering"), getSensorNameAndLocation(), F("with pin"), - _powerPin); - digitalWrite(_powerPin, HIGH); - } - if (_powerPin2 >= 0) { - // Reset power pin mode every power up because pins are set to tri-state - // on sleep - pinMode(_powerPin2, OUTPUT); - MS_DBG(F("Applying secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, HIGH); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Mark the power-on time, just in case it had not been marked - if (_millisPowerOn == 0) _millisPowerOn = millis(); - } else { - // Mark the time that the sensor was powered - _millisPowerOn = millis(); - } - // Reset enable pin because pins are set to tri-state on sleep - if (_RS485EnablePin >= 0) { pinMode(_RS485EnablePin, OUTPUT); } - // Set the status bit for sensor power attempt (bit 1) and success (bit 2) - setStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL); -} - - -// This turns off sensor power -void YosemitechParent::powerDown(void) { - if (_powerPin >= 0) { - MS_DBG(F("Turning off power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin); - digitalWrite(_powerPin, LOW); - // Unset the power-on time - _millisPowerOn = 0; - // Unset the activation time - _millisSensorActivated = 0; - // Unset the measurement request time - _millisMeasurementRequested = 0; - // Unset the status bits for sensor power (bits 1 & 2), - // activation (bits 3 & 4), and measurement request (bits 5 & 6) - clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED, - WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, - MEASUREMENT_SUCCESSFUL); - } - if (_powerPin2 >= 0) { - MS_DBG(F("Turning off secondary power to"), getSensorNameAndLocation(), - F("with pin"), _powerPin2); - digitalWrite(_powerPin2, LOW); - } - if (_powerPin < 0 && _powerPin2 < 0) { - MS_DBG(F("Power to"), getSensorNameAndLocation(), - F("is not controlled by this library.")); - // Do NOT unset any status bits or timestamps if we didn't really power - // down! - } -} - +bool YosemitechParent::addSingleMeasurementResult() { + // Perform common initialization checks + if (!initializeMeasurementResult()) { return false; } -bool YosemitechParent::addSingleMeasurementResult(void) { bool success = false; - // Check a measurement was *successfully* started (status bit 6 set) - // Only go on to get a result if it was - if (getStatusBit(MEASUREMENT_SUCCESSFUL)) { - switch (_model) { - case Y4000: { - // Initialize float variables - float DOmgL = -9999; - float Turbidity = -9999; - float Cond = -9999; - float pH = -9999; - float Temp = -9999; - float ORP = -9999; - float Chlorophyll = -9999; - float BGA = -9999; - - // Get Values - MS_DBG(F("Get Values from"), getSensorNameAndLocation()); - success = _ysensor.getValues(DOmgL, Turbidity, Cond, pH, Temp, - ORP, Chlorophyll, BGA); - - // Fix not-a-number values - if (!success || isnan(DOmgL)) DOmgL = -9999; - if (!success || isnan(Turbidity)) Turbidity = -9999; - if (!success || isnan(Cond)) Cond = -9999; - if (!success || isnan(pH)) pH = -9999; - if (!success || isnan(Temp)) Temp = -9999; - if (!success || isnan(ORP)) ORP = -9999; - if (!success || isnan(Chlorophyll)) Chlorophyll = -9999; - if (!success || isnan(BGA)) BGA = -9999; - - // For conductivity, convert mS/cm to µS/cm - if (Cond != -9999) Cond *= 1000; - - MS_DBG(F(" "), _ysensor.getParameter()); - MS_DBG(F(" "), DOmgL, ',', Turbidity, ',', Cond, ',', pH, - ',', Temp, ',', ORP, ',', Chlorophyll, ',', BGA); - + switch (_model) { + case Y4000: { + // Initialize float variables + float DOmgL = MS_INVALID_VALUE; + float Turbidity = MS_INVALID_VALUE; + float Cond = MS_INVALID_VALUE; + float pH = MS_INVALID_VALUE; + float Temp = MS_INVALID_VALUE; + float ORP = MS_INVALID_VALUE; + float Chlorophyll = MS_INVALID_VALUE; + float BGA = MS_INVALID_VALUE; + + // Get Values + MS_DBG(F("Get Values from"), getSensorNameAndLocation()); + success = _ysensor.getValues(DOmgL, Turbidity, Cond, pH, Temp, ORP, + Chlorophyll, BGA); + + // For conductivity, convert mS/cm to µS/cm + if (success && !isnan(Cond)) Cond *= 1000; + + MS_DBG(F(" "), _ysensor.getParameter()); + MS_DBG(F(" "), DOmgL, ',', Turbidity, ',', Cond, ',', pH, ',', + Temp, ',', ORP, ',', Chlorophyll, ',', BGA); + + // NOTE: Success depends on getting values, not on them being valid + // numbers! + if (success) { // Put values into the array verifyAndAddMeasurementResult(0, DOmgL); verifyAndAddMeasurementResult(1, Turbidity); @@ -282,52 +208,47 @@ bool YosemitechParent::addSingleMeasurementResult(void) { verifyAndAddMeasurementResult(5, ORP); verifyAndAddMeasurementResult(6, Chlorophyll); verifyAndAddMeasurementResult(7, BGA); - - break; } - default: { - // Initialize float variables - float parmValue = -9999; - float tempValue = -9999; - float thirdValue = -9999; - - // Get Values - MS_DBG(F("Get Values from"), getSensorNameAndLocation()); - success = _ysensor.getValues(parmValue, tempValue, thirdValue); - - // Fix not-a-number values - if (!success || isnan(parmValue)) parmValue = -9999; - if (!success || isnan(tempValue)) tempValue = -9999; - if (!success || isnan(thirdValue)) thirdValue = -9999; + break; + } + default: { + // Initialize float variables + float parmValue = MS_INVALID_VALUE; + float tempValue = MS_INVALID_VALUE; + float thirdValue = MS_INVALID_VALUE; + + // Get Values + MS_DBG(F("Get Values from"), getSensorNameAndLocation()); + success = _ysensor.getValues(parmValue, tempValue, thirdValue); + + // For conductivity, convert mS/cm to µS/cm + if (_model == Y520 && success && !isnan(parmValue)) { + parmValue *= 1000; + } - // For conductivity, convert mS/cm to µS/cm - if (_model == Y520 && parmValue != -9999) parmValue *= 1000; + MS_DBG(' ', _ysensor.getParameter(), ':', parmValue); + MS_DBG(F(" Temp:"), tempValue); - MS_DBG(F(" "), _ysensor.getParameter(), ':', parmValue); - MS_DBG(F(" Temp:"), tempValue); + // Not all sensors return a third value + if (_numReturnedValues > 2) { MS_DBG(F(" Third:"), thirdValue); } - // Not all sensors return a third value - if (_numReturnedValues > 2) { - MS_DBG(F(" Third:"), thirdValue); - } + // NOTE: Success depends on getting values, not on them being valid + // numbers! + if (success) { // Put values into the array verifyAndAddMeasurementResult(0, parmValue); verifyAndAddMeasurementResult(1, tempValue); - verifyAndAddMeasurementResult(2, thirdValue); + if (_numReturnedValues > 2) { + verifyAndAddMeasurementResult(2, thirdValue); + } } + break; } - } else { - MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); } - // Unset the time stamp for the beginning of this measurement - _millisMeasurementRequested = 0; - // Unset the status bits for a measurement request (bits 5 & 6) - clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL); - - // Return true when finished - return success; + // Return success value when finished + return finalizeMeasurementAttempt(success); } // cSpell:ignore ysensor diff --git a/src/sensors/YosemitechParent.h b/src/sensors/YosemitechParent.h index d71490790..53e1736ce 100644 --- a/src/sensors/YosemitechParent.h +++ b/src/sensors/YosemitechParent.h @@ -87,7 +87,7 @@ * This will _fry_ any board like the Mayfly that uses 3.3V logic. * You would need a voltage shifter in between the Mayfly and the MAX485 to make it work. * - * The sensor constructor requires as input: the sensor modbus address, a stream instance for data (ie, ```Serial```), and one or two power pins. + * The sensor constructor requires as input: the sensor modbus address, a stream instance for data (i.e., ```Serial```), and one or two power pins. * The Arduino pin controlling the receive and data enable on your RS485-to-TTL adapter and the number of readings to average are optional. * (Use -1 for the second power pin and -1 for the enable pin if these don't apply and you want to average more than one reading.) * For all of these sensors except pH, Yosemitech strongly recommends averaging 10 readings for each measurement. @@ -168,7 +168,7 @@ class YosemitechParent : public Sensor { * average before giving a "final" result from the sensor; optional with a * default value of 1. * @param model The model of Yosemitech sensor. - * @param sensName The name of the sensor. Defaults to "SDI12-Sensor". + * @param sensName The name of the sensor. Defaults to "Yosemitech-Sensor". * @param numVariables The number of variable results returned by the * sensor. Defaults to 2. * @param warmUpTime_ms The time in ms between when the sensor is powered on @@ -207,12 +207,9 @@ class YosemitechParent : public Sensor { /** * @brief Destroy the Yosemitech Parent object - no action taken */ - virtual ~YosemitechParent(); + ~YosemitechParent() override = default; - /** - * @copydoc Sensor::getSensorLocation() - */ - String getSensorLocation(void) override; + String getSensorLocation() override; /** * @brief Do any one-time preparations needed before the sensor will be able @@ -225,29 +222,27 @@ class YosemitechParent : public Sensor { * * @return True if the setup was successful. */ - bool setup(void) override; + bool setup() override; /** - * @copydoc Sensor::wake() - */ - bool wake(void) override; - /** - * @brief Puts the sensor to sleep, if necessary. + * @brief Wakes the sensor, starts measurements, and activates brushes. * - * This also un-sets the #_millisSensorActivated timestamp (sets it to 0). - * This does NOT power down the sensor! + * Unlike base Sensor::wake(), this starts measurements and activates the + * brushes (where applicable). Yosemitech sensors do not start to stabilize + * until after starting measurements. So we activate the sensor as part of + * the wake and then must wait the stabilization time + 1 measurement time + * before requesting the first result. * - * @return True if the sleep function completed successfully. + * @return True if the wake function completed successfully. */ - bool sleep(void) override; - - // Override these to use two power pins - void powerUp(void) override; - void powerDown(void) override; - + bool wake() override; /** - * @copydoc Sensor::addSingleMeasurementResult() + * @brief Stop measurements and empty and flush the stream before sleeping. + * + * @return True if sleep was successful. */ - bool addSingleMeasurementResult(void) override; + bool sleep() override; + + bool addSingleMeasurementResult() override; private: /** @@ -273,12 +268,8 @@ class YosemitechParent : public Sensor { * pin. */ int8_t _RS485EnablePin; - /** - * @brief Private reference to the power pin fro the RS-485 adapter. - */ - int8_t _powerPin2; }; #endif // SRC_SENSORS_YOSEMITECHPARENT_H_ -// cSpell:ignore ysensor +// cSpell:words ysensor diff --git a/src/sensors/YosemitechY4000.h b/src/sensors/YosemitechY4000.h index 61656bfe8..62943729f 100644 --- a/src/sensors/YosemitechY4000.h +++ b/src/sensors/YosemitechY4000.h @@ -109,7 +109,11 @@ * {{ @ref YosemitechY4000_DOmgL::YosemitechY4000_DOmgL }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen +/// @brief Minimum dissolved oxygen concentration in milligrams per liter. +#define Y4000_DOMGL_MIN_MGPL 0.0 +/// @brief Maximum dissolved oxygen concentration in milligrams per liter. +#define Y4000_DOMGL_MAX_MGPL 20.0 +/// @brief Decimal places in string representation; dissolved oxygen /// concentration should have 2 - resolution is 0.01 mg/L. #define Y4000_DOMGL_RESOLUTION 2 /// @brief Sensor variable number; dissolved oxygen concentration is stored in @@ -137,7 +141,11 @@ * {{ @ref YosemitechY4000_Turbidity::YosemitechY4000_Turbidity }} */ /**@{*/ -/// @brief Decimals places in string representation; turbidity should have 2 - +/// @brief Minimum turbidity in nephelometric turbidity units. +#define Y4000_TURB_MIN_NTU 0.1 +/// @brief Maximum turbidity in nephelometric turbidity units. +#define Y4000_TURB_MAX_NTU 1000.0 +/// @brief Decimal places in string representation; turbidity should have 2 - /// resolution is 0.01 NTU. #define Y4000_TURB_RESOLUTION 2 /// @brief Sensor variable number; turbidity is stored in sensorValues[1]. @@ -164,7 +172,11 @@ * {{ @ref YosemitechY4000_Cond::YosemitechY4000_Cond }} */ /**@{*/ -/// @brief Decimals places in string representation; conductivity should have 1 +/// @brief Minimum specific conductance in microsiemens per centimeter. +#define Y4000_COND_MIN_USCM 1.0 +/// @brief Maximum specific conductance in microsiemens per centimeter. +#define Y4000_COND_MAX_USCM 200000.0 +/// @brief Decimal places in string representation; conductivity should have 1 /// - resolution is 0.1 µS/cm. #define Y4000_COND_RESOLUTION 1 /// @brief Sensor variable number; conductivity is stored in sensorValues[2]. @@ -191,7 +203,11 @@ * {{ @ref YosemitechY4000_pH::YosemitechY4000_pH }} */ /**@{*/ -/// @brief Decimals places in string representation; ph should have 2 - +/// @brief Minimum pH value. +#define Y4000_PH_MIN 2.0 +/// @brief Maximum pH value. +#define Y4000_PH_MAX 12.0 +/// @brief Decimal places in string representation; ph should have 2 - /// resolution is 0.01 pH units. #define Y4000_PH_RESOLUTION 2 /// @brief Sensor variable number; pH is stored in sensorValues[3]. @@ -217,7 +233,11 @@ * {{ @ref YosemitechY4000_Temp::YosemitechY4000_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature in degrees Celsius. +#define Y4000_TEMP_MIN_C 0.0 +/// @brief Maximum temperature in degrees Celsius. +#define Y4000_TEMP_MAX_C 50.0 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y4000_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[4]. @@ -244,7 +264,11 @@ * {{ @ref YosemitechY4000_ORP::YosemitechY4000_ORP }} */ /**@{*/ -/// @brief Decimals places in string representation; orp should have 0 - +/// @brief Minimum oxidation reduction potential in millivolts. +#define Y4000_ORP_MIN_MV -999.0 +/// @brief Maximum oxidation reduction potential in millivolts. +#define Y4000_ORP_MAX_MV 999.0 +/// @brief Decimal places in string representation; orp should have 0 - /// resolution is 1 mV. #define Y4000_ORP_RESOLUTION 0 /// @brief Sensor variable number; ORP is stored in sensorValues[5]. @@ -272,7 +296,11 @@ * {{ @ref YosemitechY4000_Chlorophyll::YosemitechY4000_Chlorophyll }} */ /**@{*/ -/// @brief Decimals places in string representation; chlorophyll concentration +/// @brief Minimum chlorophyll concentration in micrograms per liter. +#define Y4000_CHLORO_MIN_UGPL 0.0 +/// @brief Maximum chlorophyll concentration in micrograms per liter. +#define Y4000_CHLORO_MAX_UGPL 400.0 +/// @brief Decimal places in string representation; chlorophyll concentration /// should have 1 - resolution is 0.1 µg/L / 0.1 RFU. #define Y4000_CHLORO_RESOLUTION 1 /// @brief Sensor variable number; chlorophyll concentration is stored in @@ -300,7 +328,11 @@ * {{ @ref YosemitechY4000_BGA::YosemitechY4000_BGA }} */ /**@{*/ -/// @brief Decimals places in string representation; bga should have 2 - +/// @brief Minimum blue green algae concentration in micrograms per liter. +#define Y4000_BGA_MIN_UGPL 0.0 +/// @brief Maximum blue green algae concentration in micrograms per liter. +#define Y4000_BGA_MAX_UGPL 100.0 +/// @brief Decimal places in string representation; bga should have 2 - /// resolution is 0.01 µg/L / 0.01 RFU. #define Y4000_BGA_RESOLUTION 2 /// @brief Sensor variable number; BGA is stored in sensorValues[7]. @@ -373,7 +405,7 @@ class YosemitechY4000 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y4000 object */ - ~YosemitechY4000() {} + ~YosemitechY4000() override = default; }; @@ -401,23 +433,13 @@ class YosemitechY4000_DOmgL : public Variable { explicit YosemitechY4000_DOmgL( YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_DOMGL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_DOMGL_VAR_NUM, - (uint8_t)Y4000_DOMGL_RESOLUTION, Y4000_DOMGL_VAR_NAME, - Y4000_DOMGL_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_DOmgL object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_DOmgL() - : Variable((uint8_t)Y4000_DOMGL_VAR_NUM, - (uint8_t)Y4000_DOMGL_RESOLUTION, Y4000_DOMGL_VAR_NAME, - Y4000_DOMGL_UNIT_NAME, Y4000_DOMGL_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_DOMGL_VAR_NUM, Y4000_DOMGL_RESOLUTION, + Y4000_DOMGL_VAR_NAME, Y4000_DOMGL_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the YosemitechY4000_DOmgL object - no action needed. */ - ~YosemitechY4000_DOmgL() {} + ~YosemitechY4000_DOmgL() override = default; }; /* clang-format off */ @@ -444,23 +466,12 @@ class YosemitechY4000_Turbidity : public Variable { explicit YosemitechY4000_Turbidity( YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_TURB_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_TURB_VAR_NUM, - (uint8_t)Y4000_TURB_RESOLUTION, Y4000_TURB_VAR_NAME, - Y4000_TURB_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_Turbidity object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_Turbidity() - : Variable((uint8_t)Y4000_TURB_VAR_NUM, (uint8_t)Y4000_TURB_RESOLUTION, - Y4000_TURB_VAR_NAME, Y4000_TURB_UNIT_NAME, - Y4000_TURB_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_TURB_VAR_NUM, Y4000_TURB_RESOLUTION, + Y4000_TURB_VAR_NAME, Y4000_TURB_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_Turbidity object - no action needed. */ - ~YosemitechY4000_Turbidity() {} + ~YosemitechY4000_Turbidity() override = default; }; /* clang-format off */ @@ -487,23 +498,12 @@ class YosemitechY4000_Cond : public Variable { explicit YosemitechY4000_Cond(YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_COND_VAR_NUM, - (uint8_t)Y4000_COND_RESOLUTION, Y4000_COND_VAR_NAME, - Y4000_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_Cond object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_Cond() - : Variable((uint8_t)Y4000_COND_VAR_NUM, (uint8_t)Y4000_COND_RESOLUTION, - Y4000_COND_VAR_NAME, Y4000_COND_UNIT_NAME, - Y4000_COND_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_COND_VAR_NUM, Y4000_COND_RESOLUTION, + Y4000_COND_VAR_NAME, Y4000_COND_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_Cond object - no action needed. */ - ~YosemitechY4000_Cond() {} + ~YosemitechY4000_Cond() override = default; }; /* clang-format off */ @@ -530,23 +530,12 @@ class YosemitechY4000_pH : public Variable { explicit YosemitechY4000_pH(YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_PH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_PH_VAR_NUM, - (uint8_t)Y4000_PH_RESOLUTION, Y4000_PH_VAR_NAME, - Y4000_PH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_pH object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_pH() - : Variable((uint8_t)Y4000_PH_VAR_NUM, (uint8_t)Y4000_PH_RESOLUTION, - Y4000_PH_VAR_NAME, Y4000_PH_UNIT_NAME, - Y4000_PH_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_PH_VAR_NUM, Y4000_PH_RESOLUTION, + Y4000_PH_VAR_NAME, Y4000_PH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_pH object - no action needed. */ - ~YosemitechY4000_pH() {} + ~YosemitechY4000_pH() override = default; }; /* clang-format off */ @@ -573,23 +562,12 @@ class YosemitechY4000_Temp : public Variable { explicit YosemitechY4000_Temp(YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_TEMP_VAR_NUM, - (uint8_t)Y4000_TEMP_RESOLUTION, Y4000_TEMP_VAR_NAME, - Y4000_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_Temp object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_Temp() - : Variable((uint8_t)Y4000_TEMP_VAR_NUM, (uint8_t)Y4000_TEMP_RESOLUTION, - Y4000_TEMP_VAR_NAME, Y4000_TEMP_UNIT_NAME, - Y4000_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_TEMP_VAR_NUM, Y4000_TEMP_RESOLUTION, + Y4000_TEMP_VAR_NAME, Y4000_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_Temp object - no action needed. */ - ~YosemitechY4000_Temp() {} + ~YosemitechY4000_Temp() override = default; }; /* clang-format off */ @@ -616,23 +594,12 @@ class YosemitechY4000_ORP : public Variable { explicit YosemitechY4000_ORP(YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_ORP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_ORP_VAR_NUM, - (uint8_t)Y4000_ORP_RESOLUTION, Y4000_ORP_VAR_NAME, - Y4000_ORP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_ORP object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_ORP() - : Variable((uint8_t)Y4000_ORP_VAR_NUM, (uint8_t)Y4000_ORP_RESOLUTION, - Y4000_ORP_VAR_NAME, Y4000_ORP_UNIT_NAME, - Y4000_ORP_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_ORP_VAR_NUM, Y4000_ORP_RESOLUTION, + Y4000_ORP_VAR_NAME, Y4000_ORP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_ORP object - no action needed. */ - ~YosemitechY4000_ORP() {} + ~YosemitechY4000_ORP() override = default; }; /* clang-format off */ @@ -659,24 +626,14 @@ class YosemitechY4000_Chlorophyll : public Variable { explicit YosemitechY4000_Chlorophyll( YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_CHLORO_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_CHLORO_VAR_NUM, - (uint8_t)Y4000_CHLORO_RESOLUTION, Y4000_CHLORO_VAR_NAME, - Y4000_CHLORO_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_Chlorophyll object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_Chlorophyll() - : Variable((uint8_t)Y4000_CHLORO_VAR_NUM, - (uint8_t)Y4000_CHLORO_RESOLUTION, Y4000_CHLORO_VAR_NAME, - Y4000_CHLORO_UNIT_NAME, Y4000_CHLORO_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_CHLORO_VAR_NUM, Y4000_CHLORO_RESOLUTION, + Y4000_CHLORO_VAR_NAME, Y4000_CHLORO_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the YosemitechY4000_Chlorophyll() object - no action * needed. */ - ~YosemitechY4000_Chlorophyll() {} + ~YosemitechY4000_Chlorophyll() override = default; }; /* clang-format off */ @@ -703,25 +660,14 @@ class YosemitechY4000_BGA : public Variable { explicit YosemitechY4000_BGA(YosemitechY4000* parentSense, const char* uuid = "", const char* varCode = Y4000_BGA_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y4000_BGA_VAR_NUM, - (uint8_t)Y4000_BGA_RESOLUTION, Y4000_BGA_VAR_NAME, - Y4000_BGA_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY4000_BGA object. - * - * @note This must be tied with a parent YosemitechY4000 before it can be - * used. - */ - YosemitechY4000_BGA() - : Variable((uint8_t)Y4000_BGA_VAR_NUM, (uint8_t)Y4000_BGA_RESOLUTION, - Y4000_BGA_VAR_NAME, Y4000_BGA_UNIT_NAME, - Y4000_BGA_DEFAULT_CODE) {} + : Variable(parentSense, Y4000_BGA_VAR_NUM, Y4000_BGA_RESOLUTION, + Y4000_BGA_VAR_NAME, Y4000_BGA_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY4000_BGA object - no action needed. */ - ~YosemitechY4000_BGA() {} + ~YosemitechY4000_BGA() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY4000_H_ -// cSpell:ignore Y4000Chloro +// cSpell:words Y4000Chloro MGPL USCM UGPL diff --git a/src/sensors/YosemitechY504.h b/src/sensors/YosemitechY504.h index bf3eedfad..f1d061b09 100644 --- a/src/sensors/YosemitechY504.h +++ b/src/sensors/YosemitechY504.h @@ -100,7 +100,11 @@ * {{ @ref YosemitechY504_DOpct::YosemitechY504_DOpct }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen percent +/// @brief Minimum dissolved oxygen percent saturation; 0% saturation +#define Y504_DOPCT_MIN_PCT 0 +/// @brief Maximum dissolved oxygen percent saturation; 200% saturation +#define Y504_DOPCT_MAX_PCT 200 +/// @brief Decimal places in string representation; dissolved oxygen percent /// should have 1 - resolution is 0.1%. #define Y504_DOPCT_RESOLUTION 1 /// @brief Sensor variable number; dissolved oxygen percent is stored in @@ -128,7 +132,11 @@ * {{ @ref YosemitechY504_Temp::YosemitechY504_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y504_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y504_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y504_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -155,7 +163,11 @@ * {{ @ref YosemitechY504_DOmgL::YosemitechY504_DOmgL }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen +/// @brief Minimum dissolved oxygen concentration; 0 mg/L +#define Y504_DOMGL_MIN_MGPL 0 +/// @brief Maximum dissolved oxygen concentration; 20 mg/L +#define Y504_DOMGL_MAX_MGPL 20 +/// @brief Decimal places in string representation; dissolved oxygen /// concentration should have 2 - resolution is 0.01 mg/L. #define Y504_DOMGL_RESOLUTION 2 /// @brief Sensor variable number; dissolved oxygen concentration is stored in @@ -227,7 +239,7 @@ class YosemitechY504 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y504 object */ - ~YosemitechY504() {} + ~YosemitechY504() override = default; }; @@ -255,23 +267,12 @@ class YosemitechY504_DOpct : public Variable { explicit YosemitechY504_DOpct(YosemitechY504* parentSense, const char* uuid = "", const char* varCode = Y504_DOPCT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y504_DOPCT_VAR_NUM, - (uint8_t)Y504_DOPCT_RESOLUTION, Y504_DOPCT_VAR_NAME, - Y504_DOPCT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY504_DOpct object. - * - * @note This must be tied with a parent YosemitechY504 before it can be - * used. - */ - YosemitechY504_DOpct() - : Variable((uint8_t)Y504_DOPCT_VAR_NUM, (uint8_t)Y504_DOPCT_RESOLUTION, - Y504_DOPCT_VAR_NAME, Y504_DOPCT_UNIT_NAME, - Y504_DOPCT_DEFAULT_CODE) {} + : Variable(parentSense, Y504_DOPCT_VAR_NUM, Y504_DOPCT_RESOLUTION, + Y504_DOPCT_VAR_NAME, Y504_DOPCT_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY504_DOpct object - no action needed. */ - ~YosemitechY504_DOpct() {} + ~YosemitechY504_DOpct() override = default; }; @@ -299,23 +300,12 @@ class YosemitechY504_Temp : public Variable { explicit YosemitechY504_Temp(YosemitechY504* parentSense, const char* uuid = "", const char* varCode = Y504_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y504_TEMP_VAR_NUM, - (uint8_t)Y504_TEMP_RESOLUTION, Y504_TEMP_VAR_NAME, - Y504_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY504_Temp object. - * - * @note This must be tied with a parent YosemitechY504 before it can be - * used. - */ - YosemitechY504_Temp() - : Variable((uint8_t)Y504_TEMP_VAR_NUM, (uint8_t)Y504_TEMP_RESOLUTION, - Y504_TEMP_VAR_NAME, Y504_TEMP_UNIT_NAME, - Y504_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y504_TEMP_VAR_NUM, Y504_TEMP_RESOLUTION, + Y504_TEMP_VAR_NAME, Y504_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY504_Temp object - no action needed. */ - ~YosemitechY504_Temp() {} + ~YosemitechY504_Temp() override = default; }; @@ -343,23 +333,12 @@ class YosemitechY504_DOmgL : public Variable { explicit YosemitechY504_DOmgL(YosemitechY504* parentSense, const char* uuid = "", const char* varCode = Y504_DOMGL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y504_DOMGL_VAR_NUM, - (uint8_t)Y504_DOMGL_RESOLUTION, Y504_DOMGL_VAR_NAME, - Y504_DOMGL_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY504_DOmgL object. - * - * @note This must be tied with a parent YosemitechY504 before it can be - * used. - */ - YosemitechY504_DOmgL() - : Variable((uint8_t)Y504_DOMGL_VAR_NUM, (uint8_t)Y504_DOMGL_RESOLUTION, - Y504_DOMGL_VAR_NAME, Y504_DOMGL_UNIT_NAME, - Y504_DOMGL_DEFAULT_CODE) {} + : Variable(parentSense, Y504_DOMGL_VAR_NUM, Y504_DOMGL_RESOLUTION, + Y504_DOMGL_VAR_NAME, Y504_DOMGL_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY504_DOmgL object - no action needed. */ - ~YosemitechY504_DOmgL() {} + ~YosemitechY504_DOmgL() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY504_H_ diff --git a/src/sensors/YosemitechY510.h b/src/sensors/YosemitechY510.h index fb83c67c6..82b5286b7 100644 --- a/src/sensors/YosemitechY510.h +++ b/src/sensors/YosemitechY510.h @@ -95,7 +95,11 @@ * {{ @ref YosemitechY510_Turbidity::YosemitechY510_Turbidity }} */ /**@{*/ -/// @brief Decimals places in string representation; turbidity should have 2 - +/// @brief Minimum turbidity in nephelometric turbidity units. +#define Y510_TURB_MIN_NTU 0.1 +/// @brief Maximum turbidity in nephelometric turbidity units. +#define Y510_TURB_MAX_NTU 1000.0 +/// @brief Decimal places in string representation; turbidity should have 2 - /// resolution is 0.01 NTU. #define Y510_TURB_RESOLUTION 2 /// @brief Sensor variable number; turbidity is stored in sensorValues[0]. @@ -122,7 +126,11 @@ * {{ @ref YosemitechY510_Temp::YosemitechY510_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature in degrees Celsius. +#define Y510_TEMP_MIN_C 0.0 +/// @brief Maximum temperature in degrees Celsius. +#define Y510_TEMP_MAX_C 50.0 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y510_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -193,7 +201,7 @@ class YosemitechY510 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y510 object */ - ~YosemitechY510() {} + ~YosemitechY510() override = default; }; @@ -221,23 +229,12 @@ class YosemitechY510_Turbidity : public Variable { explicit YosemitechY510_Turbidity( YosemitechY510* parentSense, const char* uuid = "", const char* varCode = Y510_TURB_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y510_TURB_VAR_NUM, - (uint8_t)Y510_TURB_RESOLUTION, Y510_TURB_VAR_NAME, - Y510_TURB_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY510_Turbidity object. - * - * @note This must be tied with a parent YosemitechY510 before it can be - * used. - */ - YosemitechY510_Turbidity() - : Variable((uint8_t)Y510_TURB_VAR_NUM, (uint8_t)Y510_TURB_RESOLUTION, - Y510_TURB_VAR_NAME, Y510_TURB_UNIT_NAME, - Y510_TURB_DEFAULT_CODE) {} + : Variable(parentSense, Y510_TURB_VAR_NUM, Y510_TURB_RESOLUTION, + Y510_TURB_VAR_NAME, Y510_TURB_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY510_Turbidity object - no action needed. */ - ~YosemitechY510_Turbidity() {} + ~YosemitechY510_Turbidity() override = default; }; @@ -265,23 +262,12 @@ class YosemitechY510_Temp : public Variable { explicit YosemitechY510_Temp(YosemitechY510* parentSense, const char* uuid = "", const char* varCode = Y510_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y510_TEMP_VAR_NUM, - (uint8_t)Y510_TEMP_RESOLUTION, Y510_TEMP_VAR_NAME, - Y510_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY510_Temp object. - * - * @note This must be tied with a parent YosemitechY510 before it can be - * used. - */ - YosemitechY510_Temp() - : Variable((uint8_t)Y510_TEMP_VAR_NUM, (uint8_t)Y510_TEMP_RESOLUTION, - Y510_TEMP_VAR_NAME, Y510_TEMP_UNIT_NAME, - Y510_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y510_TEMP_VAR_NUM, Y510_TEMP_RESOLUTION, + Y510_TEMP_VAR_NAME, Y510_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY510_Temp object - no action needed. */ - ~YosemitechY510_Temp() {} + ~YosemitechY510_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY510_H_ diff --git a/src/sensors/YosemitechY511.h b/src/sensors/YosemitechY511.h index 01bff973e..1831b9000 100644 --- a/src/sensors/YosemitechY511.h +++ b/src/sensors/YosemitechY511.h @@ -109,7 +109,11 @@ * {{ @ref YosemitechY511_Turbidity::YosemitechY511_Turbidity }} */ /**@{*/ -/// @brief Decimals places in string representation; turbidity should have 2 - +/// @brief Minimum turbidity; 0.1 NTU +#define Y511_TURB_MIN_NTU 0.1 +/// @brief Maximum turbidity; 1000 NTU +#define Y511_TURB_MAX_NTU 1000 +/// @brief Decimal places in string representation; turbidity should have 2 - /// resolution is 0.01 NTU. #define Y511_TURB_RESOLUTION 2 /// @brief Sensor variable number; turbidity is stored in sensorValues[0]. @@ -136,7 +140,11 @@ * {{ @ref YosemitechY511_Temp::YosemitechY511_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y511_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y511_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y511_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -207,7 +215,7 @@ class YosemitechY511 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y511 object */ - ~YosemitechY511() {} + ~YosemitechY511() override = default; }; @@ -235,23 +243,12 @@ class YosemitechY511_Turbidity : public Variable { explicit YosemitechY511_Turbidity( YosemitechY511* parentSense, const char* uuid = "", const char* varCode = Y511_TURB_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y511_TURB_VAR_NUM, - (uint8_t)Y511_TURB_RESOLUTION, Y511_TURB_VAR_NAME, - Y511_TURB_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY511_Turbidity object. - * - * @note This must be tied with a parent YosemitechY511 before it can be - * used. - */ - YosemitechY511_Turbidity() - : Variable((uint8_t)Y511_TURB_VAR_NUM, (uint8_t)Y511_TURB_RESOLUTION, - Y511_TURB_VAR_NAME, Y511_TURB_UNIT_NAME, - Y511_TURB_DEFAULT_CODE) {} + : Variable(parentSense, Y511_TURB_VAR_NUM, Y511_TURB_RESOLUTION, + Y511_TURB_VAR_NAME, Y511_TURB_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY511_Turbidity object - no action needed. */ - ~YosemitechY511_Turbidity() {} + ~YosemitechY511_Turbidity() override = default; }; @@ -279,25 +276,14 @@ class YosemitechY511_Temp : public Variable { explicit YosemitechY511_Temp(YosemitechY511* parentSense, const char* uuid = "", const char* varCode = Y511_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y511_TEMP_VAR_NUM, - (uint8_t)Y511_TEMP_RESOLUTION, Y511_TEMP_VAR_NAME, - Y511_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY511_Temp object. - * - * @note This must be tied with a parent YosemitechY511 before it can be - * used. - */ - YosemitechY511_Temp() - : Variable((uint8_t)Y511_TEMP_VAR_NUM, (uint8_t)Y511_TEMP_RESOLUTION, - Y511_TEMP_VAR_NAME, Y511_TEMP_UNIT_NAME, - Y511_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y511_TEMP_VAR_NUM, Y511_TEMP_RESOLUTION, + Y511_TEMP_VAR_NAME, Y511_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY511_Temp object - no action needed. */ - ~YosemitechY511_Temp() {} + ~YosemitechY511_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY511_H_ -// cSpell:ignore Wipered +// cSpell:words Wipered diff --git a/src/sensors/YosemitechY513.h b/src/sensors/YosemitechY513.h index 3c3d7906c..808e6010d 100644 --- a/src/sensors/YosemitechY513.h +++ b/src/sensors/YosemitechY513.h @@ -95,7 +95,11 @@ * {{ @ref YosemitechY513_BGA::YosemitechY513_BGA }} */ /**@{*/ -/// @brief Decimals places in string representation; blue green algae +/// @brief Minimum BGA concentration; 0 µg/L +#define Y513_BGA_MIN_UGPL 0 +/// @brief Maximum BGA concentration; 400 µg/L +#define Y513_BGA_MAX_UGPL 400 +/// @brief Decimal places in string representation; blue green algae /// concentration should have 1 - resolution is 0.1 µg/L / 0.1 RFU. #define Y513_BGA_RESOLUTION 1 /// @brief Sensor variable number; blue green algae concentration is stored in @@ -123,7 +127,11 @@ * {{ @ref YosemitechY513_Temp::YosemitechY513_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y513_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y513_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y513_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -194,7 +202,7 @@ class YosemitechY513 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y513 object */ - ~YosemitechY513() {} + ~YosemitechY513() override = default; }; @@ -222,24 +230,13 @@ class YosemitechY513_BGA : public Variable { explicit YosemitechY513_BGA(YosemitechY513* parentSense, const char* uuid = "", const char* varCode = Y513_BGA_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y513_BGA_VAR_NUM, - (uint8_t)Y513_BGA_RESOLUTION, Y513_BGA_VAR_NAME, - Y513_BGA_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY513_BGA object. - * - * @note This must be tied with a parent YosemitechY513 before it can be - * used. - */ - YosemitechY513_BGA() - : Variable((uint8_t)Y513_BGA_VAR_NUM, (uint8_t)Y513_BGA_RESOLUTION, - Y513_BGA_VAR_NAME, Y513_BGA_UNIT_NAME, - Y513_BGA_DEFAULT_CODE) {} + : Variable(parentSense, Y513_BGA_VAR_NUM, Y513_BGA_RESOLUTION, + Y513_BGA_VAR_NAME, Y513_BGA_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY513_BGA() object - no action * needed. */ - ~YosemitechY513_BGA() {} + ~YosemitechY513_BGA() override = default; }; @@ -267,23 +264,14 @@ class YosemitechY513_Temp : public Variable { explicit YosemitechY513_Temp(YosemitechY513* parentSense, const char* uuid = "", const char* varCode = Y513_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y513_TEMP_VAR_NUM, - (uint8_t)Y513_TEMP_RESOLUTION, Y513_TEMP_VAR_NAME, - Y513_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY513_Temp object. - * - * @note This must be tied with a parent YosemitechY513 before it can be - * used. - */ - YosemitechY513_Temp() - : Variable((uint8_t)Y513_TEMP_VAR_NUM, (uint8_t)Y513_TEMP_RESOLUTION, - Y513_TEMP_VAR_NAME, Y513_TEMP_UNIT_NAME, - Y513_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y513_TEMP_VAR_NUM, Y513_TEMP_RESOLUTION, + Y513_TEMP_VAR_NAME, Y513_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY513_Temp object - no action needed. */ - ~YosemitechY513_Temp() {} + ~YosemitechY513_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY513_H_ + +// cSpell:words UGPL diff --git a/src/sensors/YosemitechY514.h b/src/sensors/YosemitechY514.h index 7467faafb..5d561f65e 100644 --- a/src/sensors/YosemitechY514.h +++ b/src/sensors/YosemitechY514.h @@ -97,7 +97,11 @@ * {{ @ref YosemitechY514_Chlorophyll::YosemitechY514_Chlorophyll }} */ /**@{*/ -/// @brief Decimals places in string representation; chlorophyll concentration +/// @brief Minimum chlorophyll concentration; 0 µg/L +#define Y514_CHLORO_MIN_UGPL 0 +/// @brief Maximum chlorophyll concentration; 400 µg/L +#define Y514_CHLORO_MAX_UGPL 400 +/// @brief Decimal places in string representation; chlorophyll concentration /// should have 1 - resolution is 0.1 µg/L / 0.1 RFU. #define Y514_CHLORO_RESOLUTION 1 /// @brief Sensor variable number; chlorophyll concentration is stored in @@ -125,7 +129,11 @@ * {{ @ref YosemitechY514_Temp::YosemitechY514_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y514_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y514_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y514_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -196,7 +204,7 @@ class YosemitechY514 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y514 object */ - ~YosemitechY514() {} + ~YosemitechY514() override = default; }; @@ -224,24 +232,14 @@ class YosemitechY514_Chlorophyll : public Variable { explicit YosemitechY514_Chlorophyll( YosemitechY514* parentSense, const char* uuid = "", const char* varCode = Y514_CHLORO_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y514_CHLORO_VAR_NUM, - (uint8_t)Y514_CHLORO_RESOLUTION, Y514_CHLORO_VAR_NAME, - Y514_CHLORO_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY514_Chlorophyll object. - * - * @note This must be tied with a parent YosemitechY514 before it can be - * used. - */ - YosemitechY514_Chlorophyll() - : Variable((uint8_t)Y514_CHLORO_VAR_NUM, - (uint8_t)Y514_CHLORO_RESOLUTION, Y514_CHLORO_VAR_NAME, - Y514_CHLORO_UNIT_NAME, Y514_CHLORO_DEFAULT_CODE) {} + : Variable(parentSense, Y514_CHLORO_VAR_NUM, Y514_CHLORO_RESOLUTION, + Y514_CHLORO_VAR_NAME, Y514_CHLORO_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the YosemitechY514_Chlorophyll() object - no action * needed. */ - ~YosemitechY514_Chlorophyll() {} + ~YosemitechY514_Chlorophyll() override = default; }; @@ -269,25 +267,14 @@ class YosemitechY514_Temp : public Variable { explicit YosemitechY514_Temp(YosemitechY514* parentSense, const char* uuid = "", const char* varCode = Y514_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y514_TEMP_VAR_NUM, - (uint8_t)Y514_TEMP_RESOLUTION, Y514_TEMP_VAR_NAME, - Y514_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY514_Temp object. - * - * @note This must be tied with a parent YosemitechY514 before it can be - * used. - */ - YosemitechY514_Temp() - : Variable((uint8_t)Y514_TEMP_VAR_NUM, (uint8_t)Y514_TEMP_RESOLUTION, - Y514_TEMP_VAR_NAME, Y514_TEMP_UNIT_NAME, - Y514_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y514_TEMP_VAR_NUM, Y514_TEMP_RESOLUTION, + Y514_TEMP_VAR_NAME, Y514_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY514_Temp object - no action needed. */ - ~YosemitechY514_Temp() {} + ~YosemitechY514_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY514_H_ -// cSpell:ignore Wipered Y514Chloro +// cSpell:words Wipered Y514Chloro UGPL diff --git a/src/sensors/YosemitechY520.h b/src/sensors/YosemitechY520.h index 02ab4616d..f16269181 100644 --- a/src/sensors/YosemitechY520.h +++ b/src/sensors/YosemitechY520.h @@ -96,7 +96,11 @@ * {{ @ref YosemitechY520_Cond::YosemitechY520_Cond }} */ /**@{*/ -/// @brief Decimals places in string representation; conductivity should have 1 +/// @brief Minimum conductivity; 1 µS/cm +#define Y520_COND_MIN_USCM 1 +/// @brief Maximum conductivity; 200000 µS/cm (200 mS/cm) +#define Y520_COND_MAX_USCM 200000 +/// @brief Decimal places in string representation; conductivity should have 1 /// - resolution is 0.1 µS/cm. #define Y520_COND_RESOLUTION 1 /// @brief Sensor variable number; conductivity is stored in sensorValues[0]. @@ -123,7 +127,11 @@ * {{ @ref YosemitechY520_Temp::YosemitechY520_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y520_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y520_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y520_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -194,7 +202,7 @@ class YosemitechY520 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y520 object */ - ~YosemitechY520() {} + ~YosemitechY520() override = default; }; @@ -222,23 +230,12 @@ class YosemitechY520_Cond : public Variable { explicit YosemitechY520_Cond(YosemitechY520* parentSense, const char* uuid = "", const char* varCode = Y520_COND_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y520_COND_VAR_NUM, - (uint8_t)Y520_COND_RESOLUTION, Y520_COND_VAR_NAME, - Y520_COND_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY520_Cond object. - * - * @note This must be tied with a parent YosemitechY520 before it can be - * used. - */ - YosemitechY520_Cond() - : Variable((uint8_t)Y520_COND_VAR_NUM, (uint8_t)Y520_COND_RESOLUTION, - Y520_COND_VAR_NAME, Y520_COND_UNIT_NAME, - Y520_COND_DEFAULT_CODE) {} + : Variable(parentSense, Y520_COND_VAR_NUM, Y520_COND_RESOLUTION, + Y520_COND_VAR_NAME, Y520_COND_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY520_Cond object - no action needed. */ - ~YosemitechY520_Cond() {} + ~YosemitechY520_Cond() override = default; }; @@ -266,23 +263,12 @@ class YosemitechY520_Temp : public Variable { explicit YosemitechY520_Temp(YosemitechY520* parentSense, const char* uuid = "", const char* varCode = Y520_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y520_TEMP_VAR_NUM, - (uint8_t)Y520_TEMP_RESOLUTION, Y520_TEMP_VAR_NAME, - Y520_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY520_Temp object. - * - * @note This must be tied with a parent YosemitechY520 before it can be - * used. - */ - YosemitechY520_Temp() - : Variable((uint8_t)Y520_TEMP_VAR_NUM, (uint8_t)Y520_TEMP_RESOLUTION, - Y520_TEMP_VAR_NAME, Y520_TEMP_UNIT_NAME, - Y520_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y520_TEMP_VAR_NUM, Y520_TEMP_RESOLUTION, + Y520_TEMP_VAR_NAME, Y520_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY520_Temp object - no action needed. */ - ~YosemitechY520_Temp() {} + ~YosemitechY520_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY520_H_ diff --git a/src/sensors/YosemitechY532.h b/src/sensors/YosemitechY532.h index 970532d83..2242680e6 100644 --- a/src/sensors/YosemitechY532.h +++ b/src/sensors/YosemitechY532.h @@ -96,7 +96,11 @@ * {{ @ref YosemitechY532_pH::YosemitechY532_pH }} */ /**@{*/ -/// @brief Decimals places in string representation; pH should have 2 - +/// @brief Minimum pH; 2 pH units +#define Y532_PH_MIN_PH 2 +/// @brief Maximum pH; 12 pH units +#define Y532_PH_MAX_PH 12 +/// @brief Decimal places in string representation; pH should have 2 - /// resolution is 0.01 pH units. #define Y532_PH_RESOLUTION 2 /// @brief Sensor variable number; pH is stored in sensorValues[0]. @@ -122,7 +126,11 @@ * {{ @ref YosemitechY532_Temp::YosemitechY532_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y532_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y532_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y532_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -149,7 +157,11 @@ * {{ @ref YosemitechY532_Voltage::YosemitechY532_Voltage }} */ /**@{*/ -/// @brief Decimals places in string representation; voltage should have 0 - +/// @brief Minimum voltage; -999 mV +#define Y532_VOLTAGE_MIN_MV -999 +/// @brief Maximum voltage; 999 mV +#define Y532_VOLTAGE_MAX_MV 999 +/// @brief Decimal places in string representation; voltage should have 0 - /// resolution is 1mV. #define Y532_VOLTAGE_RESOLUTION 0 /// @brief Sensor variable number; voltage is stored in sensorValues[2]. @@ -220,7 +232,7 @@ class YosemitechY532 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y532 object */ - ~YosemitechY532() {} + ~YosemitechY532() override = default; }; @@ -248,22 +260,12 @@ class YosemitechY532_pH : public Variable { explicit YosemitechY532_pH(YosemitechY532* parentSense, const char* uuid = "", const char* varCode = Y532_PH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y532_PH_VAR_NUM, - (uint8_t)Y532_PH_RESOLUTION, Y532_PH_VAR_NAME, - Y532_PH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY532_pH object. - * - * @note This must be tied with a parent YosemitechY532 before it can be - * used. - */ - YosemitechY532_pH() - : Variable((uint8_t)Y532_PH_VAR_NUM, (uint8_t)Y532_PH_RESOLUTION, - Y532_PH_VAR_NAME, Y532_PH_UNIT_NAME, Y532_PH_DEFAULT_CODE) {} + : Variable(parentSense, Y532_PH_VAR_NUM, Y532_PH_RESOLUTION, + Y532_PH_VAR_NAME, Y532_PH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY532_pH object - no action needed. */ - ~YosemitechY532_pH() {} + ~YosemitechY532_pH() override = default; }; @@ -291,23 +293,12 @@ class YosemitechY532_Temp : public Variable { explicit YosemitechY532_Temp(YosemitechY532* parentSense, const char* uuid = "", const char* varCode = Y532_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y532_TEMP_VAR_NUM, - (uint8_t)Y532_TEMP_RESOLUTION, Y532_TEMP_VAR_NAME, - Y532_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY532_Temp object. - * - * @note This must be tied with a parent YosemitechY532 before it can be - * used. - */ - YosemitechY532_Temp() - : Variable((uint8_t)Y532_TEMP_VAR_NUM, (uint8_t)Y532_TEMP_RESOLUTION, - Y532_TEMP_VAR_NAME, Y532_TEMP_UNIT_NAME, - Y532_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y532_TEMP_VAR_NUM, Y532_TEMP_RESOLUTION, + Y532_TEMP_VAR_NAME, Y532_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY532_Temp object - no action needed. */ - ~YosemitechY532_Temp() {} + ~YosemitechY532_Temp() override = default; }; @@ -335,23 +326,13 @@ class YosemitechY532_Voltage : public Variable { explicit YosemitechY532_Voltage( YosemitechY532* parentSense, const char* uuid = "", const char* varCode = Y532_VOLTAGE_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y532_VOLTAGE_VAR_NUM, - (uint8_t)Y532_VOLTAGE_RESOLUTION, Y532_VOLTAGE_VAR_NAME, - Y532_VOLTAGE_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY532_Voltage object. - * - * @note This must be tied with a parent YosemitechY532 before it can be - * used. - */ - YosemitechY532_Voltage() - : Variable((uint8_t)Y532_VOLTAGE_VAR_NUM, - (uint8_t)Y532_VOLTAGE_RESOLUTION, Y532_VOLTAGE_VAR_NAME, - Y532_VOLTAGE_UNIT_NAME, Y532_VOLTAGE_DEFAULT_CODE) {} + : Variable(parentSense, Y532_VOLTAGE_VAR_NUM, Y532_VOLTAGE_RESOLUTION, + Y532_VOLTAGE_VAR_NAME, Y532_VOLTAGE_UNIT_NAME, varCode, + uuid) {} /** * @brief Destroy the YosemitechY532_Voltage object - no action needed. */ - ~YosemitechY532_Voltage() {} + ~YosemitechY532_Voltage() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY532_H_ diff --git a/src/sensors/YosemitechY533.h b/src/sensors/YosemitechY533.h index e39eaf86c..eef6c29e2 100644 --- a/src/sensors/YosemitechY533.h +++ b/src/sensors/YosemitechY533.h @@ -96,7 +96,11 @@ * {{ @ref YosemitechY533_ORP::YosemitechY533_ORP }} */ /**@{*/ -/// @brief Decimals places in string representation; ph should have 2 - +/// @brief Minimum ORP; -999 mV +#define Y533_ORP_MIN_MV -999 +/// @brief Maximum ORP; 999 mV +#define Y533_ORP_MAX_MV 999 +/// @brief Decimal places in string representation; ORP should have 0 - /// resolution is 1 mV units. #define Y533_ORP_RESOLUTION 0 /// @brief Sensor variable number; ORP is stored in sensorValues[0]. @@ -125,7 +129,11 @@ * {{ @ref YosemitechY533_Temp::YosemitechY533_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y533_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y533_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y533_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -196,7 +204,7 @@ class YosemitechY533 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y533 object */ - ~YosemitechY533() {} + ~YosemitechY533() override = default; }; @@ -212,35 +220,24 @@ class YosemitechY533 : public YosemitechParent { class YosemitechY533_ORP : public Variable { public: /** - * @brief Construct a new YosemitechY533_pH object. + * @brief Construct a new YosemitechY533_ORP object. * * @param parentSense The parent YosemitechY533 providing the result * values. * @param uuid A universally unique identifier (UUID or GUID) for the * variable; optional with the default value of an empty string. * @param varCode A short code to help identify the variable in files; - * optional with a default value of "Y533pH". + * optional with a default value of "Y533ORP". */ explicit YosemitechY533_ORP(YosemitechY533* parentSense, const char* uuid = "", const char* varCode = Y533_ORP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y533_ORP_VAR_NUM, - (uint8_t)Y533_ORP_RESOLUTION, Y533_ORP_VAR_NAME, - Y533_ORP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY533_ORP object. - * - * @note This must be tied with a parent YosemitechY533 before it can be - * used. - */ - YosemitechY533_ORP() - : Variable((uint8_t)Y533_ORP_VAR_NUM, (uint8_t)Y533_ORP_RESOLUTION, - Y533_ORP_VAR_NAME, Y533_ORP_UNIT_NAME, - Y533_ORP_DEFAULT_CODE) {} + : Variable(parentSense, Y533_ORP_VAR_NUM, Y533_ORP_RESOLUTION, + Y533_ORP_VAR_NAME, Y533_ORP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY533_ORP object - no action needed. */ - ~YosemitechY533_ORP() {} + ~YosemitechY533_ORP() override = default; }; @@ -268,23 +265,12 @@ class YosemitechY533_Temp : public Variable { explicit YosemitechY533_Temp(YosemitechY533* parentSense, const char* uuid = "", const char* varCode = Y533_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y533_TEMP_VAR_NUM, - (uint8_t)Y533_TEMP_RESOLUTION, Y533_TEMP_VAR_NAME, - Y533_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY533_Temp object. - * - * @note This must be tied with a parent YosemitechY533 before it can be - * used. - */ - YosemitechY533_Temp() - : Variable((uint8_t)Y533_TEMP_VAR_NUM, (uint8_t)Y533_TEMP_RESOLUTION, - Y533_TEMP_VAR_NAME, Y533_TEMP_UNIT_NAME, - Y533_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y533_TEMP_VAR_NUM, Y533_TEMP_RESOLUTION, + Y533_TEMP_VAR_NAME, Y533_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY533_Temp object - no action needed. */ - ~YosemitechY533_Temp() {} + ~YosemitechY533_Temp() override = default; }; /**@}*/ diff --git a/src/sensors/YosemitechY551.h b/src/sensors/YosemitechY551.h index 72c50199f..b7a064dfa 100644 --- a/src/sensors/YosemitechY551.h +++ b/src/sensors/YosemitechY551.h @@ -99,7 +99,11 @@ * {{ @ref YosemitechY551_COD::YosemitechY551_COD }} */ /**@{*/ -/// @brief Decimals places in string representation; cod should have 2 - +/// @brief Minimum COD; 0.75 mg/L +#define Y551_COD_MIN_MGPL 0.75 +/// @brief Maximum COD; 370 mg/L +#define Y551_COD_MAX_MGPL 370 +/// @brief Decimal places in string representation; cod should have 2 - /// resolution is 0.01 mg/L COD. #define Y551_COD_RESOLUTION 2 /// @brief Sensor variable number; COD is stored in sensorValues[0]. @@ -126,7 +130,11 @@ * {{ @ref YosemitechY551_Temp::YosemitechY551_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Minimum temperature; 5°C +#define Y551_TEMP_MIN_C 5 +/// @brief Maximum temperature; 45°C +#define Y551_TEMP_MAX_C 45 +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define Y551_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -153,7 +161,11 @@ * {{ @ref YosemitechY551_Turbidity::YosemitechY551_Turbidity }} */ /**@{*/ -/// @brief Decimals places in string representation; turbidity should have 2 - +/// @brief Minimum turbidity; 0.1 NTU +#define Y551_TURB_MIN_NTU 0.1 +/// @brief Maximum turbidity; 1000 NTU +#define Y551_TURB_MAX_NTU 1000 +/// @brief Decimal places in string representation; turbidity should have 2 - /// resolution is 0.01 NTU. #define Y551_TURB_RESOLUTION 2 /// @brief Sensor variable number; turbidity is stored in sensorValues[2]. @@ -224,7 +236,7 @@ class YosemitechY551 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y551 object */ - ~YosemitechY551() {} + ~YosemitechY551() override = default; }; @@ -252,23 +264,12 @@ class YosemitechY551_COD : public Variable { explicit YosemitechY551_COD(YosemitechY551* parentSense, const char* uuid = "", const char* varCode = Y551_COD_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y551_COD_VAR_NUM, - (uint8_t)Y551_COD_RESOLUTION, Y551_COD_VAR_NAME, - Y551_COD_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY551_COD object. - * - * @note This must be tied with a parent YosemitechY551 before it can be - * used. - */ - YosemitechY551_COD() - : Variable((uint8_t)Y551_COD_VAR_NUM, (uint8_t)Y551_COD_RESOLUTION, - Y551_COD_VAR_NAME, Y551_COD_UNIT_NAME, - Y551_COD_DEFAULT_CODE) {} + : Variable(parentSense, Y551_COD_VAR_NUM, Y551_COD_RESOLUTION, + Y551_COD_VAR_NAME, Y551_COD_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY551_COD object - no action needed. */ - ~YosemitechY551_COD() {} + ~YosemitechY551_COD() override = default; }; @@ -296,23 +297,12 @@ class YosemitechY551_Temp : public Variable { explicit YosemitechY551_Temp(YosemitechY551* parentSense, const char* uuid = "", const char* varCode = Y551_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y551_TEMP_VAR_NUM, - (uint8_t)Y551_TEMP_RESOLUTION, Y551_TEMP_VAR_NAME, - Y551_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY551_Temp object. - * - * @note This must be tied with a parent YosemitechY551 before it can be - * used. - */ - YosemitechY551_Temp() - : Variable((uint8_t)Y551_TEMP_VAR_NUM, (uint8_t)Y551_TEMP_RESOLUTION, - Y551_TEMP_VAR_NAME, Y551_TEMP_UNIT_NAME, - Y551_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y551_TEMP_VAR_NUM, Y551_TEMP_RESOLUTION, + Y551_TEMP_VAR_NAME, Y551_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY551_Temp object - no action needed. */ - ~YosemitechY551_Temp() {} + ~YosemitechY551_Temp() override = default; }; @@ -340,23 +330,12 @@ class YosemitechY551_Turbidity : public Variable { explicit YosemitechY551_Turbidity( YosemitechY551* parentSense, const char* uuid = "", const char* varCode = Y551_TURB_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y551_TURB_VAR_NUM, - (uint8_t)Y551_TURB_RESOLUTION, Y551_TURB_VAR_NAME, - Y551_TURB_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY551_Turbidity object. - * - * @note This must be tied with a parent YosemitechY551 before it can be - * used. - */ - YosemitechY551_Turbidity() - : Variable((uint8_t)Y551_TURB_VAR_NUM, (uint8_t)Y551_TURB_RESOLUTION, - Y551_TURB_VAR_NAME, Y551_TURB_UNIT_NAME, - Y551_TURB_DEFAULT_CODE) {} + : Variable(parentSense, Y551_TURB_VAR_NUM, Y551_TURB_RESOLUTION, + Y551_TURB_VAR_NAME, Y551_TURB_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY551_Turbidity object - no action needed. */ - ~YosemitechY551_Turbidity() {} + ~YosemitechY551_Turbidity() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY551_H_ diff --git a/src/sensors/YosemitechY560.h b/src/sensors/YosemitechY560.h index c5d228755..63d1fead5 100644 --- a/src/sensors/YosemitechY560.h +++ b/src/sensors/YosemitechY560.h @@ -97,7 +97,11 @@ * {{ @ref YosemitechY560_NH4_N::YosemitechY560_NH4_N }} */ /**@{*/ -/// @brief Decimals places in string representation; NH4_N should have 1 - +/// @brief Minimum NH4_N concentration; 0 mg/L +#define Y560_NH4_N_MIN_MGPL 0 +/// @brief Maximum NH4_N concentration; 100 mg/L +#define Y560_NH4_N_MAX_MGPL 100 +/// @brief Decimal places in string representation; NH4_N should have 1 - /// resolution is 0.1 mg/L. #define Y560_NH4_N_RESOLUTION 1 /// @brief Sensor variable number; NH4_N is stored in sensorValues[0]. @@ -124,7 +128,11 @@ * {{ @ref YosemitechY560_Temp::YosemitechY560_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y560_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y560_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y560_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -151,7 +159,11 @@ * {{ @ref YosemitechY560_pH::YosemitechY560_pH }} */ /**@{*/ -/// @brief Decimals places in string representation; pH should have 2 - +/// @brief Minimum pH; 2 pH units +#define Y560_PH_MIN_PH 2 +/// @brief Maximum pH; 12 pH units +#define Y560_PH_MAX_PH 12 +/// @brief Decimal places in string representation; pH should have 2 - /// resolution is 0.01 pH units. #define Y560_PH_RESOLUTION 2 /// @brief Sensor variable number; pH is stored in sensorValues[2]. @@ -221,7 +233,7 @@ class YosemitechY560 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y560 object */ - ~YosemitechY560() {} + ~YosemitechY560() override = default; }; @@ -249,23 +261,12 @@ class YosemitechY560_NH4_N : public Variable { explicit YosemitechY560_NH4_N(YosemitechY560* parentSense, const char* uuid = "", const char* varCode = Y560_NH4_N_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y560_NH4_N_VAR_NUM, - (const uint8_t)Y560_NH4_N_RESOLUTION, Y560_NH4_N_VAR_NAME, - Y560_NH4_N_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY560_NH4_N object. - * - * @note This must be tied with a parent YosemitechY560 before it can be - * used. - */ - YosemitechY560_NH4_N() - : Variable((uint8_t)Y560_NH4_N_VAR_NUM, - (const uint8_t)Y560_NH4_N_RESOLUTION, Y560_NH4_N_VAR_NAME, - Y560_NH4_N_UNIT_NAME, Y560_NH4_N_DEFAULT_CODE) {} + : Variable(parentSense, Y560_NH4_N_VAR_NUM, Y560_NH4_N_RESOLUTION, + Y560_NH4_N_VAR_NAME, Y560_NH4_N_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY560_NH4_N object - no action needed. */ - ~YosemitechY560_NH4_N() {} + ~YosemitechY560_NH4_N() override = default; }; @@ -293,23 +294,12 @@ class YosemitechY560_Temp : public Variable { explicit YosemitechY560_Temp(YosemitechY560* parentSense, const char* uuid = "", const char* varCode = Y560_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y560_TEMP_VAR_NUM, - (uint8_t)Y560_TEMP_RESOLUTION, Y560_TEMP_VAR_NAME, - Y560_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY560_Temp object. - * - * @note This must be tied with a parent YosemitechY560 before it can be - * used. - */ - YosemitechY560_Temp() - : Variable((uint8_t)Y560_TEMP_VAR_NUM, (uint8_t)Y560_TEMP_RESOLUTION, - Y560_TEMP_VAR_NAME, Y560_TEMP_UNIT_NAME, - Y560_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y560_TEMP_VAR_NUM, Y560_TEMP_RESOLUTION, + Y560_TEMP_VAR_NAME, Y560_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY560_Temp object - no action needed. */ - ~YosemitechY560_Temp() {} + ~YosemitechY560_Temp() override = default; }; @@ -337,22 +327,12 @@ class YosemitechY560_pH : public Variable { explicit YosemitechY560_pH(YosemitechY560* parentSense, const char* uuid = "", const char* varCode = Y560_PH_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y560_PH_VAR_NUM, - (uint8_t)Y560_PH_RESOLUTION, Y560_PH_VAR_NAME, - Y560_PH_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY560_pH object. - * - * @note This must be tied with a parent YosemitechY560 before it can be - * used. - */ - YosemitechY560_pH() - : Variable((uint8_t)Y560_PH_VAR_NUM, (uint8_t)Y560_PH_RESOLUTION, - Y560_PH_VAR_NAME, Y560_PH_UNIT_NAME, Y560_PH_DEFAULT_CODE) {} + : Variable(parentSense, Y560_PH_VAR_NUM, Y560_PH_RESOLUTION, + Y560_PH_VAR_NAME, Y560_PH_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY560_pH object - no action needed. */ - ~YosemitechY560_pH() {} + ~YosemitechY560_pH() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY560_H_ diff --git a/src/sensors/YosemitechY700.h b/src/sensors/YosemitechY700.h index f1a8cbd02..7a66fdc58 100644 --- a/src/sensors/YosemitechY700.h +++ b/src/sensors/YosemitechY700.h @@ -95,7 +95,11 @@ * {{ @ref YosemitechY700_Pressure::YosemitechY700_Pressure }} */ /**@{*/ -/// @brief Decimals places in string representation; Pressure should have 1 +/// @brief Minimum pressure; 0 mmH2O +#define Y700_PRES_MIN_MMH2O 0 +/// @brief Maximum pressure; 100000 mmH2O (100 mH2O, depending on model) +#define Y700_PRES_MAX_MMH2O 100000 +/// @brief Decimal places in string representation; Pressure should have 2 /// - resolution is 0.01 mm. #define Y700_PRES_RESOLUTION 2 /// @brief Sensor variable number; pressure is stored in sensorValues[0]. @@ -122,7 +126,11 @@ * {{ @ref YosemitechY700_Temp::YosemitechY700_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 1 - +/// @brief Minimum temperature; 0°C +#define Y700_TEMP_MIN_C 0 +/// @brief Maximum temperature; 50°C +#define Y700_TEMP_MAX_C 50 +/// @brief Decimal places in string representation; temperature should have 1 - /// resolution is 0.1°C. #define Y700_TEMP_RESOLUTION 1 /// @brief Sensor variable number; temperature is stored in sensorValues[1]. @@ -193,7 +201,7 @@ class YosemitechY700 : public YosemitechParent { /** * @brief Destroy the Yosemitech Y700 object */ - ~YosemitechY700() {} + ~YosemitechY700() override = default; }; @@ -221,23 +229,12 @@ class YosemitechY700_Pressure : public Variable { explicit YosemitechY700_Pressure( YosemitechY700* parentSense, const char* uuid = "", const char* varCode = Y700_PRES_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y700_PRES_VAR_NUM, - (uint8_t)Y700_PRES_RESOLUTION, Y700_PRES_VAR_NAME, - Y700_PRES_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY700_Pressure object. - * - * @note This must be tied with a parent YosemitechY700 before it can be - * used. - */ - YosemitechY700_Pressure() - : Variable((uint8_t)Y700_PRES_VAR_NUM, (uint8_t)Y700_PRES_RESOLUTION, - Y700_PRES_VAR_NAME, Y700_PRES_UNIT_NAME, - Y700_PRES_DEFAULT_CODE) {} + : Variable(parentSense, Y700_PRES_VAR_NUM, Y700_PRES_RESOLUTION, + Y700_PRES_VAR_NAME, Y700_PRES_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY700_Pressure object - no action needed. */ - ~YosemitechY700_Pressure() {} + ~YosemitechY700_Pressure() override = default; }; @@ -265,23 +262,12 @@ class YosemitechY700_Temp : public Variable { explicit YosemitechY700_Temp(YosemitechY700* parentSense, const char* uuid = "", const char* varCode = Y700_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)Y700_TEMP_VAR_NUM, - (uint8_t)Y700_TEMP_RESOLUTION, Y700_TEMP_VAR_NAME, - Y700_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new YosemitechY700_Temp object. - * - * @note This must be tied with a parent YosemitechY700 before it can be - * used. - */ - YosemitechY700_Temp() - : Variable((uint8_t)Y700_TEMP_VAR_NUM, (uint8_t)Y700_TEMP_RESOLUTION, - Y700_TEMP_VAR_NAME, Y700_TEMP_UNIT_NAME, - Y700_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, Y700_TEMP_VAR_NUM, Y700_TEMP_RESOLUTION, + Y700_TEMP_VAR_NAME, Y700_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the YosemitechY700_Temp object - no action needed. */ - ~YosemitechY700_Temp() {} + ~YosemitechY700_Temp() override = default; }; /**@}*/ #endif // SRC_SENSORS_YOSEMITECHY700_H_ diff --git a/src/sensors/ZebraTechDOpto.h b/src/sensors/ZebraTechDOpto.h index 6ac86e297..13145c884 100644 --- a/src/sensors/ZebraTechDOpto.h +++ b/src/sensors/ZebraTechDOpto.h @@ -109,11 +109,15 @@ * The temperature variable from a ZebraTech D-Opto * - Range is not specified in sensor datasheet * - Accuracy is ± 0.1°C + * - Resolution: 0.01°C + * + * @todo Find and define minimum and maximum temperature measurement range from + * a ZebraTech D-Opto. * * {{ @ref ZebraTechDOpto_Temp::ZebraTechDOpto_Temp }} */ /**@{*/ -/// @brief Decimals places in string representation; temperature should have 2 - +/// @brief Decimal places in string representation; temperature should have 2 - /// resolution is 0.01°C. #define DOPTO_TEMP_RESOLUTION 2 /// @brief Sensor variable number; temperature is stored in sensorValues[0]. @@ -136,11 +140,15 @@ * The percent saturation variable from a ZebraTech D-Opto * - Range is not specified in sensor datasheet * - Accuracy is 1 % of reading or 0.02PPM, whichever is greater + * - Resolution: 0.01% + * + * @todo Find and define minimum and maximum dissolved oxygen percent saturation + * range from a ZebraTech D-Opto. * * {{ @ref ZebraTechDOpto_DOpct::ZebraTechDOpto_DOpct }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen percent +/// @brief Decimal places in string representation; dissolved oxygen percent /// should have 2. #define DOPTO_DOPCT_RESOLUTION 2 /// @brief Sensor variable number; dissolved oxygen percent is stored in @@ -164,11 +172,15 @@ * The DO concentration variable from a ZebraTech D-Opto * - Range is not specified in sensor datasheet * - Accuracy is 1 % of reading or 0.02PPM, whichever is greater + * - Resolution: 0.001 PPM + * + * @todo Find and define minimum and maximum dissolved oxygen concentration + * range from a ZebraTech D-Opto. * * {{ @ref ZebraTechDOpto_DOmgL::ZebraTechDOpto_DOmgL }} */ /**@{*/ -/// @brief Decimals places in string representation; dissolved oxygen +/// @brief Decimal places in string representation; dissolved oxygen /// concentration should have 3 - resolution is 0.01 % / 0.001 PPM. #define DOPTO_DOMGL_RESOLUTION 3 /// @brief Sensor variable number; dissolved oxygen concentration is stored in @@ -249,7 +261,7 @@ class ZebraTechDOpto : public SDI12Sensors { /** * @brief Destroy the Zebra-Tech DOpto object */ - ~ZebraTechDOpto() {} + ~ZebraTechDOpto() override = default; }; @@ -277,23 +289,12 @@ class ZebraTechDOpto_Temp : public Variable { explicit ZebraTechDOpto_Temp(ZebraTechDOpto* parentSense, const char* uuid = "", const char* varCode = DOPTO_TEMP_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DOPTO_TEMP_VAR_NUM, - (uint8_t)DOPTO_TEMP_RESOLUTION, DOPTO_TEMP_VAR_NAME, - DOPTO_TEMP_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ZebraTechDOpto_Temp object. - * - * @note This must be tied with a parent ZebraTechDOpto before it can be - * used. - */ - ZebraTechDOpto_Temp() - : Variable((uint8_t)DOPTO_TEMP_VAR_NUM, (uint8_t)DOPTO_TEMP_RESOLUTION, - DOPTO_TEMP_VAR_NAME, DOPTO_TEMP_UNIT_NAME, - DOPTO_TEMP_DEFAULT_CODE) {} + : Variable(parentSense, DOPTO_TEMP_VAR_NUM, DOPTO_TEMP_RESOLUTION, + DOPTO_TEMP_VAR_NAME, DOPTO_TEMP_UNIT_NAME, varCode, uuid) {} /** * @brief Destroy the ZebraTechDOpto_Temp object - no action needed. */ - ~ZebraTechDOpto_Temp() {} + ~ZebraTechDOpto_Temp() override = default; }; @@ -321,23 +322,13 @@ class ZebraTechDOpto_DOpct : public Variable { explicit ZebraTechDOpto_DOpct( ZebraTechDOpto* parentSense, const char* uuid = "", const char* varCode = DOPTO_DOPCT_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DOPTO_DOPCT_VAR_NUM, - (uint8_t)DOPTO_DOPCT_RESOLUTION, DOPTO_DOPCT_VAR_NAME, - DOPTO_DOPCT_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ZebraTechDOpto_DOpct object. - * - * @note This must be tied with a parent ZebraTechDOpto before it can be - * used. - */ - ZebraTechDOpto_DOpct() - : Variable((uint8_t)DOPTO_DOPCT_VAR_NUM, - (uint8_t)DOPTO_DOPCT_RESOLUTION, DOPTO_DOPCT_VAR_NAME, - DOPTO_DOPCT_UNIT_NAME, DOPTO_DOPCT_DEFAULT_CODE) {} + : Variable(parentSense, DOPTO_DOPCT_VAR_NUM, DOPTO_DOPCT_RESOLUTION, + DOPTO_DOPCT_VAR_NAME, DOPTO_DOPCT_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the ZebraTechDOpto_DOpct object - no action needed. */ - ~ZebraTechDOpto_DOpct() {} + ~ZebraTechDOpto_DOpct() override = default; }; @@ -365,25 +356,15 @@ class ZebraTechDOpto_DOmgL : public Variable { explicit ZebraTechDOpto_DOmgL( ZebraTechDOpto* parentSense, const char* uuid = "", const char* varCode = DOPTO_DOMGL_DEFAULT_CODE) - : Variable(parentSense, (uint8_t)DOPTO_DOMGL_VAR_NUM, - (uint8_t)DOPTO_DOMGL_RESOLUTION, DOPTO_DOMGL_VAR_NAME, - DOPTO_DOMGL_UNIT_NAME, varCode, uuid) {} - /** - * @brief Construct a new ZebraTechDOpto_DOmgL object. - * - * @note This must be tied with a parent ZebraTechDOpto before it can be - * used. - */ - ZebraTechDOpto_DOmgL() - : Variable((uint8_t)DOPTO_DOMGL_VAR_NUM, - (uint8_t)DOPTO_DOMGL_RESOLUTION, DOPTO_DOMGL_VAR_NAME, - DOPTO_DOMGL_UNIT_NAME, DOPTO_DOMGL_DEFAULT_CODE) {} + : Variable(parentSense, DOPTO_DOMGL_VAR_NUM, DOPTO_DOMGL_RESOLUTION, + DOPTO_DOMGL_VAR_NAME, DOPTO_DOMGL_UNIT_NAME, varCode, uuid) { + } /** * @brief Destroy the ZebraTechDOpto_DOmgL object - no action needed. */ - ~ZebraTechDOpto_DOmgL() {} + ~ZebraTechDOpto_DOmgL() override = default; }; /**@}*/ #endif // SRC_SENSORS_ZEBRATECHDOPTO_H_ -// cSpell:ignore Pololu +// cSpell:words Pololu