Skip to content

Fix multi-pin power, add retries, remove refs to old data portal#494

Open
SRGDamia1 wants to merge 544 commits intomasterfrom
develop
Open

Fix multi-pin power, add retries, remove refs to old data portal#494
SRGDamia1 wants to merge 544 commits intomasterfrom
develop

Conversation

@SRGDamia1
Copy link
Copy Markdown
Contributor

@SRGDamia1 SRGDamia1 commented Feb 17, 2026

Summary by CodeRabbit

  • New Features

    • Added AWS IoT Core and S3 publishers for cloud data publishing.
    • Introduced Monitor My Watershed as primary data publishing platform (replaces EnviroDIY).
    • Enhanced sensor measurement retry logic and timing configuration.
    • Added support for external analog voltage readers across multiple sensors.
    • New processor analog sensing capabilities with unified interface.
  • Bug Fixes

    • Improved sensor measurement validation and error handling.
    • Enhanced power management and wake-up sequences.
    • Better handling of invalid measurements and sensor responses.
  • Documentation

    • Updated README and examples with new publisher references.
    • Added comprehensive developer setup guide.
    • Corrected terminology and fixed documentation typos.
  • Breaking Changes

    • EnviroDIY publisher replaced with Monitor My Watershed publisher.
    • Various sensor constructor signatures updated for flexibility.
    • Removed data_saving example and related build configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 17, 2026

Important

Review skipped

Too many files!

This PR contains 252 files, which is 102 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c31b2045-235a-4cae-bc39-3950106973c3

📥 Commits

Reviewing files that changed from the base of the PR and between f149a37 and 79165f6.

📒 Files selected for processing (252)
  • .clang-format
  • .coderabbit.yaml
  • .gitattributes
  • .gitignore
  • ChangeLog.md
  • README.md
  • build-menu-configurations.ps1
  • continuous_integration/dependencies.json
  • continuous_integration/generate_job_matrix.py
  • continuous_integration/platformio.ini
  • cspell.json
  • docs/DoxygenLayout.xml
  • docs/FAQ/Arduino-Streams.md
  • docs/FAQ/Code-Debugging.md
  • docs/FAQ/Power-Parasites.md
  • docs/FAQ/Processor-Compatibility.md
  • docs/For-Developers/Developer-Setup.md
  • docs/Further-Reading/Modem-Notes.md
  • docs/Further-Reading/SAMD-Clocks.md
  • docs/Further-Reading/Sleep-Configurations.md
  • docs/Getting-Started/Getting-Started.md
  • docs/Getting-Started/Library-Dependencies.md
  • docs/doxyfile
  • docs/mcss-conf.py
  • examples/AWS_IoT_Core/AWS_IoT_Core.ino
  • examples/AWS_IoT_Core/ReadMe.md
  • examples/AWS_IoT_Core/platformio.ini
  • examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino
  • examples/EnviroDIY_Monitoring_Kit/ReadMe.md
  • examples/EnviroDIY_Monitoring_Kit/platformio.ini
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/DRWI_2G.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/platformio.ini
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/DRWI_DigiLTE.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/DRWI_Mayfly1.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/DRWI_NoCellular.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/platformio.ini
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/DRWI_SIM7080LTE.ino
  • examples/OutdatedExamples/DRWI_CitizenScience/DRWI_SIM7080LTE/ReadMe.md
  • examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md
  • examples/OutdatedExamples/simple_logging_LearnEnviroDIY/platformio.ini
  • examples/OutdatedExamples/simple_logging_LearnEnviroDIY/simple_logging_LearnEnviroDIY.ino
  • examples/ReadMe.md
  • examples/baro_rho_correction/ReadMe.md
  • examples/baro_rho_correction/baro_rho_correction.ino
  • examples/baro_rho_correction/platformio.ini
  • examples/data_saving/ReadMe.md
  • examples/data_saving/data_saving.ino
  • examples/data_saving/platformio.ini
  • examples/double_logger/ReadMe.md
  • examples/double_logger/double_logger.ino
  • examples/double_logger/platformio.ini
  • examples/example_dependencies.json
  • examples/examples.dox
  • examples/logging_to_MMW/ReadMe.md
  • examples/logging_to_MMW/logging_to_MMW.ino
  • examples/logging_to_MMW/platformio.ini
  • examples/logging_to_ThingSpeak/ReadMe.md
  • examples/logging_to_ThingSpeak/logging_to_ThingSpeak.ino
  • examples/logging_to_ThingSpeak/platformio.ini
  • examples/menu_a_la_carte/ReadMe.md
  • examples/menu_a_la_carte/menu_a_la_carte.ino
  • examples/menu_a_la_carte/platformio.ini
  • examples/simple_logging/ReadMe.md
  • examples/simple_logging/platformio.ini
  • examples/simple_logging/simple_logging.ino
  • examples/single_sensor/ReadMe.md
  • examples/single_sensor/platformio.ini
  • extras/AWS_IoT_SetCertificates/AWS_IoT_SetCertificates.ino
  • extras/AWS_IoT_SetCertificates/ReadMe.md
  • extras/LTExBee_FirstConnection/LTExBee_FirstConnection.ino
  • extras/sdi12_address_change/platformio.ini
  • extras/sdi12_address_change/sdi12_address_change.ino
  • library.json
  • library.properties
  • setupGitFilters.bat
  • setupGitFilters.ps1
  • src/ClockSupport.cpp
  • src/ClockSupport.h
  • src/KnownProcessors.h
  • src/LogBuffer.cpp
  • src/LogBuffer.h
  • src/LoggerBase.cpp
  • src/LoggerBase.h
  • src/LoggerModem.cpp
  • src/LoggerModem.h
  • src/ModSensorConfig.h
  • src/ModSensorDebugConfig.h
  • src/SensorBase.cpp
  • src/SensorBase.h
  • src/VariableArray.cpp
  • src/VariableArray.h
  • src/VariableBase.cpp
  • src/VariableBase.h
  • src/WatchDogs/WatchDogAVR.cpp
  • src/WatchDogs/WatchDogSAMD.cpp
  • src/WatchDogs/WatchDogSAMD.h
  • src/dataPublisherBase.cpp
  • src/dataPublisherBase.h
  • src/modems/DigiXBee.cpp
  • src/modems/DigiXBee.h
  • src/modems/DigiXBee3GBypass.cpp
  • src/modems/DigiXBee3GBypass.h
  • src/modems/DigiXBeeCellularTransparent.cpp
  • src/modems/DigiXBeeCellularTransparent.h
  • src/modems/DigiXBeeLTEBypass.cpp
  • src/modems/DigiXBeeLTEBypass.h
  • src/modems/DigiXBeeWifi.cpp
  • src/modems/DigiXBeeWifi.h
  • src/modems/Espressif.cpp
  • src/modems/Espressif.h
  • src/modems/EspressifESP32.cpp
  • src/modems/EspressifESP32.h
  • src/modems/EspressifESP8266.cpp
  • src/modems/EspressifESP8266.h
  • src/modems/LoggerModemMacros.h
  • src/modems/QuectelBG96.cpp
  • src/modems/QuectelBG96.h
  • src/modems/SIMComSIM7000.cpp
  • src/modems/SIMComSIM7000.h
  • src/modems/SIMComSIM7080.cpp
  • src/modems/SIMComSIM7080.h
  • src/modems/SIMComSIM800.cpp
  • src/modems/SIMComSIM800.h
  • src/modems/SequansMonarch.cpp
  • src/modems/SequansMonarch.h
  • src/modems/Sodaq2GBeeR6.cpp
  • src/modems/Sodaq2GBeeR6.h
  • src/modems/SodaqUBeeR410M.cpp
  • src/modems/SodaqUBeeR410M.h
  • src/modems/SodaqUBeeU201.cpp
  • src/modems/SodaqUBeeU201.h
  • src/publishers/AWS_IoT_Publisher.cpp
  • src/publishers/AWS_IoT_Publisher.h
  • src/publishers/DreamHostPublisher.cpp
  • src/publishers/DreamHostPublisher.h
  • src/publishers/EnviroDIYPublisher.h
  • src/publishers/MonitorMyWatershedPublisher.cpp
  • src/publishers/MonitorMyWatershedPublisher.h
  • src/publishers/S3PresignedPublisher.cpp
  • src/publishers/S3PresignedPublisher.h
  • src/publishers/ThingSpeakPublisher.cpp
  • src/publishers/ThingSpeakPublisher.h
  • src/publishers/UbidotsPublisher.cpp
  • src/publishers/UbidotsPublisher.h
  • src/sensors/ANBpH.cpp
  • src/sensors/ANBpH.h
  • src/sensors/AOSongAM2315.cpp
  • src/sensors/AOSongAM2315.h
  • src/sensors/AOSongDHT.cpp
  • src/sensors/AOSongDHT.h
  • src/sensors/AlphasenseCO2.cpp
  • src/sensors/AlphasenseCO2.h
  • src/sensors/AnalogElecConductivity.cpp
  • src/sensors/AnalogElecConductivity.h
  • src/sensors/AnalogVoltageReader.h
  • src/sensors/ApogeeSQ212.cpp
  • src/sensors/ApogeeSQ212.h
  • src/sensors/AtlasParent.cpp
  • src/sensors/AtlasParent.h
  • src/sensors/AtlasScientificCO2.cpp
  • src/sensors/AtlasScientificCO2.h
  • src/sensors/AtlasScientificDO.cpp
  • src/sensors/AtlasScientificDO.h
  • src/sensors/AtlasScientificEC.cpp
  • src/sensors/AtlasScientificEC.h
  • src/sensors/AtlasScientificORP.h
  • src/sensors/AtlasScientificRTD.h
  • src/sensors/AtlasScientificpH.h
  • src/sensors/BoschBME280.cpp
  • src/sensors/BoschBME280.h
  • src/sensors/BoschBMP3xx.cpp
  • src/sensors/BoschBMP3xx.h
  • src/sensors/CampbellClariVUE10.h
  • src/sensors/CampbellOBS3.cpp
  • src/sensors/CampbellOBS3.h
  • src/sensors/CampbellRainVUE10.h
  • src/sensors/Decagon5TM.cpp
  • src/sensors/Decagon5TM.h
  • src/sensors/DecagonCTD.h
  • src/sensors/DecagonES2.h
  • src/sensors/EverlightALSPT19.cpp
  • src/sensors/EverlightALSPT19.h
  • src/sensors/FreescaleMPL115A2.cpp
  • src/sensors/FreescaleMPL115A2.h
  • src/sensors/GeoluxHydroCam.cpp
  • src/sensors/GeoluxHydroCam.h
  • src/sensors/GroPointGPLP8.h
  • src/sensors/GroPointParent.cpp
  • src/sensors/GroPointParent.h
  • src/sensors/InSituRDO.h
  • src/sensors/InSituTrollSdi12a.h
  • src/sensors/KellerAcculevel.h
  • src/sensors/KellerNanolevel.h
  • src/sensors/KellerParent.cpp
  • src/sensors/KellerParent.h
  • src/sensors/MaxBotixSonar.cpp
  • src/sensors/MaxBotixSonar.h
  • src/sensors/MaximDS18.cpp
  • src/sensors/MaximDS18.h
  • src/sensors/MaximDS3231.cpp
  • src/sensors/MaximDS3231.h
  • src/sensors/MeaSpecMS5803.cpp
  • src/sensors/MeaSpecMS5803.h
  • src/sensors/MeterHydros21.h
  • src/sensors/MeterTeros11.cpp
  • src/sensors/MeterTeros11.h
  • src/sensors/PaleoTerraRedox.cpp
  • src/sensors/PaleoTerraRedox.h
  • src/sensors/ProcessorAnalog.cpp
  • src/sensors/ProcessorAnalog.h
  • src/sensors/ProcessorStats.cpp
  • src/sensors/ProcessorStats.h
  • src/sensors/RainCounterI2C.cpp
  • src/sensors/RainCounterI2C.h
  • src/sensors/SDI12Sensors.cpp
  • src/sensors/SDI12Sensors.h
  • src/sensors/SensirionSHT4x.cpp
  • src/sensors/SensirionSHT4x.h
  • src/sensors/TEConnectivityMS5837.cpp
  • src/sensors/TEConnectivityMS5837.h
  • src/sensors/TIADS1x15.cpp
  • src/sensors/TIADS1x15.h
  • src/sensors/TIINA219.cpp
  • src/sensors/TIINA219.h
  • src/sensors/TallyCounterI2C.cpp
  • src/sensors/TallyCounterI2C.h
  • src/sensors/TurnerCyclops.cpp
  • src/sensors/TurnerCyclops.h
  • src/sensors/TurnerTurbidityPlus.cpp
  • src/sensors/TurnerTurbidityPlus.h
  • src/sensors/VegaPuls21.h
  • src/sensors/YosemitechParent.cpp
  • src/sensors/YosemitechParent.h
  • src/sensors/YosemitechY4000.h
  • src/sensors/YosemitechY504.h
  • src/sensors/YosemitechY510.h
  • src/sensors/YosemitechY511.h
  • src/sensors/YosemitechY513.h
  • src/sensors/YosemitechY514.h
  • src/sensors/YosemitechY520.h
  • src/sensors/YosemitechY532.h
  • src/sensors/YosemitechY533.h
  • src/sensors/YosemitechY551.h
  • src/sensors/YosemitechY560.h
  • src/sensors/YosemitechY700.h
  • src/sensors/ZebraTechDOpto.h

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

@github-actions
Copy link
Copy Markdown

Please add your changes to the change log!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/sensors/GeoluxHydroCam.cpp (1)

353-358: ⚠️ Potential issue | 🔴 Critical

Operator precedence bug causes incorrect timing calculation.

The ternary operator ?: has lower precedence than +, so these expressions are parsed as:

minTime = (_stabilizationTime_ms + _alwaysAutoFocus) ? HYDROCAM_AUTOFOCUS_TIME_MS : 0L;
maxTime = (HYDROCAM_STABILIZATION_TIME_MAX + _alwaysAutoFocus) ? HYDROCAM_AUTOFOCUS_TIME_MAX : 0L;

Since _stabilizationTime_ms (11) and HYDROCAM_STABILIZATION_TIME_MAX (16000) are non-zero, the condition is always truthy, causing minTime and maxTime to always use the autofocus times regardless of _alwaysAutoFocus.

🐛 Proposed fix: Add parentheses to enforce correct precedence
-    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 = _stabilizationTime_ms +
+        (_alwaysAutoFocus ? HYDROCAM_AUTOFOCUS_TIME_MS : 0L);
+    uint32_t maxTime = HYDROCAM_STABILIZATION_TIME_MAX +
+        (_alwaysAutoFocus ? HYDROCAM_AUTOFOCUS_TIME_MAX : 0L);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GeoluxHydroCam.cpp` around lines 353 - 358, The ternary is
binding incorrectly in the minTime/maxTime expressions; change the expressions
so the conditional is evaluated first and then added, e.g. compute minTime using
_stabilizationTime_ms + (_alwaysAutoFocus ? HYDROCAM_AUTOFOCUS_TIME_MS : 0L) and
compute maxTime using HYDROCAM_STABILIZATION_TIME_MAX + (_alwaysAutoFocus ?
HYDROCAM_AUTOFOCUS_TIME_MAX : 0L) — update the lines that set minTime and
maxTime (referencing variables minTime, maxTime, _stabilizationTime_ms,
_alwaysAutoFocus and constants HYDROCAM_AUTOFOCUS_TIME_MS,
HYDROCAM_STABILIZATION_TIME_MAX, HYDROCAM_AUTOFOCUS_TIME_MAX) to add the
necessary parentheses so the ternary has correct precedence.
build-menu-configurations.ps1 (1)

147-152: ⚠️ Potential issue | 🟠 Major

Fix publisher flag array/loop variable mismatch.
$publisherFlag defines the array, but the loop iterates $publisherFlags, so publisher builds are skipped. Rename the array to match the loop.

🛠️ Suggested fix
-$publisherFlag = @(`
+$publisherFlags = @(`
     'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', `
     'BUILD_PUB_DREAM_HOST_PUBLISHER', `
     'BUILD_PUB_THING_SPEAK_PUBLISHER')
 
 Foreach ($publisherFlag in $publisherFlags)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 152, The array is defined
as $publisherFlag but the loop iterates over $publisherFlags, causing the
Foreach to never run; rename the array variable to $publisherFlags (or change
the Foreach to iterate over $publisherFlag) so the names match and the loop
processes the publisher list; update the declaration that currently uses
$publisherFlag (containing 'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER',
'BUILD_PUB_DREAM_HOST_PUBLISHER', 'BUILD_PUB_THING_SPEAK_PUBLISHER') to the
corrected symbol $publisherFlags to match Foreach ($publisherFlag in
$publisherFlags).
src/SensorBase.cpp (1)

367-384: ⚠️ Potential issue | 🟠 Major

Avoid clearing power/wake status inside clearValues()

Sensor::update() calls clearValues() after a successful wake. Clearing POWER/WAKE/MEASUREMENT bits and timestamps here makes startSingleMeasurement() treat the sensor as not awake, so updates can fail silently. Consider keeping value reset separate from state reset, or only resetting state when the sensor is actually powered down.

🧩 Suggested fix (guard state reset)
 void Sensor::clearValues(void) {
     MS_DBG(F("Clearing value array for"), getSensorNameAndLocation());
     for (uint8_t i = 0; i < _numReturnedValues; i++) {
         sensorValues[i]               = -9999;
         numberGoodMeasurementsMade[i] = 0;
     }
     // Reset measurement attempt counters
     _measurementAttemptsCompleted = 0;
     _retryAttemptsMade            = 0;
-    // reset all timing values
-    _millisPowerOn              = 0;
-    _millisSensorActivated      = 0;
-    _millisMeasurementRequested = 0;
-    _millisMeasurementCompleted = 0;
-    // Unset all status bits except setup (bit 0) and error (bit 7)
-    clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED,
-                    WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED,
-                    MEASUREMENT_SUCCESSFUL);
+    // Only reset timing/status when the sensor is not powered/active.
+    if (!getStatusBit(POWER_SUCCESSFUL) && !getStatusBit(WAKE_SUCCESSFUL)) {
+        _millisPowerOn              = 0;
+        _millisSensorActivated      = 0;
+        _millisMeasurementRequested = 0;
+        _millisMeasurementCompleted = 0;
+        clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED,
+                        WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED,
+                        MEASUREMENT_SUCCESSFUL);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.cpp` around lines 367 - 384, clearValues() currently resets
timestamps and clears status bits (via clearStatusBits(POWER_ATTEMPTED,
POWER_SUCCESSFUL, WAKE_ATTEMPTED, WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED,
MEASUREMENT_SUCCESSFUL)) which makes startSingleMeasurement() think the sensor
is not awake; change clearValues() to only reset measurement arrays
(sensorValues, numberGoodMeasurementsMade) and measurement counters
(_measurementAttemptsCompleted, _retryAttemptsMade) while leaving
power/wake/measurement status bits and timestamps (_millisPowerOn,
_millisSensorActivated, _millisMeasurementRequested,
_millisMeasurementCompleted) untouched, or add a boolean parameter (e.g.,
clearState=false) so callers can explicitly request a full state reset when
powering down; update call sites (including Sensor::update()) to only call the
default clearValues() that preserves state after a wake.
src/sensors/ANBpH.cpp (1)

314-390: ⚠️ Potential issue | 🟠 Major

Fix off‑by‑one retry boundary when storing last‑attempt values

_retryAttemptsMade is incremented in bumpMeasurementAttemptCount() after this block. With the current == _allowedMeasurementRetries - 1 check, a failed first attempt is treated as “last try” when allowed retries is 1, and -1 underflows when allowed retries is 0. Use >= _allowedMeasurementRetries (or equivalent) to detect the final attempt before the increment.

✅ Suggested fix
-    if (success || _retryAttemptsMade == _allowedMeasurementRetries - 1) {
+    if (success || _retryAttemptsMade >= _allowedMeasurementRetries) {
         verifyAndAddMeasurementResult(ANB_PH_PH_VAR_NUM, pH);
         verifyAndAddMeasurementResult(ANB_PH_TEMP_VAR_NUM, temp);
         verifyAndAddMeasurementResult(ANB_PH_SALINITY_VAR_NUM, sal);
         verifyAndAddMeasurementResult(ANB_PH_SPCOND_VAR_NUM, spcond);
         verifyAndAddMeasurementResult(ANB_PH_EC_VAR_NUM, raw_cond);
         verifyAndAddMeasurementResult(ANB_PH_HEALTH_CODE_VAR_NUM,
                                       static_cast<int16_t>(health));
         verifyAndAddMeasurementResult(ANB_PH_DIAGNOSTIC_CODE_VAR_NUM,
                                       static_cast<int16_t>(diagnostic));
         verifyAndAddMeasurementResult(ANB_PH_STATUS_CODE_VAR_NUM,
                                       static_cast<int16_t>(status));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.cpp` around lines 314 - 390, The off‑by‑one check in
ANBpH::addSingleMeasurementResult uses "_retryAttemptsMade ==
_allowedMeasurementRetries - 1" but bumpMeasurementAttemptCount increments
_retryAttemptsMade after this block, which misclassifies the last attempt and
underflows when _allowedMeasurementRetries is 0; change the conditional to use a
>= check (e.g., "_retryAttemptsMade >= _allowedMeasurementRetries") so the
final-attempt branch that calls verifyAndAddMeasurementResult(...) triggers
correctly before bumpMeasurementAttemptCount(...) is called, and ensure the
change references _retryAttemptsMade, _allowedMeasurementRetries,
addSingleMeasurementResult, and bumpMeasurementAttemptCount.
src/sensors/ANBpH.h (1)

478-507: ⚠️ Potential issue | 🟠 Major

Validate loggingIntervalMinutes bounds at runtime.

Docs require 10–240 minutes, but there’s no guard in the API. Out‑of‑range values could silently misconfigure sensor timing. Please clamp or reject invalid values in the constructor or setup().

🔧 Suggested guard (in src/sensors/ANBpH.cpp constructor)
-    : Sensor(...),
-      _loggingIntervalMinutes(loggingIntervalMinutes),
+    : Sensor(...),
+      _loggingIntervalMinutes(loggingIntervalMinutes),
       _RS485EnablePin(enablePin) {
+    if (_loggingIntervalMinutes < 10) _loggingIntervalMinutes = 10;
+    if (_loggingIntervalMinutes > 240) _loggingIntervalMinutes = 240;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 507, The ANBpH constructors accept
loggingIntervalMinutes but do not enforce the documented 10–240 minute bounds;
add a runtime guard in the ANBpH constructors (the overloads declared in
ANBpH(byte, Stream*, int8_t, int16_t, ...) and ANBpH(byte, Stream&, ...)) or
inside ANBpH::setup() to validate loggingIntervalMinutes and either clamp it to
the range [10,240] or fail fast (e.g., return an error / set an internal invalid
flag) depending on your error-handling convention; update the constructor to
check the value, apply the clamp or mark invalid, and ensure any downstream code
reading the member uses that validated value.
🧹 Nitpick comments (18)
src/sensors/CampbellOBS3.cpp (1)

100-110: Address the TODO: Verify voltage range bounds.

The @todo comment indicates uncertainty about the voltage range validation. The current bounds (-0.3V to 3.6V) appear to match the ADS1115/ADS1015 input range when powered at 3.3V (GND - 0.3V to VDD + 0.3V per datasheet), but this should be verified against the OBS3 sensor specifications to ensure no valid readings are being rejected.

Would you like me to open an issue to track this verification task, or do you have documentation confirming these bounds are correct for the OBS3 sensor's output range?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` around lines 100 - 110, The voltage bounds
check using adcVoltage (< 3.6 && > -0.3) is provisional and needs verification
against the OBS3 sensor output spec; update this by confirming the OBS3 output
voltage range and then either (a) replace the hardcoded bounds with verified
constants (e.g., OBS3_MIN_VOLTAGE, OBS3_MAX_VOLTAGE) or (b) make the bounds
configurable via a calibration/config struct, and ensure the code path that
computes calibResult (using _x2_coeff_A, _x1_coeff_B, _x0_coeff_C) and calls
verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage) only accepts
adcVoltage values within the verified range; include a clear comment referencing
the OBS3 datasheet section or issue ID used to verify the bounds.
src/sensors/MeaSpecMS5803.cpp (1)

78-86: Consider adding upper-bound pressure validation.

Temperature is validated against its datasheet range (-40°C to +85°C), but pressure only checks for the disconnected-sensor case (0.0). Since _maxPressure is available, validating that the pressure reading is within a plausible range (e.g., below _maxPressure * 1.1 or similar) could help detect faulty readings.

💡 Optional enhancement for pressure range check
-    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
-        press != 0.0) {
+    // Max pressure in mbar (1 bar = 1000 mbar), with 10% headroom
+    float pressureLimit = _maxPressure * 1000.0 * 1.1;
+    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
+        press > 0.0 && press <= pressureLimit) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MeaSpecMS5803.cpp` around lines 78 - 86, The pressure check
currently only guards against 0.0; update the conditional that validates temp
and press (the if block that calls verifyAndAddMeasurementResult for
MS5803_TEMP_VAR_NUM and MS5803_PRESSURE_VAR_NUM and sets success) to also
enforce an upper bound using the sensor's configured _maxPressure (e.g., require
press > 0.0 && (_maxPressure <= 0 || press <= _maxPressure * 1.1) or similar to
allow a small margin); ensure you handle the case where _maxPressure is
unset/nonpositive so you don’t reject valid readings.
src/sensors/TurnerTurbidityPlus.cpp (1)

171-180: Consider adding debug logging for out-of-range voltage.

When adcVoltage falls outside the valid range (-0.3, 5.3), the function silently fails without any diagnostic output. Adding an else clause with a debug message would help troubleshoot sensor issues in the field.

🔧 Suggested enhancement
     if (adcVoltage < 5.3 && adcVoltage > -0.3) {
         // 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));
         verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM,
                                       adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage out of valid range:"), String(adcVoltage, 3),
+               F("V (expected -0.3 to 5.3V)"));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.cpp` around lines 171 - 180, Add an else
branch to the adcVoltage range check (the if using adcVoltage < 5.3 &&
adcVoltage > -0.3) to emit a debug/log message when the voltage is out of range;
keep the existing successful-path behavior that computes calibResult from
_conc_std, _volt_std, and _volt_blank and calls verifyAndAddMeasurementResult
for TURBIDITY_PLUS_VAR_NUM and TURBIDITY_PLUS_VOLTAGE_VAR_NUM, but in the else
call MS_DBG (or the same logging macro used above) with a clear message
including the actual adcVoltage value and that it was out of range so field
diagnostics are available.
src/sensors/PaleoTerraRedox.cpp (2)

147-152: Consider addressing the TODO or tracking it as an issue.

The @todo comment indicates incomplete failure condition handling. The current checks (NaN and all-zeros) cover basic cases, but additional sensor-specific error conditions may exist per the data sheet.

Would you like me to open an issue to track adding comprehensive failure conditions for the PaleoTerraRedox sensor?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` around lines 147 - 152, The TODO says to add
comprehensive failure conditions for the PaleoTerraRedox sensor; replace the
informal inline checks around res, i2c_status and config with a dedicated
predicate (e.g., isPaleoTerraRedoxFailure(res, i2c_status, config)) that
implements all documented error states from the sensor datasheet (keep current
isnan(res) and the all-zero case but also add any CRC, status-bit, out-of-range,
timeout, or I2C error patterns), use that predicate to set res = -9999, and
either remove the `@todo` or add a short TODO linking to a newly opened issue
tracking missing cases so it’s visible (refer to symbols: res, i2c_status,
config, and the new isPaleoTerraRedoxFailure function).

111-111: Assigning -1 to unsigned byte wraps to 255.

byte is an unsigned type (alias for uint8_t), so -1 wraps to 255. While the value is overwritten on line 116 before use, this initialization is misleading and could cause issues if the code is refactored.

💡 Suggested fix: Initialize to a valid sentinel or the expected error value
-    byte i2c_status = -1;
+    byte i2c_status = 0xFF;  // Initialize to non-zero (error) value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` at line 111, The variable i2c_status is
declared as unsigned byte and should not be initialized to -1 (wraps to 255);
replace the misleading initialization by either changing i2c_status to a signed
type (e.g., int or int8_t) if you need a -1 error code, or keep it as byte and
initialize to a clear sentinel like 0xFF via a named constant (e.g., const byte
I2C_STATUS_INVALID = 0xFF) and use that constant in the declaration and any
checks; update references to i2c_status (including where it’s overwritten on
line 116) to use the chosen sentinel or signed type consistently.
src/sensors/TurnerCyclops.cpp (1)

96-114: Consider logging when ADC voltage is out of range.

When Line 107 fails, the measurement quietly fails; a debug line would speed field diagnosis.

💡 Optional logging for out-of-range voltage
 if (adcVoltage < 3.6 && adcVoltage > -0.3) {
     // Apply the unique calibration curve for the given sensor
     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);
     success = true;
+} else {
+    MS_DBG(F("  ADC voltage out of expected range:"), adcVoltage);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 96 - 114, Add explicit debug
logging for the out-of-range ADC voltage branch so failures are visible: in the
code around ads.readADC_SingleEnded/_adsChannel where adcVoltage is computed,
when the condition (adcVoltage < 3.6 && adcVoltage > -0.3) is false, log a clear
debug message including the adcCounts, adcVoltage and _adsChannel (and
optionally the expected range) before returning/failing to call
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, ...) or
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, ...); place the log
alongside existing MS_DBG usage to match formatting and aid field diagnosis.
.gitattributes (1)

35-38: Consider documenting the git filter setup.

The smudgePasswords and disableDebug filters require local git configuration to function (e.g., git config filter.smudgePasswords.clean ...). Without setup, git silently ignores these directives.

Consider adding a README or contributing guide section explaining how to configure these filters, or providing a setup script in the filters/ directory.

#!/bin/bash
# Description: Check if filter setup documentation exists

# Search for documentation about git filters
rg -i "smudgePasswords|disableDebug|filter.*clean|filter.*smudge" --type md

# Check for setup scripts
fd -e sh -e bat . filters/ 2>/dev/null || echo "filters/ directory not found or empty"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitattributes around lines 35 - 38, Document the custom git filters
referenced in .gitattributes (smudgePasswords, disableDebug) by adding a short
README or CONTRIBUTING section and/or a setup script in a filters/ directory
that shows the exact git config keys to set (e.g., filter.smudgePasswords.*,
filter.disableDebug.*) and the expected clean/smudge/required commands, plus
instructions for installing the script and verifying the configuration;
reference the .gitattributes entries and include a verification step that checks
for the filters (so reviewers/consumers know how to enable smudgePasswords and
disableDebug locally).
src/sensors/TurnerTurbidityPlus.h (1)

312-317: Track the new PGA auto-gain TODO.
If this is intended work, consider filing an issue so it doesn’t get lost; happy to help draft the implementation or tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.h` around lines 312 - 317, Create a tracked
issue describing the TODO for automatic PGA gain selection for the ADS1115
referenced by the _PGA_gain member (type adsGain_t) in TurnerTurbidityPlus.h:
describe the intended behavior (auto-determine gain from board
supply/measurement range), acceptance criteria, proposed approach (voltage
thresholds or auto-ranging algorithm), and list required tests (unit tests for
gain selection and integration test on hardware). Link the issue to the TODO
comment and include pointers to the symbols _PGA_gain and adsGain_t so future
implementation or PRs can reference it.
src/sensors/TIADS1x15.cpp (1)

96-102: Consider adding debug logging for out-of-range ADC readings.

When adcVoltage falls outside the valid range (not between -0.3 and 3.6), the code silently fails without logging. For consistency with other sensor implementations (like MaximDS18.cpp lines 203-204 and FreescaleMPL115A2.cpp line 86), consider adding a debug message:

💡 Suggested improvement
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // Apply the gain calculation, with a default gain of 10 V/V Gain
         calibResult = adcVoltage * _gain;
         MS_DBG(F("  calibResult:"), calibResult);
         verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult);
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage"), adcVoltage, F("V outside valid range"));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 96 - 102, Add a debug log for
out-of-range ADC readings: in the TIADS1x15 measurement handling (the block
using adcVoltage, calibResult, _gain, verifyAndAddMeasurementResult,
TIADS1X15_VAR_NUM and setting success), add an MS_DBG call in the else-path when
adcVoltage is not between -0.3 and 3.6 that prints a clear message with the
offending adcVoltage value and that the sample was ignored; keep the existing
behavior (do not set success or add a measurement) but ensure the debug line
mirrors other sensors’ style for consistency.
.coderabbit.yaml (1)

46-51: Redundant LanguageTool configuration entries.

The disabled_rules list includes EN_UNPAIRED_BRACKETS and disabled_categories includes TYPOS, TYPOGRAPHY, and CASING. According to the schema documentation, these are already disabled by default ("EN_UNPAIRED_BRACKETS, and EN_UNPAIRED_QUOTES are always disabled" and "TYPOS, TYPOGRAPHY, and CASING are always disabled").

While this doesn't cause any issues, these entries are redundant and could be removed for cleaner configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 46 - 51, Remove the redundant LanguageTool
entries by deleting the EN_UNPAIRED_BRACKETS item from the disabled_rules list
and the TYPOS, TYPOGRAPHY, and CASING items from the disabled_categories list;
update the .coderabbit.yaml configuration so disabled_rules and
disabled_categories no longer explicitly include those symbols (they are
disabled by default) and ensure the remaining YAML structure/indentation remains
valid.
src/sensors/ApogeeSQ212.cpp (1)

96-108: Address the TODO: Verify voltage range for SQ-212 sensor.

The @todo comment on line 96 suggests uncertainty about the appropriate voltage range validation for the SQ-212 sensor. The current range (-0.3V to 3.6V) appears to be based on the ADS operating limits at 3.3V supply rather than the sensor's actual output range.

According to the code comment on line 77-78, the sensor returns 0-2.5V. Consider tightening the upper bound if that's the expected range, or document why the ADS limits are used instead.

Would you like me to open an issue to track verification of the SQ-212 voltage range, or help research the sensor datasheet specifications?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 108, The voltage-range check
for the SQ-212 is using ADS supply limits instead of the sensor output spec;
update the if-condition around adcVoltage to validate against the sensor's
expected output (use 0.0 to 2.5V with a small tolerance, e.g., -0.01..2.51)
instead of -0.3..3.6, remove the `@todo` comment, and add a brief debug/log when
adcVoltage is out of that tightened range before calling
verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, ...),
verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, ...), and
bumpMeasurementAttemptCount(success) so out-of-range readings are clearly noted;
keep using SQ212_CALIBRATION_FACTOR for calibResult calculation.
src/sensors/ProcessorAnalog.cpp (2)

47-52: Consider reducing priming reads to one for consistency.

This implementation uses two priming reads (lines 48 and 50) before the actual read, while similar sensors like EverlightALSPT19 use only one priming read. If there's a specific reason for two priming reads on the processor analog input, consider adding a comment to explain it.

♻️ Optional: Reduce to single priming read for consistency
     MS_DBG(F("Getting analog voltage from pin"), _dataPin);
     pinMode(_dataPin, INPUT);
     analogRead(_dataPin);  // priming reading
-    // The return value from analogRead() is IN BITS NOT IN VOLTS!!
-    analogRead(_dataPin);  // another priming reading
-    float rawAnalog = analogRead(_dataPin);
+    // The return value from analogRead() is IN BITS NOT IN VOLTS!!
+    uint32_t rawAnalog = analogRead(_dataPin);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.cpp` around lines 47 - 52, The code in
ProcessorAnalog.cpp performs two priming analogRead() calls before the actual
read (see pinMode, the two analogRead() priming calls, and float rawAnalog),
which is inconsistent with other sensors like EverlightALSPT19; either remove
the extra priming read so there is only a single priming analogRead() before
assigning rawAnalog, or keep both but add a brief comment explaining why two
priming reads are required for this specific sensor/ADC timing behavior; update
the MS_DBG and surrounding lines accordingly so only the intended number of
priming reads remain and the logic is consistent.

51-56: Consider using integer type for raw ADC value.

analogRead() returns an integer type. Storing it in a float (line 51) before multiplication works but loses the semantic clarity. Other similar sensors (e.g., EverlightALSPT19) use uint32_t for ADC values. The calculation itself is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.cpp` around lines 51 - 56, The raw analog sample
should be stored in an integer type rather than a float to preserve semantic
clarity: change the local variable rawAnalog (from analogRead(_dataPin)) to an
unsigned integer (e.g., uint32_t as used in EverlightALSPT19) in
ProcessorAnalog.cpp, then perform the existing voltage calculation for
sensorValue_analog by casting rawAnalog to float only at the point of
multiplication with (_operatingVoltage / static_cast<float>(PROCESSOR_ADC_MAX))
* _voltageMultiplier so the math stays identical but the ADC value retains its
integer semantics.
src/sensors/KnownProcessors.h (1)

259-266: Consider consistency in fallback handling.

The fallback for OPERATING_VOLTAGE is defined here with a pragma warning, while fallbacks for BATTERY_PIN and BATTERY_MULTIPLIER are defined in ProcessorStats.h. This split is functional but could cause confusion during maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KnownProcessors.h` around lines 259 - 266, The fallback for
OPERATING_VOLTAGE is declared in KnownProcessors.h while BATTERY_PIN and
BATTERY_MULTIPLIER fallbacks live in ProcessorStats.h, causing inconsistent
maintenance; move or duplicate the fallback logic so all processor-related
defaults live together: either relocate the OPERATING_VOLTAGE
`#ifndef/`#define/#pragma block into the same header that contains BATTERY_PIN and
BATTERY_MULTIPLIER fallback definitions (or conversely move the battery
fallbacks into KnownProcessors.h), and ensure the unique symbols
OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER are consistently guarded
with `#ifndef/`#define and the same pragma warning style so all defaults are
defined in one place and maintainers can find them easily.
src/VariableArray.cpp (2)

469-549: Complex power pin conflict logic is thorough but dense.

The nested conditionals checking both primary and secondary power pins against all other sensors are comprehensive. However, the deeply nested structure (lines 476-494) reduces readability.

Consider extracting a helper function like bool sharesPowerPin(Sensor* a, Sensor* b) to simplify the logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 469 - 549, The power-pin conflict check
is hard to read due to duplicated nested conditionals; refactor by extracting a
helper like bool sharesPowerPin(Sensor* a, Sensor* b) that returns true if a's
primary or secondary power pin equals b's primary or secondary power pin (and
ignores negative pins), then replace the repeated compound checks in the
for-loops (the loops that set canPowerDown and the MS_DBG loop) with calls to
sharesPowerPin(sensorList[i], sensorList[k]); keep the surrounding logic that
also checks getNumberCompleteMeasurementsAttempts() <
getNumberMeasurementsToAverage() and retain calls to powerDown(), MS_DBG(), and
the same sensorList/getPowerPin/getSecondaryPowerPin/getSensorNameAndLocation
symbols.

299-315: VLA usage is acceptable for this embedded systems library.

While Sensor* sensorList[_sensorCount] at line 300 is technically a Variable Length Array (VLA)—a C99 feature not standard in C++—this codebase deliberately uses VLAs throughout. For an embedded systems library like ModularSensors, VLAs are a reasonable choice for memory efficiency. The project's target compilers (Arduino-compatible environments) consistently support this as a compiler extension.

The suggested refactor to use MAX_NUMBER_SENSORS is not viable; this constant does not exist in the codebase and no predefined sensor count limit is enforced. The dynamic sizing of sensorList based on the actual number of unique sensors is intentional design.

If portability to non-VLA-supporting compilers becomes a requirement, consider documenting this compiler requirement or using dynamic allocation; otherwise, no change is needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 299 - 315, The code uses a VLA (Sensor*
sensorList[_sensorCount]) intentionally for memory efficiency on Arduino-like
compilers; if you want to keep current behavior, leave sensorList as-is in
VariableArray.cpp and add a brief comment documenting the compiler/extension
requirement. If portability to non-VLA compilers is required, replace the VLA
with a heap allocation or a std::vector-based buffer (allocate Sensor*
dynamically using new[] or use std::vector<Sensor*>) and ensure you check
allocation success and delete[]/clear appropriately; touch the sensorList usage
in the loop that references _sensorCount, arrayOfVars, and isLastVarFromSensor
to use the new buffer and validate addedSensors as before.
src/publishers/MonitorMyWatershedPublisher.h (2)

299-307: Make initial transmission count user‑configurable.

There’s a TODO here; happy to help add a setter or constructor param if you want.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 299 - 307, The
TODO requests making the initial transmission count configurable: add a way to
set MonitorMyWatershedPublisher::_initialTransmissionsRemaining from outside the
class by adding either a constructor parameter to MonitorMyWatershedPublisher
(e.g., size_t or uint8_t initialTransmissions = 5) that initializes
_initialTransmissionsRemaining, and/or a public setter method
setInitialTransmissionsRemaining(uint8_t n) with simple validation (non-zero,
sensible max) and an optional getter getInitialTransmissionsRemaining(); update
any places that construct MonitorMyWatershedPublisher to pass the new parameter
or call the setter, and ensure the default remains 5 when nobody supplies a
value.

146-149: Consider caching the endpoint String.

Repeated String concatenation can fragment heap on embedded targets; a cached endpoint updated in setHost/setPath avoids churn.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 146 - 149,
getEndpoint currently rebuilds the endpoint each call by concatenating
monitorMWHost and monitorMWPath, causing repeated String allocations; add a
cached member (e.g., cachedEndpoint) updated whenever setHost or setPath (or any
methods that change monitorMWHost/monitorMWPath) is called, and have getEndpoint
return the cachedEndpoint instead of recomputing it; ensure setHost/setPath
recompute and store the concatenation of monitorMWHost + monitorMWPath into
cachedEndpoint and that getEndpoint simply returns cachedEndpoint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-118: The exclusion logic currently compares the full path
variable root to folder names so excluded folders slip through; change checks to
use folder = os.path.basename(root) (from the os.walk(examples_path) tuple) and
perform exclusions and later filters (data_saving/mayfly/drwi) against that
basename instead of root to avoid accidental workspace-path matches; also stop
leaving subdirs unused (rename to _ or actually use it) to satisfy linters, keep
appending os.path.realpath(root) to non_menu_examples, and ensure the same
basename-based fix is applied to the analogous block around the
data_saving/mayfly/drwi filters (lines ~316-322).

In `@examples/examples.dox`:
- Around line 67-77: Update the cross-reference and anchor to the new page
anchor name: replace the reference token "@ref examples_outdated" with "@ref
page_examples_outdated" in the examples.dox snippet (the line containing
[Examples for Outdated Hardware](`@ref` examples_outdated)), and also change the
anchor definition "{`#examples_outdated`}" in ReadMe.md to
"{`#page_examples_outdated`}" so the `@ref` target matches the renamed page anchor.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: Locate the sentence containing the incorrect plural
"UUID's" in the ReadMe section that mentions copying from the "View Token UUID
List" link and change "UUID's" to the correct plural "UUIDs" so the line reads
"...paste the UUIDs all together..." ensuring the only edit is removal of the
apostrophe; reference the exact phrase "View Token UUID List" and the token
phrase "UUID's" to find the spot.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Line 93: The Markdown link in the ReadMe line containing "Go back to the web
page for your site on [Monitor My Watershed](https://monitormywatershed.org/)"
has an extraneous trailing slash character after the closing parenthesis; remove
that stray '/' so the sentence reads "...on [Monitor My
Watershed](https://monitormywatershed.org)" to avoid rendering the slash as
literal text.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-196: The inner retry loop currently uses a hard-coded limit
(rangeAttempts < 25) which can exceed HRXL_MEASUREMENT_TIME_MS; change the loop
to stop based on a measurement time budget instead of 25: capture a start
timestamp before the loop, and in the while condition check elapsed time against
HRXL_MEASUREMENT_TIME_MS (or compute maxAttempts = max(1,
HRXL_MEASUREMENT_TIME_MS / STREAM_TIMEOUT_MS) and bound by a configured retry
count) so retries bail out when the time budget is exhausted; keep the existing
trigger logic (_triggerPin, digitalWrite), result reads (_stream->parseInt(),
_stream->read()), and the post-loop calls
(verifyAndAddMeasurementResult(HRXL_VAR_NUM, result) and
bumpMeasurementAttemptCount(success)) unchanged but ensure success is false if
the time budget is hit.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 86-89: The log message in the MS_DBG call inside the
TallyCounterI2C code currently claims "returns all values 0 or bad" while the
condition checks events < 0; update the message to accurately reflect that a
negative events value indicates sensor non-response (e.g., "sensor returned
negative value, assuming non-response") or, if a zero return should also be
considered a non-response, change the condition to events <= 0 and then keep a
message that mentions "zero or negative return indicating non-response"; adjust
the MS_DBG(getSensorNameAndLocation(), ...) invocation accordingly and ensure
any downstream logic that relies on events (e.g., handling of events = -9999)
remains consistent with the chosen behavior.

In `@src/sensors/TIINA219.cpp`:
- Around line 74-107: The measurement logic is accepting stale INA219 values
because it never checks the Adafruit INA219 I2C status; update
addSingleMeasurementResult() to call ina219_phy.getCurrent_mA(),
ina219_phy.getBusVoltage_V(), ina219_phy.getPower_mW() and then require
ina219_phy.success() in the success determination (e.g. success =
ina219_phy.success() && !isnan(...)). Also validate the return value of
ina219_phy.begin() in setup() and wake() and treat a failed begin() as a failed
measurement/init (use the same bumpMeasurementAttemptCount(false) or appropriate
error path) so verifyAndAddMeasurementResult is only called when I2C reads/begin
succeeded.

In `@src/sensors/YosemitechParent.cpp`:
- Line 230: The conversion for Y520 multiplies parmValue without verifying the
getValues() success flag, so when getValues() fails parmValue (-9999) gets
incorrectly scaled; update the condition guarding the conversion in
YosemitechParent.cpp to include the success boolean (the same pattern used for
Y4000), e.g. ensure the check uses success and !isnan(parmValue) before
multiplying, referencing the variables/methods _model, Y520, parmValue and the
getValues()/success flag.

---

Outside diff comments:
In `@build-menu-configurations.ps1`:
- Around line 147-152: The array is defined as $publisherFlag but the loop
iterates over $publisherFlags, causing the Foreach to never run; rename the
array variable to $publisherFlags (or change the Foreach to iterate over
$publisherFlag) so the names match and the loop processes the publisher list;
update the declaration that currently uses $publisherFlag (containing
'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', 'BUILD_PUB_DREAM_HOST_PUBLISHER',
'BUILD_PUB_THING_SPEAK_PUBLISHER') to the corrected symbol $publisherFlags to
match Foreach ($publisherFlag in $publisherFlags).

In `@src/SensorBase.cpp`:
- Around line 367-384: clearValues() currently resets timestamps and clears
status bits (via clearStatusBits(POWER_ATTEMPTED, POWER_SUCCESSFUL,
WAKE_ATTEMPTED, WAKE_SUCCESSFUL, MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL))
which makes startSingleMeasurement() think the sensor is not awake; change
clearValues() to only reset measurement arrays (sensorValues,
numberGoodMeasurementsMade) and measurement counters
(_measurementAttemptsCompleted, _retryAttemptsMade) while leaving
power/wake/measurement status bits and timestamps (_millisPowerOn,
_millisSensorActivated, _millisMeasurementRequested,
_millisMeasurementCompleted) untouched, or add a boolean parameter (e.g.,
clearState=false) so callers can explicitly request a full state reset when
powering down; update call sites (including Sensor::update()) to only call the
default clearValues() that preserves state after a wake.

In `@src/sensors/ANBpH.cpp`:
- Around line 314-390: The off‑by‑one check in ANBpH::addSingleMeasurementResult
uses "_retryAttemptsMade == _allowedMeasurementRetries - 1" but
bumpMeasurementAttemptCount increments _retryAttemptsMade after this block,
which misclassifies the last attempt and underflows when
_allowedMeasurementRetries is 0; change the conditional to use a >= check (e.g.,
"_retryAttemptsMade >= _allowedMeasurementRetries") so the final-attempt branch
that calls verifyAndAddMeasurementResult(...) triggers correctly before
bumpMeasurementAttemptCount(...) is called, and ensure the change references
_retryAttemptsMade, _allowedMeasurementRetries, addSingleMeasurementResult, and
bumpMeasurementAttemptCount.

In `@src/sensors/ANBpH.h`:
- Around line 478-507: The ANBpH constructors accept loggingIntervalMinutes but
do not enforce the documented 10–240 minute bounds; add a runtime guard in the
ANBpH constructors (the overloads declared in ANBpH(byte, Stream*, int8_t,
int16_t, ...) and ANBpH(byte, Stream&, ...)) or inside ANBpH::setup() to
validate loggingIntervalMinutes and either clamp it to the range [10,240] or
fail fast (e.g., return an error / set an internal invalid flag) depending on
your error-handling convention; update the constructor to check the value, apply
the clamp or mark invalid, and ensure any downstream code reading the member
uses that validated value.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 353-358: The ternary is binding incorrectly in the minTime/maxTime
expressions; change the expressions so the conditional is evaluated first and
then added, e.g. compute minTime using _stabilizationTime_ms + (_alwaysAutoFocus
? HYDROCAM_AUTOFOCUS_TIME_MS : 0L) and compute maxTime using
HYDROCAM_STABILIZATION_TIME_MAX + (_alwaysAutoFocus ?
HYDROCAM_AUTOFOCUS_TIME_MAX : 0L) — update the lines that set minTime and
maxTime (referencing variables minTime, maxTime, _stabilizationTime_ms,
_alwaysAutoFocus and constants HYDROCAM_AUTOFOCUS_TIME_MS,
HYDROCAM_STABILIZATION_TIME_MAX, HYDROCAM_AUTOFOCUS_TIME_MAX) to add the
necessary parentheses so the ternary has correct precedence.

---

Nitpick comments:
In @.coderabbit.yaml:
- Around line 46-51: Remove the redundant LanguageTool entries by deleting the
EN_UNPAIRED_BRACKETS item from the disabled_rules list and the TYPOS,
TYPOGRAPHY, and CASING items from the disabled_categories list; update the
.coderabbit.yaml configuration so disabled_rules and disabled_categories no
longer explicitly include those symbols (they are disabled by default) and
ensure the remaining YAML structure/indentation remains valid.

In @.gitattributes:
- Around line 35-38: Document the custom git filters referenced in
.gitattributes (smudgePasswords, disableDebug) by adding a short README or
CONTRIBUTING section and/or a setup script in a filters/ directory that shows
the exact git config keys to set (e.g., filter.smudgePasswords.*,
filter.disableDebug.*) and the expected clean/smudge/required commands, plus
instructions for installing the script and verifying the configuration;
reference the .gitattributes entries and include a verification step that checks
for the filters (so reviewers/consumers know how to enable smudgePasswords and
disableDebug locally).

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 299-307: The TODO requests making the initial transmission count
configurable: add a way to set
MonitorMyWatershedPublisher::_initialTransmissionsRemaining from outside the
class by adding either a constructor parameter to MonitorMyWatershedPublisher
(e.g., size_t or uint8_t initialTransmissions = 5) that initializes
_initialTransmissionsRemaining, and/or a public setter method
setInitialTransmissionsRemaining(uint8_t n) with simple validation (non-zero,
sensible max) and an optional getter getInitialTransmissionsRemaining(); update
any places that construct MonitorMyWatershedPublisher to pass the new parameter
or call the setter, and ensure the default remains 5 when nobody supplies a
value.
- Around line 146-149: getEndpoint currently rebuilds the endpoint each call by
concatenating monitorMWHost and monitorMWPath, causing repeated String
allocations; add a cached member (e.g., cachedEndpoint) updated whenever setHost
or setPath (or any methods that change monitorMWHost/monitorMWPath) is called,
and have getEndpoint return the cachedEndpoint instead of recomputing it; ensure
setHost/setPath recompute and store the concatenation of monitorMWHost +
monitorMWPath into cachedEndpoint and that getEndpoint simply returns
cachedEndpoint.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-108: The voltage-range check for the SQ-212 is using ADS supply
limits instead of the sensor output spec; update the if-condition around
adcVoltage to validate against the sensor's expected output (use 0.0 to 2.5V
with a small tolerance, e.g., -0.01..2.51) instead of -0.3..3.6, remove the
`@todo` comment, and add a brief debug/log when adcVoltage is out of that
tightened range before calling verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM,
...), verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, ...), and
bumpMeasurementAttemptCount(success) so out-of-range readings are clearly noted;
keep using SQ212_CALIBRATION_FACTOR for calibResult calculation.

In `@src/sensors/CampbellOBS3.cpp`:
- Around line 100-110: The voltage bounds check using adcVoltage (< 3.6 && >
-0.3) is provisional and needs verification against the OBS3 sensor output spec;
update this by confirming the OBS3 output voltage range and then either (a)
replace the hardcoded bounds with verified constants (e.g., OBS3_MIN_VOLTAGE,
OBS3_MAX_VOLTAGE) or (b) make the bounds configurable via a calibration/config
struct, and ensure the code path that computes calibResult (using _x2_coeff_A,
_x1_coeff_B, _x0_coeff_C) and calls
verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage) only accepts
adcVoltage values within the verified range; include a clear comment referencing
the OBS3 datasheet section or issue ID used to verify the bounds.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: The fallback for OPERATING_VOLTAGE is declared in
KnownProcessors.h while BATTERY_PIN and BATTERY_MULTIPLIER fallbacks live in
ProcessorStats.h, causing inconsistent maintenance; move or duplicate the
fallback logic so all processor-related defaults live together: either relocate
the OPERATING_VOLTAGE `#ifndef/`#define/#pragma block into the same header that
contains BATTERY_PIN and BATTERY_MULTIPLIER fallback definitions (or conversely
move the battery fallbacks into KnownProcessors.h), and ensure the unique
symbols OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER are consistently
guarded with `#ifndef/`#define and the same pragma warning style so all defaults
are defined in one place and maintainers can find them easily.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 78-86: The pressure check currently only guards against 0.0;
update the conditional that validates temp and press (the if block that calls
verifyAndAddMeasurementResult for MS5803_TEMP_VAR_NUM and
MS5803_PRESSURE_VAR_NUM and sets success) to also enforce an upper bound using
the sensor's configured _maxPressure (e.g., require press > 0.0 && (_maxPressure
<= 0 || press <= _maxPressure * 1.1) or similar to allow a small margin); ensure
you handle the case where _maxPressure is unset/nonpositive so you don’t reject
valid readings.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 147-152: The TODO says to add comprehensive failure conditions for
the PaleoTerraRedox sensor; replace the informal inline checks around res,
i2c_status and config with a dedicated predicate (e.g.,
isPaleoTerraRedoxFailure(res, i2c_status, config)) that implements all
documented error states from the sensor datasheet (keep current isnan(res) and
the all-zero case but also add any CRC, status-bit, out-of-range, timeout, or
I2C error patterns), use that predicate to set res = -9999, and either remove
the `@todo` or add a short TODO linking to a newly opened issue tracking missing
cases so it’s visible (refer to symbols: res, i2c_status, config, and the new
isPaleoTerraRedoxFailure function).
- Line 111: The variable i2c_status is declared as unsigned byte and should not
be initialized to -1 (wraps to 255); replace the misleading initialization by
either changing i2c_status to a signed type (e.g., int or int8_t) if you need a
-1 error code, or keep it as byte and initialize to a clear sentinel like 0xFF
via a named constant (e.g., const byte I2C_STATUS_INVALID = 0xFF) and use that
constant in the declaration and any checks; update references to i2c_status
(including where it’s overwritten on line 116) to use the chosen sentinel or
signed type consistently.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 47-52: The code in ProcessorAnalog.cpp performs two priming
analogRead() calls before the actual read (see pinMode, the two analogRead()
priming calls, and float rawAnalog), which is inconsistent with other sensors
like EverlightALSPT19; either remove the extra priming read so there is only a
single priming analogRead() before assigning rawAnalog, or keep both but add a
brief comment explaining why two priming reads are required for this specific
sensor/ADC timing behavior; update the MS_DBG and surrounding lines accordingly
so only the intended number of priming reads remain and the logic is consistent.
- Around line 51-56: The raw analog sample should be stored in an integer type
rather than a float to preserve semantic clarity: change the local variable
rawAnalog (from analogRead(_dataPin)) to an unsigned integer (e.g., uint32_t as
used in EverlightALSPT19) in ProcessorAnalog.cpp, then perform the existing
voltage calculation for sensorValue_analog by casting rawAnalog to float only at
the point of multiplication with (_operatingVoltage /
static_cast<float>(PROCESSOR_ADC_MAX)) * _voltageMultiplier so the math stays
identical but the ADC value retains its integer semantics.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 96-102: Add a debug log for out-of-range ADC readings: in the
TIADS1x15 measurement handling (the block using adcVoltage, calibResult, _gain,
verifyAndAddMeasurementResult, TIADS1X15_VAR_NUM and setting success), add an
MS_DBG call in the else-path when adcVoltage is not between -0.3 and 3.6 that
prints a clear message with the offending adcVoltage value and that the sample
was ignored; keep the existing behavior (do not set success or add a
measurement) but ensure the debug line mirrors other sensors’ style for
consistency.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 96-114: Add explicit debug logging for the out-of-range ADC
voltage branch so failures are visible: in the code around
ads.readADC_SingleEnded/_adsChannel where adcVoltage is computed, when the
condition (adcVoltage < 3.6 && adcVoltage > -0.3) is false, log a clear debug
message including the adcCounts, adcVoltage and _adsChannel (and optionally the
expected range) before returning/failing to call
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, ...) or
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, ...); place the log
alongside existing MS_DBG usage to match formatting and aid field diagnosis.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 171-180: Add an else branch to the adcVoltage range check (the if
using adcVoltage < 5.3 && adcVoltage > -0.3) to emit a debug/log message when
the voltage is out of range; keep the existing successful-path behavior that
computes calibResult from _conc_std, _volt_std, and _volt_blank and calls
verifyAndAddMeasurementResult for TURBIDITY_PLUS_VAR_NUM and
TURBIDITY_PLUS_VOLTAGE_VAR_NUM, but in the else call MS_DBG (or the same logging
macro used above) with a clear message including the actual adcVoltage value and
that it was out of range so field diagnostics are available.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 312-317: Create a tracked issue describing the TODO for automatic
PGA gain selection for the ADS1115 referenced by the _PGA_gain member (type
adsGain_t) in TurnerTurbidityPlus.h: describe the intended behavior
(auto-determine gain from board supply/measurement range), acceptance criteria,
proposed approach (voltage thresholds or auto-ranging algorithm), and list
required tests (unit tests for gain selection and integration test on hardware).
Link the issue to the TODO comment and include pointers to the symbols _PGA_gain
and adsGain_t so future implementation or PRs can reference it.

In `@src/VariableArray.cpp`:
- Around line 469-549: The power-pin conflict check is hard to read due to
duplicated nested conditionals; refactor by extracting a helper like bool
sharesPowerPin(Sensor* a, Sensor* b) that returns true if a's primary or
secondary power pin equals b's primary or secondary power pin (and ignores
negative pins), then replace the repeated compound checks in the for-loops (the
loops that set canPowerDown and the MS_DBG loop) with calls to
sharesPowerPin(sensorList[i], sensorList[k]); keep the surrounding logic that
also checks getNumberCompleteMeasurementsAttempts() <
getNumberMeasurementsToAverage() and retain calls to powerDown(), MS_DBG(), and
the same sensorList/getPowerPin/getSecondaryPowerPin/getSensorNameAndLocation
symbols.
- Around line 299-315: The code uses a VLA (Sensor* sensorList[_sensorCount])
intentionally for memory efficiency on Arduino-like compilers; if you want to
keep current behavior, leave sensorList as-is in VariableArray.cpp and add a
brief comment documenting the compiler/extension requirement. If portability to
non-VLA compilers is required, replace the VLA with a heap allocation or a
std::vector-based buffer (allocate Sensor* dynamically using new[] or use
std::vector<Sensor*>) and ensure you check allocation success and delete[]/clear
appropriately; touch the sensorList usage in the loop that references
_sensorCount, arrayOfVars, and isLastVarFromSensor to use the new buffer and
validate addedSensors as before.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
src/sensors/GroPointParent.h (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Incorrect file name in header comment.

The Doxygen @file comment references GroPointParent.cpp, but this is the header file GroPointParent.h.

📝 Proposed fix
-* `@file` GroPointParent.cpp
+* `@file` GroPointParent.h
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` at line 2, The Doxygen `@file` tag in the
header's top comment incorrectly names GroPointParent.cpp; update the `@file` tag
in GroPointParent.h to reference GroPointParent.h (edit the comment block at the
top of GroPointParent.h where the `@file` tag appears, e.g., in the header comment
for the GroPointParent class) so the file name matches the header file; also
scan the same comment block for any other copy-paste mismatches and correct
them.
build-menu-configurations.ps1 (2)

147-153: ⚠️ Potential issue | 🟠 Major

Fix the publisher flags array variable mismatch.

$publisherFlag is defined, but the loop iterates $publisherFlags, so the publisher build checks never run.

🛠️ Proposed fix
-$publisherFlag = @(
+$publisherFlags = @(
     'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', `
     'BUILD_PUB_DREAM_HOST_PUBLISHER', `
     'BUILD_PUB_THING_SPEAK_PUBLISHER')
 
-Foreach ($publisherFlag in $publisherFlags)
+Foreach ($publisherFlag in $publisherFlags)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 153, The code defines the
array variable as $publisherFlag but the foreach iterates $publisherFlags, so
the loop never executes; fix by making the names consistent—either rename the
array to $publisherFlags where it's declared or change the foreach to iterate
$publisherFlag; update the declaration and any other references (the array and
the Foreach (...) block) so both use the same identifier (e.g., $publisherFlags)
to restore the publisher build checks.

147-153: ⚠️ Potential issue | 🟡 Minor

Address the BOM warning for non-ASCII content.

Static analysis reports missing BOM for a non-ASCII encoded file. Save this file as UTF‑8 with BOM or remove non‑ASCII characters to avoid encoding issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 153, The file encoding
warning means build-menu-configurations.ps1 contains non-ASCII characters and
should be saved with a UTF-8 BOM or stripped of non-ASCII characters; open the
file (inspect the $publisherFlag / $publisherFlags declarations and surrounding
strings/ comments and the Foreach block) and either (a) re-save the file in your
editor/CI as "UTF-8 with BOM" or (b) remove/replace any non-ASCII characters
(smart quotes, em-dashes, non-breaking spaces) in the
$publisherFlag/$publisherFlags lines and nearby text so the file is pure
ASCII/UTF-8 without needing a BOM. Ensure you commit the re-encoded file so
static analysis no longer reports the missing BOM.
library.json (1)

354-362: ⚠️ Potential issue | 🟠 Major

Address the findBaud() removal when upgrading to 0.4.2.

The version jump from 0.2.5 to 0.4.2 introduces one breaking change: the findBaud() function was removed in v0.2.6 (noted as "non-functional" in the changelog). If any code depends on this function, it will fail to compile after upgrading. Additionally, v0.4.1 includes a fix to the startReadOnly() return value that may impact code relying on the previous behavior. Most other changes between these versions are additive (new firmware commands in v0.4.0, fixes to isMeasurementComplete() in v0.4.2).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library.json` around lines 354 - 362, Upgrade to ANBSensorsModbus v0.4.2
removed the findBaud() API and changed startReadOnly() return behavior (and
fixed isMeasurementComplete()), so search the codebase for any calls to
findBaud(), startReadOnly(), or assumptions about startReadOnly()’s return value
and replace/remove them: remove uses of findBaud() and replace with explicit
baud configuration or the library’s supported initialization flow (e.g.,
setSerialBaud or constructor-based baud setup), adapt code that depends on
startReadOnly()’s old return semantics to handle the new return value correctly,
and verify uses of isMeasurementComplete() follow the updated semantics; update
unit/integration tests and any documentation/comments referencing findBaud()
accordingly.
README.md (1)

116-119: ⚠️ Potential issue | 🟡 Minor

Keep the AWS S3 notes within the bullet list.
The last sentence renders outside the S3 sub-list due to missing indentation.

🔧 Proposed fix
-  - The S3 publisher does **NOT** publish any sensor data by default.
-It is intended for publishing images.
+  - The S3 publisher does **NOT** publish any sensor data by default.
+  - It is intended for publishing images.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 116 - 119, The final sentence about intended use for
publishing images is not indented as part of the AWS S3 bullet, so move or
re-indent "It is intended for publishing images." so it remains inside the S3
sub-list (i.e., make it a continuation of the "- The S3 publisher..." bullet or
add it as another "- " sub-bullet under the "[AWS S3]" entry); update the
README.md content around the S3 publisher block so all S3 notes stay within the
same bullet list.
examples/logging_to_MMW/ReadMe.md (1)

24-24: ⚠️ Potential issue | 🟡 Minor

Fix the “universally universal” typo.
Use “universally unique identifiers” in both the TOC and the section header.

✏️ Proposed fix
-    - [Set the universally universal identifiers (UUID) for each variable](`#set-the-universally-universal-identifiers-uuid-for-each-variable`)
+    - [Set the universally unique identifiers (UUID) for each variable](`#set-the-universally-unique-identifiers-uuid-for-each-variable`)
@@
-### Set the universally universal identifiers (UUID) for each variable<!--! {`#example_mmw_uuids`} -->
+### Set the universally unique identifiers (UUID) for each variable<!--! {`#example_mmw_uuids`} -->

Also applies to: 59-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/logging_to_MMW/ReadMe.md` at line 24, Replace the incorrect phrase
"universally universal identifiers (UUID)" with the correct "universally unique
identifiers (UUID)" in both the TOC entry and the corresponding section header;
specifically update the TOC bullet text "[Set the universally universal
identifiers (UUID) for each variable]" and the section heading that uses the
same phrasing so both instances match the correct wording.
src/publishers/MonitorMyWatershedPublisher.cpp (2)

305-439: ⚠️ Potential issue | 🟠 Major

Guard against missing registration token before appending headers.

_registrationToken can be nullptr if a token wasn’t provided via constructor or setToken(). txBufferAppend(_registrationToken) is likely unsafe in that case.

🛡️ Suggested guard
     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;
     }
+    if (_registrationToken == nullptr || strlen(_registrationToken) == 0) {
+        PRINTOUT(F("A registration token must be set before publishing data "
+                   "to Monitor My Watershed!."));
+        return 0;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 305 - 439, In
MonitorMyWatershedPublisher::flushDataBuffer guard against a null/empty
_registrationToken before calling txBufferAppend(tokenHeader) and
txBufferAppend(_registrationToken): check if _registrationToken == nullptr ||
strlen(_registrationToken) == 0, and if so log a clear error (e.g., PRINTOUT)
and return a non-publishing code (e.g., 0) or otherwise avoid appending the
token header; if you choose to proceed without returning, append an empty string
instead of dereferencing the null pointer. Ensure the check is placed before the
txBufferAppend(tokenHeader) / txBufferAppend(_registrationToken) calls so
txBufferAppend is never called with a null pointer.

208-249: ⚠️ Potential issue | 🟠 Major

Fix buffer-fullness threshold ordering.

The current if (percent >= 50) ... else if (percent >= 75) ... else if (percent >= 90) short-circuits the higher thresholds. This prevents the intended aggressive send cadence at 75% and 90% fullness.

🛠️ Suggested fix
-    if (percent >= 50) {
-        interval /= 2;
-    } else if (percent >= 75) {
-        interval /= 4;
-    } else if (percent >= 90) {
-        interval = 1;
-    }
+    if (percent >= 90) {
+        interval = 1;
+    } else if (percent >= 75) {
+        interval /= 4;
+    } else if (percent >= 50) {
+        interval /= 2;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 208 - 249, In
MonitorMyWatershedPublisher::connectionNeeded the percent fullness checks use if
(percent >= 50) else if (percent >= 75) else if (percent >= 90) which
short-circuits higher thresholds; update the ordering (or use descending
comparisons) on the _logBuffer.getPercentFull() checks so the 90% and 75%
branches run before the 50% branch (e.g., check percent >= 90, then >= 75, then
>= 50) and preserve the same interval adjustments to ensure more aggressive send
cadence at higher buffer fullness.
src/sensors/ANBpH.cpp (1)

343-385: ⚠️ Potential issue | 🟠 Major

Avoid using uninitialized values when getValues() fails.
With the new “last retry” path (Line 374), a failed read can still flow into debug prints and verifyAndAddMeasurementResult, but pH/temp/sal/spcond are uninitialized. Initialize them and only apply range/health checks when the read succeeds.

🛠️ Safer handling
-    bool              success = false;
-    float             pH, temp, sal, spcond, raw_cond = -9999;
+    bool  success  = false;
+    float pH       = -9999;
+    float temp     = -9999;
+    float sal      = -9999;
+    float spcond   = -9999;
+    float raw_cond = -9999;
@@
-    success = _anb_sensor.getValues(pH, temp, sal, spcond, raw_cond, health,
-                                    diagnostic);
+    bool gotValues = _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<int16_t>(health), '-',
-           _anb_sensor.getHealthString(health));
-    MS_DBG(F("  Diagnostic Code:"), static_cast<int16_t>(diagnostic), '-',
-           _anb_sensor.getDiagnosticString(diagnostic));
-    MS_DBG(F("  Status Code:"), static_cast<int16_t>(status), '-',
-           _anb_sensor.getStatusString(status));
-
-    if (health == ANBHealthCode::NOT_IMMERSED) {
-        PRINTOUT(F("  WARNING: ANB pH sensor is not immersed!"));
-    }
-
-    success &= ((0.0 < pH && pH < 14.00) ||
-                health == ANBHealthCode::NOT_IMMERSED);
+    if (gotValues) {
+        // 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<int16_t>(health), '-',
+               _anb_sensor.getHealthString(health));
+        MS_DBG(F("  Diagnostic Code:"), static_cast<int16_t>(diagnostic), '-',
+               _anb_sensor.getDiagnosticString(diagnostic));
+        MS_DBG(F("  Status Code:"), static_cast<int16_t>(status), '-',
+               _anb_sensor.getStatusString(status));
+
+        if (health == ANBHealthCode::NOT_IMMERSED) {
+            PRINTOUT(F("  WARNING: ANB pH sensor is not immersed!"));
+        }
+
+        success = ((0.0 < pH && pH < 14.00) ||
+                   health == ANBHealthCode::NOT_IMMERSED);
+    }
@@
-    if (success || _retryAttemptsMade == _allowedMeasurementRetries - 1) {
+    if (gotValues || _retryAttemptsMade == _allowedMeasurementRetries - 1) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.cpp` around lines 343 - 385, The getValues() call may fail
and leave pH/temp/sal/spcond/raw_cond uninitialized; ensure you initialize those
measurement variables (pH, temp, sal, spcond, raw_cond, health, diagnostic,
status) to safe defaults before calling _anb_sensor.getValues(), only perform
range/health checks (the success &= ((0.0 < pH && pH < 14.00) || ...)) and log
prints when success is true (or when you explicitly want to log an error state),
and only call verifyAndAddMeasurementResult for pH/temp/etc. if success is true
or you have a defined fallback value; update the logic around getValues(),
success, _retryAttemptsMade, _allowedMeasurementRetries, and
verifyAndAddMeasurementResult to reflect this safe-path.
src/sensors/ANBpH.h (1)

478-501: ⚠️ Potential issue | 🟡 Minor

Clarify valid logging‑interval values for always‑powered mode.
Line 478 says 10–240 minutes, but setup allows 0 for continuous power when the sensor is always powered. Either document that 0 is allowed only in always‑powered mode or enforce the range in code.

📘 Suggested doc tweak
-     * being controlled by the mcu.  Must be between 10 and 240 minutes.
+     * being controlled by the mcu.  Must be between 10 and 240 minutes when
+     * power is cycled; use 0 only for always‑powered mode.

Also applies to: 612-617

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 501, The comment says
loggingIntervalMinutes must be 10–240 but the code permits 0 for always‑powered
sensors; update both the docs and validation: in the ANBpH
constructor/initializer that takes loggingIntervalMinutes, powerPin, powerPin2,
and enablePin, explicitly allow loggingIntervalMinutes == 0 only when the sensor
is always powered (i.e., power is not MCU‑controlled: powerPin == -1 &&
powerPin2 == -1 && enablePin == -1), otherwise validate and reject (throw or
return an error) values outside 10–240; also update the docblock for
loggingIntervalMinutes to state that 0 is valid only in always‑powered mode.
src/SensorBase.h (1)

656-795: ⚠️ Potential issue | 🟡 Minor

Add default member initializer to _powerPin2 for defensive programming.

Line 656 declares _powerPin2 without initialization, while the constructor initializes it to -1. Although the current constructor always sets this value, adding a default member initializer ensures the pin is initialized even if new constructors are added in the future:

Proposed safe default
-    int8_t _powerPin2;
+    int8_t _powerPin2 = -1;

This follows the same safety pattern used elsewhere in the codebase and prevents uninitialized state in powerUp() and powerDown() calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.h` around lines 656 - 795, Declare a default member
initializer for _powerPin2 so it cannot be left uninitialized (e.g., initialize
the int8_t _powerPin2 member to -1 inline); update the class member declaration
for _powerPin2 (not the constructor) to include the default, keeping existing
constructor logic unchanged; this ensures powerUp() and powerDown() see a safe
default even if new constructors are later added.
🧹 Nitpick comments (11)
src/sensors/RainCounterI2C.h (1)

239-239: Good fix: Changed bitwise OR to logical OR in preprocessor conditional.

The change from | to || is correct. Using bitwise OR (|) in preprocessor conditionals can lead to unexpected behavior; logical OR (||) is the intended operator here.

Minor style nit: Consider using defined(DOXYGEN) with parentheses for consistency with lines 203 and 312 in this file.

-#if !defined(MS_RAIN_SOFTWAREWIRE) || defined DOXYGEN
+#if !defined(MS_RAIN_SOFTWAREWIRE) || defined(DOXYGEN)

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/RainCounterI2C.h` at line 239, The preprocessor conditional using
MS_RAIN_SOFTWAREWIRE and DOXYGEN should use the logical OR (already fixed) and
follow the project's style by wrapping the defined macro in parentheses; update
the condition from using defined DOXYGEN to defined(DOXYGEN) in the same
directive that references MS_RAIN_SOFTWAREWIRE and DOXYGEN so it matches the
existing style used elsewhere in this header.
src/sensors/MeaSpecMS5803.cpp (1)

78-85: Consider validating pressure bounds with _maxPressure.

Right now any non‑zero pressure is accepted; adding an upper‑bound check would prevent out‑of‑range values from being recorded.

♻️ Suggested validation tweak
-    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
-        press != 0.0) {
+    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
+        press > 0.0 && press <= static_cast<float>(_maxPressure)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MeaSpecMS5803.cpp` around lines 78 - 85, The code currently
accepts any non-zero pressure when deciding to call
verifyAndAddMeasurementResult for MS5803_PRESSURE_VAR_NUM; add an upper-bound
check using the sensor's configured _maxPressure (e.g., require press <=
_maxPressure) alongside the existing checks (temp range and press != 0.0) before
setting success and adding results. Locate the if block that checks
isnan(temp)/isnan(press), temp range, and press != 0.0 and extend it to validate
press against _maxPressure so out-of-range high pressures are rejected before
calling verifyAndAddMeasurementResult and setting success.
src/sensors/AlphasenseCO2.cpp (1)

92-117: Good defensive handling with switch-based mux selection.

The switch statement properly handles all differential mux configurations with a default case for invalid values.

Minor inconsistency in debug output: Other ADC-based sensors (e.g., TIADS1x15.cpp, ApogeeSQ212.cpp) include both adcCounts and adcVoltage in debug output. Consider including adcCounts here for consistency:

♻️ Optional: Include adcCounts in debug output
     // Convert ADC counts value to voltage (V)
     adcVoltage = ads.computeVolts(adcCounts);
-    MS_DBG(F("  ads.readADC_Differential("), _adsDiffMux, F("):"),
-           String(adcVoltage, 3));
+    MS_DBG(F("  ads.readADC_Differential("), _adsDiffMux, F("):"), adcCounts,
+           '=', adcVoltage);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 92 - 117, The debug output after
reading the ADC differential should include both adcCounts and adcVoltage for
consistency with other sensors; update the MS_DBG call in the block following
the switch on _adsDiffMux (after calls to
ads.readADC_Differential_0_1/_0_3/_1_3/_2_3 and adcVoltage =
ads.computeVolts(adcCounts)) to log adcCounts as well as adcVoltage, keeping the
existing message and precision, and ensure this path still uses
bumpMeasurementAttemptCount(false) on the default invalid-mux case.
src/sensors/ApogeeSQ212.cpp (1)

96-108: Consider adding debug logging for out-of-range voltage readings.

When the voltage falls outside the valid range (-0.3V to 3.6V), the function silently returns failure without logging. Adding a debug message would help with troubleshooting sensor issues.

💡 Suggested enhancement
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage out of range:"), adcVoltage, F("V"));
     }
 
     // Return success value when finished
     return bumpMeasurementAttemptCount(success);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 108, The function that checks
adcVoltage in ApogeeSQ212.cpp currently drops out silently when voltage is
outside the valid range; add a debug log before returning failure to indicate
the out-of-range voltage and the expected range. Specifically, inside the
function that uses the adcVoltage check (the block that currently sets
calibResult and calls verifyAndAddMeasurementResult for SQ212_PAR_VAR_NUM and
SQ212_VOLTAGE_VAR_NUM), add an MS_DBG (or existing logger) call in the else path
that logs adcVoltage and the allowed range (-0.3 to 3.6) and optionally the
sensor id/constants (SQ212_CALIBRATION_FACTOR or SQ212_VOLTAGE_VAR_NUM) so
failures are visible, then continue to return
bumpMeasurementAttemptCount(success).
src/sensors/PaleoTerraRedox.cpp (2)

147-152: Address the TODO for failure conditions.

The current failure conditions check for NaN and a disconnected sensor (all zeros). Consider adding checks for:

  • ADC overflow/underflow conditions
  • Configuration register indicating conversion not ready (RDY bit)
  • Out-of-range voltage values

Would you like me to help identify additional failure conditions based on the MCP3424 ADC datasheet (commonly used in these sensors), or open an issue to track this?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` around lines 147 - 152, The PaleoTerraRedox
failure checks are incomplete: extend the failure logic in the PaleoTerraRedox
read/convert path (where res, i2c_status, and config are evaluated) to also
detect ADC overflow/underflow, conversion-not-ready (RDY) in the MCP3424-style
config register, and out-of-range voltages; specifically, inspect the raw ADC
result/flag bits for overflow/underflow and the config RDY bit and mark res =
-9999 on those conditions, and add bounds checks on the computed voltage (e.g.,
below min or above max physical sensor voltage) to set the same failure code so
all failure modes (isnan(res), all-zeros i2c/config, ADC overflow/underflow, RDY
not set, out-of-range voltage) produce a consistent -9999 result.

120-125: PTR_CONVERSION_WAIT_TIME_MS is properly defined; consider using requestFrom return value directly.

The constant is correctly defined in the header with value 300ms, appropriate for the 18-bit conversion time. For the I2C read pattern, you could simplify by using the return value from requestFrom directly: if (!_i2c->requestFrom(int(_i2cAddressHex), 4)) instead of checking available() afterward—this is more idiomatic in Arduino code and avoids the extra function call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` around lines 120 - 125, The code waits
PTR_CONVERSION_WAIT_TIME_MS then calls _i2c->requestFrom(int(_i2cAddressHex), 4)
and checks _i2c->available(); replace that pattern by using the return value of
requestFrom directly: call requestFrom(int(_i2cAddressHex), 4) and if it returns
fewer than 4 bytes (or zero) immediately return
bumpMeasurementAttemptCount(false); update the conditional that currently uses
_i2c->available() to instead test the requestFrom result so you avoid the extra
available() call and make the I2C read idiomatic.
src/sensors/TurnerTurbidityPlus.h (1)

312-317: Consider tracking the new @todo outside the header.

To avoid stale TODOs, please consider creating an issue/backlog item for the auto-gain idea.

Would you like me to draft an issue summary for this?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.h` around lines 312 - 317, The header field
adsGain_t _PGA_gain carries a `@todo` about auto-determining gain which should not
live as a stale comment in the header; open a tracker/backlog issue describing
the auto-gain feature (including goals, acceptance criteria, and any constraints
like board voltage sources and measurement flow) and replace the in-code `@todo`
in TurnerTurbidityPlus.h with a short reference comment that points to the
created issue ID or URL (or remove the todo entirely), so future readers are
directed to the proper issue rather than an inline TODO.
.coderabbit.yaml (1)

29-33: Consider extending path_instructions to include .h and .cpp files.

The path instruction currently only covers **/*.hpp files. This codebase predominantly uses .h for headers and .cpp for implementations (e.g., src/sensors/*.h, src/modems/*.cpp). Consider adding similar instructions for these patterns to ensure consistent review coverage.

  path_instructions:
    - path: '**/*.hpp'
      instructions: >-
        Review the C++ code, point out issues relative to principles of clean
        code, expressiveness, and performance.
    - path: '**/*.h'
      instructions: >-
        Review the C++ code, point out issues relative to principles of clean
        code, expressiveness, and performance.
    - path: '**/*.cpp'
      instructions: >-
        Review the C++ code, point out issues relative to principles of clean
        code, expressiveness, and performance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 29 - 33, Update the path_instructions block to
also target .h and .cpp files so reviews cover headers and implementations (in
addition to .hpp); specifically add entries for patterns '**/*.h' and '**/*.cpp'
with the same review instructions text as the existing '**/*.hpp' entry,
ensuring the linter/reviewer will process files like src/sensors/*.h and
src/modems/*.cpp.
src/modems/LoggerModemMacros.h (1)

543-551: Potential issue with deleteClient casting when types differ.

The deleteClient function casts the incoming Client* pointer to TinyGsm##TinyGSMType::GsmClient##TinyGSMType* using static_cast. If a client created by one modem type is accidentally passed to a delete function of a different modem type, this would result in undefined behavior.

Consider adding a runtime type check or documenting that clients must only be deleted by the same modem instance that created them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/LoggerModemMacros.h` around lines 543 - 551, The deleteClient
implementation (specificModem::deleteClient) unsafely static_casts a Client* to
TinyGsm##TinyGSMType::GsmClient##TinyGSMType*, which can UB if the pointer is
from a different modem type; update the function to perform a safe runtime check
(use dynamic_cast to that concrete GsmClient type and verify the result is
non-null before calling delete, and log/handle the mismatch) or ensure Client
has a virtual destructor so deleting via the base pointer is safe—apply the
change in the macro MS_MODEM_DELETE_CLIENT (and consider adding a clear
comment/doc that clients must be deleted by their creating modem if you keep
static_cast).
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino (1)

299-299: Verify the begin() signature.

The MonitorMWPost.begin(dataLogger, registrationToken, samplingFeature) call re-passes the same parameters used in the constructor on line 220-221. This appears redundant since the object was already constructed with these values.

Consider whether this explicit begin() call is necessary or if a parameterless begin() would suffice, similar to how dataLogger.begin() is called on line 298.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino`
at line 299, The call MonitorMWPost.begin(dataLogger, registrationToken,
samplingFeature) is re-passing constructor parameters and likely redundant;
inspect the MonitorMWPost constructor and its begin() method and either (A)
remove the parameters from the begin() call and call MonitorMWPost.begin() if
the constructor already stored dataLogger/registrationToken/samplingFeature, or
(B) if begin() must accept them, update the constructor or begin signature so
responsibilities are clear (e.g., move initialization into begin(dataLogger,
registrationToken, samplingFeature) and construct with no args), ensuring
consistency between the MonitorMWPost constructor and MonitorMWPost.begin method
and mirroring the parameterless call pattern used by dataLogger.begin().
src/VariableArray.cpp (1)

299-315: Consider stack usage for variable-length array.

The VLA Sensor* sensorList[_sensorCount] allocates on the stack. While typically fine for small sensor counts, embedded systems with limited stack space may encounter issues with many sensors.

This is likely acceptable given typical use cases, but worth noting for users with large sensor arrays.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 299 - 315, The local VLA Sensor*
sensorList[_sensorCount] risks stack overflow on embedded targets; replace it
with heap or container allocation (e.g., allocate Sensor** via new
Sensor*[_sensorCount] and delete[] after use, or use std::vector<Sensor*>
sensorList;), then use the same logic with addedSensors, arrayOfVars,
parentSensor and isLastVarFromSensor to populate and validate the list, and add
a null/failed-allocation check to return false if allocation fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitattributes:
- Around line 34-38: The .gitattributes file references two filter drivers
(smudgePasswords and disableDebug) that have no definitions, causing Git to warn
and skip filtering; add git filter driver configurations for
filter.smudgePasswords and filter.disableDebug (both .clean and .smudge
commands) in repository setup (e.g., a setup script or in CONTRIBUTING.md) or
provide clear contributor instructions to run the equivalent git config --local
commands to register the filters (ensuring the smudge/clean commands or scripts
referenced are present and executable) so the filters named smudgePasswords and
disableDebug work as intended.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The code calls a non-existent TinyGSM API method
forceModemBaud (via modem.gsmModem.forceModemBaud(modemSerial,
static_cast<uint32_t>(modemBaud))) which will not compile; replace those calls
with the supported TinyGSM workflow by using TinyGsmAutoBaud(SerialAT, min, max)
for initial autobaud or call setBaud(rate) on the modem/serial after AT sync is
established (e.g., use modemSerial.setBaud(modemBaud) or
modem.gsmModem.setBaud(modemBaud) once communication is confirmed), and remove
the forceModemBaud references (also update any matching calls later in the file
such as the other forceModemBaud occurrences).

In `@examples/ReadMe.md`:
- Line 126: Remove the duplicated word in the sentence "The The EnviroDIY Sensor
Station Kit is designed..." by deleting the extra "The" so it reads "The
EnviroDIY Sensor Station Kit is designed to be used with the EnviroDIY
Monitoring Station Kit"; locate this exact sentence in ReadMe.md and update it
accordingly.

In `@src/modems/EspressifESP32.h`:
- Line 162: The public member TinyGsmESP32 gsmModem exposes a concrete type and
breaks encapsulation; change the API so external code does not depend on
TinyGsmESP32 directly by making gsmModem non-public (e.g., private) and exposing
a type-agnostic accessor that returns a pointer/reference to the abstract/base
TinyGsm interface or a small wrapper API. Update the EspressifESP32 class to
hide the TinyGsmESP32 field and add a method like getGsmModem() that returns
TinyGsm* or an interface with only the needed methods (testAT, setNetworkMode,
commandMode, sendAT, etc.), and adjust any callers (e.g., menu_a_la_carte) to
use that accessor instead of accessing gsmModem directly.

In `@src/modems/SodaqUBeeU201.h`:
- Around line 78-79: The public API was changed by replacing the type of
gsmModem from TinyGsm to TinyGsmUBLOX in SodaqUBeeU201 (and adding the include
TinyGsmClientUBLOX.h); update the release notes to document this breaking change
clearly: state that SodaqUBeeU201 now exposes a TinyGsmUBLOX-typed gsmModem (not
TinyGsm), list migration guidance for downstream users (e.g., update any code
that relied on the old type or performed static casts), reference the header
SodaqUBeeU201.h and the new symbol TinyGsmUBLOX, and highlight compatibility
rationale (modem-specific driver types) so integrators can adapt their code.

In `@src/ModSensorConfig.h`:
- Around line 274-284: Remove the earlier duplicate macro definition of
TINY_GSM_YIELD_MS and keep only the documented block (the `#ifndef/`#define block
around TINY_GSM_YIELD_MS with the Doxygen comment) so there's a single canonical
definition; also fix the typo in the Doxygen text by changing "is set  to
prevent" to "is set to prevent" in the documented comment. Ensure references to
TINY_GSM_YIELD_MS in other code remain unchanged and that only the documented
definition (with the comment) exists.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-55: Update the constructor documentation for
MonitorMyWatershedPublisher to state that it initializes members to default
host, path and port values rather than leaving members unset; specifically edit
the docblock above MonitorMyWatershedPublisher() to mention the default
host/path/port initialization so the comment matches the constructor's behavior.

In `@src/SensorBase.cpp`:
- Around line 373-384: clearValues() is wrongly resetting power/wake timing and
status which breaks waitForStability() when update() calls clearValues() after
wake(); modify clearValues() so it does NOT zero _millisPowerOn or
_millisSensorActivated and does NOT clear POWER_ATTEMPTED, POWER_SUCCESSFUL,
WAKE_ATTEMPTED or WAKE_SUCCESSFUL; instead only reset measurement-related
counters (_measurementAttemptsCompleted, _retryAttemptsMade) and measurement
timing (_millisMeasurementRequested, _millisMeasurementCompleted) and clear only
the measurement-related status bits (MEASUREMENT_ATTEMPTED,
MEASUREMENT_SUCCESSFUL) so wake/power stabilization logic in waitForStability()
continues to work.

In `@src/sensors/ANBpH.cpp`:
- Around line 152-176: When power-cycling is used (_powerPin >= 0) and the
caller passed a zero logging interval (_loggingIntervalMinutes == 0), the code
currently programs continuous scanning (0) which conflicts with power-cycling;
change the logic to treat zero as the minimum non-zero interval: when _powerPin
>= 0 and _loggingIntervalMinutes == 0 set programmedInterval to the same minimum
used for short intervals (10) and log a warning, then call
_anb_sensor.setIntervalTime(programmedInterval) (referencing
_loggingIntervalMinutes, programmedInterval, _powerPin, and
_anb_sensor.setIntervalTime); alternatively, if you prefer to fail setup instead
of clamping, return an error/false when _loggingIntervalMinutes == 0 and
_powerPin >= 0.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 66-74: Update the ADC part-number typos in the comment block
titled "ADS Library default settings": replace "TI1115" with "ADS1115" and
"TI1015" with "ADS1015" so the documentation matches the actual Texas
Instruments ADC part numbers referenced by the code (look for the comment block
near the ADS library defaults in ApogeeSQ212.cpp).

In `@src/sensors/AtlasParent.cpp`:
- Around line 174-186: The loop parsing I2C results can exit early when
_i2c->available() == 0 but leaves the local variable success true, which causes
bumpMeasurementAttemptCount(success) to treat a truncated read as successful;
update the code in the parsing block (the loop that uses _numReturnedValues,
_i2c->available(), and parseFloat()) so that if available() == 0 you set success
= false (and optionally log the truncation) before breaking, and ensure any
remaining measurements are handled (e.g., left unset or passed through
verifyAndAddMeasurementResult with placeholder -9999) so that
bumpMeasurementAttemptCount(success) correctly records a failed attempt.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 199-261: The code currently creates the image file before
validating image size/transfer, leading to empty files and _filename being set
on failure; change the flow in the capture routine using generateFileName,
initializeSDCard, imgFile.open, _camera.getImageSize and _camera.transferImage
so that you: 1) check SD initialisation and obtain image_size from
_camera.getImageSize first and fail fast if image_size == 0, 2) only open/create
imgFile (imgFile.open) immediately before calling transferImage, 3) set
_filename and mark success only after bytes_transferred == image_size, and 4)
ensure imgFile.close() is still called in all paths but that empty/failed files
are not left referenced by _filename.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: Add safe default fallbacks for unknown boards by defining
LOGGER_BOARD, BATTERY_PIN, and BATTERY_MULTIPLIER when they are not already
defined, similar to the existing OPERATING_VOLTAGE fallback in
KnownProcessors.h; update the section that currently only sets OPERATING_VOLTAGE
to also `#ifndef/`#define LOGGER_BOARD (to a generic value like LOG_NONE or 0),
`#ifndef/`#define BATTERY_PIN (to an invalid/no-pin sentinel like -1 or 255), and
`#ifndef/`#define BATTERY_MULTIPLIER (to 1.0), and include a pragma message for
each so builds on unsupported processors won’t fail due to missing LOGGER_BOARD,
BATTERY_PIN, or BATTERY_MULTIPLIER macros while preserving the existing warning
behavior.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 58-69: The code currently calls bumpMeasurementAttemptCount(true)
unconditionally after reading float tempVal = rtc.getTemperature(), which will
suppress retries even when tempVal is NaN from an I2C failure; change the logic
to validate tempVal (e.g., using isnan(tempVal)) before marking success: keep
calling verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) as-is (it
already handles NaN), but only call bumpMeasurementAttemptCount(true) when
tempVal is a finite number, otherwise call bumpMeasurementAttemptCount(false)
(or return failure) so retries are not suppressed; update the block around
rtc.getTemperature(), verifyAndAddMeasurementResult, and
bumpMeasurementAttemptCount to implement this conditional success marking.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Line 111: The variable i2c_status is declared as type byte (uint8_t) but
initialized with the negative literal -1; change the initialization to an
explicit unsigned value (e.g., byte i2c_status = 0; or byte i2c_status = 0xFF;)
or remove the initializer entirely since i2c_status is overwritten later (see
the assignment to i2c_status in the surrounding code) so use a valid uint8_t
literal to avoid implicit conversion from negative values.

In `@src/sensors/ProcessorAnalog.h`:
- Around line 1-14: The file header in ProcessorAnalog.h contains a stray
asterisk after the filename on the top comment line; remove the extra '*' so the
header reads correctly. Open ProcessorAnalog.h and update the comment block that
starts with "@file ProcessorAnalog.h" to eliminate the stray character, leaving
the rest of the docblock and references to the ProcessorAnalog and
ProcessorAnalog_Voltage classes unchanged.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 96-102: counter_internal.Clear() is being called unconditionally
which wipes the pending count even when Peek() indicates the sensor read failed;
move the call so it only executes after a successful read path (i.e., only when
Peek() returned true/indicated success and before returning from
bumpMeasurementAttemptCount(success)). Update the control flow around
Peek()/counter_internal.Clear() so failed reads do not clear the counter; ensure
counter_internal.Clear() remains executed in the branch that leads to
bumpMeasurementAttemptCount(success) on success.

---

Outside diff comments:
In `@build-menu-configurations.ps1`:
- Around line 147-153: The code defines the array variable as $publisherFlag but
the foreach iterates $publisherFlags, so the loop never executes; fix by making
the names consistent—either rename the array to $publisherFlags where it's
declared or change the foreach to iterate $publisherFlag; update the declaration
and any other references (the array and the Foreach (...) block) so both use the
same identifier (e.g., $publisherFlags) to restore the publisher build checks.
- Around line 147-153: The file encoding warning means
build-menu-configurations.ps1 contains non-ASCII characters and should be saved
with a UTF-8 BOM or stripped of non-ASCII characters; open the file (inspect the
$publisherFlag / $publisherFlags declarations and surrounding strings/ comments
and the Foreach block) and either (a) re-save the file in your editor/CI as
"UTF-8 with BOM" or (b) remove/replace any non-ASCII characters (smart quotes,
em-dashes, non-breaking spaces) in the $publisherFlag/$publisherFlags lines and
nearby text so the file is pure ASCII/UTF-8 without needing a BOM. Ensure you
commit the re-encoded file so static analysis no longer reports the missing BOM.

In `@examples/logging_to_MMW/ReadMe.md`:
- Line 24: Replace the incorrect phrase "universally universal identifiers
(UUID)" with the correct "universally unique identifiers (UUID)" in both the TOC
entry and the corresponding section header; specifically update the TOC bullet
text "[Set the universally universal identifiers (UUID) for each variable]" and
the section heading that uses the same phrasing so both instances match the
correct wording.

In `@library.json`:
- Around line 354-362: Upgrade to ANBSensorsModbus v0.4.2 removed the findBaud()
API and changed startReadOnly() return behavior (and fixed
isMeasurementComplete()), so search the codebase for any calls to findBaud(),
startReadOnly(), or assumptions about startReadOnly()’s return value and
replace/remove them: remove uses of findBaud() and replace with explicit baud
configuration or the library’s supported initialization flow (e.g.,
setSerialBaud or constructor-based baud setup), adapt code that depends on
startReadOnly()’s old return semantics to handle the new return value correctly,
and verify uses of isMeasurementComplete() follow the updated semantics; update
unit/integration tests and any documentation/comments referencing findBaud()
accordingly.

In `@README.md`:
- Around line 116-119: The final sentence about intended use for publishing
images is not indented as part of the AWS S3 bullet, so move or re-indent "It is
intended for publishing images." so it remains inside the S3 sub-list (i.e.,
make it a continuation of the "- The S3 publisher..." bullet or add it as
another "- " sub-bullet under the "[AWS S3]" entry); update the README.md
content around the S3 publisher block so all S3 notes stay within the same
bullet list.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 305-439: In MonitorMyWatershedPublisher::flushDataBuffer guard
against a null/empty _registrationToken before calling
txBufferAppend(tokenHeader) and txBufferAppend(_registrationToken): check if
_registrationToken == nullptr || strlen(_registrationToken) == 0, and if so log
a clear error (e.g., PRINTOUT) and return a non-publishing code (e.g., 0) or
otherwise avoid appending the token header; if you choose to proceed without
returning, append an empty string instead of dereferencing the null pointer.
Ensure the check is placed before the txBufferAppend(tokenHeader) /
txBufferAppend(_registrationToken) calls so txBufferAppend is never called with
a null pointer.
- Around line 208-249: In MonitorMyWatershedPublisher::connectionNeeded the
percent fullness checks use if (percent >= 50) else if (percent >= 75) else if
(percent >= 90) which short-circuits higher thresholds; update the ordering (or
use descending comparisons) on the _logBuffer.getPercentFull() checks so the 90%
and 75% branches run before the 50% branch (e.g., check percent >= 90, then >=
75, then >= 50) and preserve the same interval adjustments to ensure more
aggressive send cadence at higher buffer fullness.

In `@src/SensorBase.h`:
- Around line 656-795: Declare a default member initializer for _powerPin2 so it
cannot be left uninitialized (e.g., initialize the int8_t _powerPin2 member to
-1 inline); update the class member declaration for _powerPin2 (not the
constructor) to include the default, keeping existing constructor logic
unchanged; this ensures powerUp() and powerDown() see a safe default even if new
constructors are later added.

In `@src/sensors/ANBpH.cpp`:
- Around line 343-385: The getValues() call may fail and leave
pH/temp/sal/spcond/raw_cond uninitialized; ensure you initialize those
measurement variables (pH, temp, sal, spcond, raw_cond, health, diagnostic,
status) to safe defaults before calling _anb_sensor.getValues(), only perform
range/health checks (the success &= ((0.0 < pH && pH < 14.00) || ...)) and log
prints when success is true (or when you explicitly want to log an error state),
and only call verifyAndAddMeasurementResult for pH/temp/etc. if success is true
or you have a defined fallback value; update the logic around getValues(),
success, _retryAttemptsMade, _allowedMeasurementRetries, and
verifyAndAddMeasurementResult to reflect this safe-path.

In `@src/sensors/ANBpH.h`:
- Around line 478-501: The comment says loggingIntervalMinutes must be 10–240
but the code permits 0 for always‑powered sensors; update both the docs and
validation: in the ANBpH constructor/initializer that takes
loggingIntervalMinutes, powerPin, powerPin2, and enablePin, explicitly allow
loggingIntervalMinutes == 0 only when the sensor is always powered (i.e., power
is not MCU‑controlled: powerPin == -1 && powerPin2 == -1 && enablePin == -1),
otherwise validate and reject (throw or return an error) values outside 10–240;
also update the docblock for loggingIntervalMinutes to state that 0 is valid
only in always‑powered mode.

In `@src/sensors/GroPointParent.h`:
- Line 2: The Doxygen `@file` tag in the header's top comment incorrectly names
GroPointParent.cpp; update the `@file` tag in GroPointParent.h to reference
GroPointParent.h (edit the comment block at the top of GroPointParent.h where
the `@file` tag appears, e.g., in the header comment for the GroPointParent class)
so the file name matches the header file; also scan the same comment block for
any other copy-paste mismatches and correct them.

---

Duplicate comments:
In `@continuous_integration/generate_job_matrix.py`:
- Around line 104-118: The exclusion check uses full path `root` so comparisons
like `root not in [".history", "logger_test", ...]` never match and `subdirs` is
unused (B007); update the os.walk loop in the block that builds
`non_menu_examples` (and the similar block around the other occurrence) to
iterate as `for root, _, files in os.walk(examples_path):` (or name `subdirs` as
`_`) and perform exclusions against the directory basename via
`os.path.basename(root)` (include your folder names such as "data_saving",
"mayfly", "drwi", ".history", "logger_test", "archive", "tests", and
`menu_example_name`), leaving the rest of the logic (appending
`os.path.realpath(root)` and the verbose print using `file_path`) unchanged.

In `@examples/examples.dox`:
- Around line 67-70: The `@ref` target in the subpage link is outdated: change the
reference from "@ref examples_outdated" to match the renamed anchor
"page_examples_outdated" (or rename the anchor back to examples_outdated if that
was intended) so the cross-reference matches the page identifier used in the
`@subpage` declaration; update the reference in the Examples file where "@ref
examples_outdated" appears to "@ref page_examples_outdated".

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: Replace the incorrect possessive "UUID's" with the
correct plural "UUIDs" in the ReadMe sentence that refers to copying values from
the "View Token UUID List" link (the sentence beginning "If you are sending data
to Monitor My Watershed..." and the following note about order); make the same
plural correction anywhere else in this file where "UUID's" appears to ensure
consistent pluralization.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md`:
- Around line 45-49: The content under the "Prepare and set up PlatformIO"
section is a duplicate of the earlier portal terminology update and requires no
changes; leave the header "Prepare and set up PlatformIO" and its bullet list
(including the Monitor My Watershed registration and the platformio.ini
replacement link) as-is and do not modify the existing text or links.
- Around line 96-99: This section headed "### Set the universally universal
identifiers (UUID) for each variable" (which references the "View Token UUID
List" button) is a duplicate of the portal terminology update and requires no
separate changes; remove this redundant subsection (or replace it with a
one-line pointer) so the ReadMe doesn't repeat guidance—look for the heading
text and the phrase "View Token UUID List" to locate and remove or collapse the
duplicate content.

In `@src/modems/QuectelBG96.h`:
- Around line 238-242: The public TinyGsmBG96 member gsmModem depends on the
TinyGSM API and should be conditionally declared under the same preprocessor
guard used elsewhere; modify the QuectelBG96 header to wrap the TinyGsmBG96
gsmModem declaration with the identical `#if/`#ifdef check (the one used for
TinyGSM compatibility) so the member is only compiled when the TinyGSM API is
available, keeping the class definition consistent with other TinyGSM-dependent
symbols.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 125-196: The retry loop in
MaxBotixSonar::addSingleMeasurementResult (the while loop using rangeAttempts
and _stream->parseInt()) can exceed the allowed measurement time budget; fix it
by establishing a hard deadline before the retry loop (use millis() or micros()
and the configured per-measurement budget/166ms constant) and, inside the loop,
check elapsed time and break out as a failed attempt if the deadline is reached;
also cap retries accordingly (instead of blindly looping to 25) so the
combination of triggering, parseInt wait, and retries cannot exceed the
measurement timeout, and ensure bumpMeasurementAttemptCount(false) is called
when you exit due to deadline.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 86-89: The log message and the negative-only check disagree:
update the branch that currently tests events < 0 in TallyCounterI2C.cpp to
handle zero as well by changing the condition to events <= 0, and update the
MS_DBG call (using getSensorNameAndLocation() and the F(...) message) to say
"non-positive or bad, assuming sensor non-response" (or similar) so the message
matches the condition and intent.

In `@src/sensors/TIINA219.cpp`:
- Around line 74-103: The code in TIINA219::addSingleMeasurementResult currently
treats readings as valid if they are not NaN, which can accept stale values when
the INA219 I2C transaction failed; update the success criteria to also check
ina219_phy.success() (ideally immediately after each read or at least after the
three reads) and only mark success when all three reads are not NaN AND
ina219_phy.success() is true; if ina219_phy.success() is false, call
bumpMeasurementAttemptCount(false) and avoid calling
verifyAndAddMeasurementResult(INA219_CURRENT_MA_VAR_NUM/INA219_BUS_VOLTAGE_VAR_NUM/INA219_POWER_MW_VAR_NUM).
Ensure you reference the existing getCurrent_mA(), getBusVoltage_V(),
getPower_mW() calls and preserve the early return on
!getStatusBit(MEASUREMENT_SUCCESSFUL).

In `@src/sensors/YosemitechParent.cpp`:
- Line 230: The Y520 conductivity conversion currently multiplies parmValue
without verifying getValues() succeeded, so when getValues() fails the sentinel
-9999 is altered; update the condition at the if (_model == Y520 &&
!isnan(parmValue)) check to also verify success (e.g., if (success &&
!isnan(parmValue))) so the conversion in YosemitechParent.cpp preserves the
sentinel on failure (match the existing Y4000 pattern that uses success and
!isnan(Cond)).

---

Nitpick comments:
In @.coderabbit.yaml:
- Around line 29-33: Update the path_instructions block to also target .h and
.cpp files so reviews cover headers and implementations (in addition to .hpp);
specifically add entries for patterns '**/*.h' and '**/*.cpp' with the same
review instructions text as the existing '**/*.hpp' entry, ensuring the
linter/reviewer will process files like src/sensors/*.h and src/modems/*.cpp.

In
`@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/DRWI_Mayfly1_WiFi.ino`:
- Line 299: The call MonitorMWPost.begin(dataLogger, registrationToken,
samplingFeature) is re-passing constructor parameters and likely redundant;
inspect the MonitorMWPost constructor and its begin() method and either (A)
remove the parameters from the begin() call and call MonitorMWPost.begin() if
the constructor already stored dataLogger/registrationToken/samplingFeature, or
(B) if begin() must accept them, update the constructor or begin signature so
responsibilities are clear (e.g., move initialization into begin(dataLogger,
registrationToken, samplingFeature) and construct with no args), ensuring
consistency between the MonitorMWPost constructor and MonitorMWPost.begin method
and mirroring the parameterless call pattern used by dataLogger.begin().

In `@src/modems/LoggerModemMacros.h`:
- Around line 543-551: The deleteClient implementation
(specificModem::deleteClient) unsafely static_casts a Client* to
TinyGsm##TinyGSMType::GsmClient##TinyGSMType*, which can UB if the pointer is
from a different modem type; update the function to perform a safe runtime check
(use dynamic_cast to that concrete GsmClient type and verify the result is
non-null before calling delete, and log/handle the mismatch) or ensure Client
has a virtual destructor so deleting via the base pointer is safe—apply the
change in the macro MS_MODEM_DELETE_CLIENT (and consider adding a clear
comment/doc that clients must be deleted by their creating modem if you keep
static_cast).

In `@src/sensors/AlphasenseCO2.cpp`:
- Around line 92-117: The debug output after reading the ADC differential should
include both adcCounts and adcVoltage for consistency with other sensors; update
the MS_DBG call in the block following the switch on _adsDiffMux (after calls to
ads.readADC_Differential_0_1/_0_3/_1_3/_2_3 and adcVoltage =
ads.computeVolts(adcCounts)) to log adcCounts as well as adcVoltage, keeping the
existing message and precision, and ensure this path still uses
bumpMeasurementAttemptCount(false) on the default invalid-mux case.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-108: The function that checks adcVoltage in ApogeeSQ212.cpp
currently drops out silently when voltage is outside the valid range; add a
debug log before returning failure to indicate the out-of-range voltage and the
expected range. Specifically, inside the function that uses the adcVoltage check
(the block that currently sets calibResult and calls
verifyAndAddMeasurementResult for SQ212_PAR_VAR_NUM and SQ212_VOLTAGE_VAR_NUM),
add an MS_DBG (or existing logger) call in the else path that logs adcVoltage
and the allowed range (-0.3 to 3.6) and optionally the sensor id/constants
(SQ212_CALIBRATION_FACTOR or SQ212_VOLTAGE_VAR_NUM) so failures are visible,
then continue to return bumpMeasurementAttemptCount(success).

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 78-85: The code currently accepts any non-zero pressure when
deciding to call verifyAndAddMeasurementResult for MS5803_PRESSURE_VAR_NUM; add
an upper-bound check using the sensor's configured _maxPressure (e.g., require
press <= _maxPressure) alongside the existing checks (temp range and press !=
0.0) before setting success and adding results. Locate the if block that checks
isnan(temp)/isnan(press), temp range, and press != 0.0 and extend it to validate
press against _maxPressure so out-of-range high pressures are rejected before
calling verifyAndAddMeasurementResult and setting success.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 147-152: The PaleoTerraRedox failure checks are incomplete: extend
the failure logic in the PaleoTerraRedox read/convert path (where res,
i2c_status, and config are evaluated) to also detect ADC overflow/underflow,
conversion-not-ready (RDY) in the MCP3424-style config register, and
out-of-range voltages; specifically, inspect the raw ADC result/flag bits for
overflow/underflow and the config RDY bit and mark res = -9999 on those
conditions, and add bounds checks on the computed voltage (e.g., below min or
above max physical sensor voltage) to set the same failure code so all failure
modes (isnan(res), all-zeros i2c/config, ADC overflow/underflow, RDY not set,
out-of-range voltage) produce a consistent -9999 result.
- Around line 120-125: The code waits PTR_CONVERSION_WAIT_TIME_MS then calls
_i2c->requestFrom(int(_i2cAddressHex), 4) and checks _i2c->available(); replace
that pattern by using the return value of requestFrom directly: call
requestFrom(int(_i2cAddressHex), 4) and if it returns fewer than 4 bytes (or
zero) immediately return bumpMeasurementAttemptCount(false); update the
conditional that currently uses _i2c->available() to instead test the
requestFrom result so you avoid the extra available() call and make the I2C read
idiomatic.

In `@src/sensors/RainCounterI2C.h`:
- Line 239: The preprocessor conditional using MS_RAIN_SOFTWAREWIRE and DOXYGEN
should use the logical OR (already fixed) and follow the project's style by
wrapping the defined macro in parentheses; update the condition from using
defined DOXYGEN to defined(DOXYGEN) in the same directive that references
MS_RAIN_SOFTWAREWIRE and DOXYGEN so it matches the existing style used elsewhere
in this header.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 312-317: The header field adsGain_t _PGA_gain carries a `@todo`
about auto-determining gain which should not live as a stale comment in the
header; open a tracker/backlog issue describing the auto-gain feature (including
goals, acceptance criteria, and any constraints like board voltage sources and
measurement flow) and replace the in-code `@todo` in TurnerTurbidityPlus.h with a
short reference comment that points to the created issue ID or URL (or remove
the todo entirely), so future readers are directed to the proper issue rather
than an inline TODO.

In `@src/VariableArray.cpp`:
- Around line 299-315: The local VLA Sensor* sensorList[_sensorCount] risks
stack overflow on embedded targets; replace it with heap or container allocation
(e.g., allocate Sensor** via new Sensor*[_sensorCount] and delete[] after use,
or use std::vector<Sensor*> sensorList;), then use the same logic with
addedSensors, arrayOfVars, parentSensor and isLastVarFromSensor to populate and
validate the list, and add a null/failed-allocation check to return false if
allocation fails.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/sensors/ANBpH.h (1)

478-509: ⚠️ Potential issue | 🔴 Critical

Breaking API change: verify existing usages are updated.

Insertion of loggingIntervalMinutes as the fourth parameter breaks existing code. The example file menu_a_la_carte.ino demonstrates this—the call:

ANBpH anbPH(anbModbusAddress, modbusSerial, anbPower, alAdapterPower,
            al485EnablePin, anbNumberReadings);

now passes alAdapterPower (an int8_t pin value) where loggingIntervalMinutes (an int16_t) is expected, causing semantic misinterpretation. All existing ANBpH instantiations must be updated to include the logging interval parameter.

The documentation requirement for 10–240 minutes is enforced: the setup() method at lines 153–164 of ANBpH.cpp clamps out-of-range values (except 0 for continuous mode) and logs the adjustment via MS_DBG(). This clamping pattern is appropriate for embedded systems.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 509, The new fourth parameter
loggingIntervalMinutes added to the ANBpH constructors breaks existing call
sites (e.g., menu_a_la_carte.ino) where earlier positional arguments like
alAdapterPower are now shifted into loggingIntervalMinutes; update all ANBpH
instantiations to insert the correct logging interval (int16_t, 10–240 or 0 for
continuous) at the fourth position or switch to named/explicit factory/overload
if desired, and verify setup() in ANBpH.cpp still clamps out-of-range values
(10–240, 0 allowed) and logs adjustments via MS_DBG(); specifically search for
ANBpH( ... ) usages and the ANBpH constructors to correct the argument order and
ensure powerPin2 and enablePin remain correct.
library.json (1)

154-359: ⚠️ Potential issue | 🔴 Critical

Correct version specifications and verify availability before deployment.

Critical issues found:

  • YosemitechModbus 0.5.4 does not exist—latest release is 0.5.3 (Sept 26, 2025)
  • GroPointModbus 0.1.5 does not exist—latest release is 0.1.4 (Sept 26, 2025)
  • Adafruit ADS1X15 2.6.2 not yet indexed in Arduino Library Manager (only 2.6.1 available as of Jan 18, 2026); requires GitHub ZIP install
  • KellerModbus 0.2.7 not yet indexed in Arduino Library Manager (only 0.2.6 available); requires GitHub ZIP install
  • ANBSensorsModbus 0.4.2 not yet indexed in Arduino Library Manager (only 0.3.0 available as of Oct 6, 2025); requires GitHub ZIP install

Additionally, DallasTemperature 4.0.6 introduces breaking behavior: applications using 85°C or 127.94°C as valid readings will need to be updated to handle new error constants (DEVICE_POWER_ON_RESET_RAW, DEVICE_INSUFFICIENT_POWER_RAW).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library.json` around lines 154 - 359, Update the library version entries to
only reference releases that exist in the Arduino Library Manager or switch to
GitHub ZIP/git refs for unreleased versions: change "YosemitechModbus" version
from "~0.5.4" to "~0.5.3" (or a GitHub URL if you need 0.5.4), change
"GroPointModbus" from "~0.1.5" to "~0.1.4" (or use its repo ZIP), set "Adafruit
ADS1X15" to the indexed "~2.6.1" or replace the version field with the GitHub
ZIP/git ref for 2.6.2, set "KellerModbus" to the indexed "~0.2.6" or use its
GitHub ZIP for 0.2.7, and set "ANBSensorsModbus" to the indexed "~0.3.0" or use
a GitHub ZIP/git ref for 0.4.2; additionally update any code that consumes
DallasTemperature readings to handle the new error constants
DEVICE_POWER_ON_RESET_RAW and DEVICE_INSUFFICIENT_POWER_RAW (replace literal
85°C/127.94°C checks with comparisons against these constants) so consumers
aren’t broken by version 4.0.6.
🧹 Nitpick comments (11)
src/sensors/CampbellOBS3.cpp (1)

56-56: Minor: Debug message printed before ADC operations complete.

The "is reporting:" debug message is printed before ADC initialization (line 81) and reading occurs. If the ADC fails to initialize, the log will show this message followed by a failure, which could be slightly misleading during troubleshooting.

Consider moving this debug statement after the ADC initialization succeeds, or adjusting the message to indicate the measurement attempt is starting rather than reporting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` at line 56, The debug log
MS_DBG(getSensorNameAndLocation(), F("is reporting:")); is emitted before ADC
initialization and readings; move this call so it runs only after the ADC
initialization succeeds (i.e., after the ADC begin/init/check routine) and
immediately before the measurement/read calls, or alternatively change the
message text to something like "is attempting measurement:" to indicate a start
rather than a successful report; update the call referencing
getSensorNameAndLocation() accordingly so logs reflect successful ADC init
before claiming a report.
src/sensors/TurnerCyclops.cpp (1)

99-107: Consider explicit error handling for ADC read failures.

The Adafruit ADS library's readADC_SingleEnded() can return error values (typically -1) on failure. While the voltage range check at line 107 provides implicit protection, explicit validation of the ADC read could make error handling more robust and the code's intent clearer.

♻️ Optional: Add explicit ADC read validation
     // Measure the ADC raw count
     adcCounts = ads.readADC_SingleEnded(_adsChannel);
+    if (adcCounts < 0) {
+        MS_DBG(F("  ADC read failed on channel"), _adsChannel);
+        return bumpMeasurementAttemptCount(false);
+    }
     // Convert ADC raw counts value to voltage (V)
     adcVoltage = ads.computeVolts(adcCounts);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 99 - 107, The ADC read uses
ads.readADC_SingleEnded(_adsChannel) but doesn't explicitly check for a failure
sentinel (e.g., -1); add an explicit validation right after calling
ads.readADC_SingleEnded by checking adcCounts for error values and handle them
(log via MS_DBG or process logger, skip further computeVolts/processing, and
return/mark sensor read as failed) instead of relying solely on the subsequent
adcVoltage range check; update the block around ads.readADC_SingleEnded,
adcCounts, adcVoltage and computeVolts to perform this validation and clear,
explicit error path.
src/sensors/MaxBotixSonar.cpp (1)

156-156: TODO: Unify retries with other sensors.

This TODO aligns with the PR's broader effort to consolidate measurement workflows. The retry mechanism here differs from the new bumpMeasurementAttemptCount pattern used elsewhere.

Would you like me to open an issue to track unifying this internal retry loop with the library's new measurement attempt infrastructure?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.cpp` at line 156, The TODO notes a custom internal
retry loop in MaxBotixSonar.cpp that should be unified with the library's new
measurement-attempt infrastructure; replace the bespoke retry logic inside the
MaxBotixSonar measurement method with calls into the shared
bumpMeasurementAttemptCount/measurement workflow used by other sensors (preserve
current retry semantics where needed), remove the internal loop and the TODO,
update the MaxBotixSonar measurement method (e.g., read/measure function in
MaxBotixSonar class) to use the common retry state and failure propagation, and
adjust any tests or callers to rely on the centralized attempt counting.
src/modems/SIMComSIM7080.cpp (1)

50-58: Pre-existing: Duplicate AT command detected.

Lines 51 and 57 both send the same +CNETLIGHT=1 command. The comment on lines 53-56 suggests the second call is intended for a different purpose ("netlight indication of GPRS status"), but it sends the identical command. This may be intentional or a copy-paste oversight from before this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/SIMComSIM7080.cpp` around lines 50 - 58, The code in
SIMComSIM7080.cpp issues duplicate gsmModem.sendAT(F("+CNETLIGHT=1")) /
gsmModem.waitResponse() calls: remove the redundant call or replace the second
call with the correct AT parameter that enables netlight GPRS indication per the
SIM7080 spec; locate the two gsmModem.sendAT(...) / gsmModem.waitResponse()
pairs and either delete the duplicate or update the second sendAT to the proper
command (consult the SIM7080 AT command reference) so the netlight GPRS behavior
is configured correctly.
src/sensors/TurnerTurbidityPlus.h (1)

312-316: Track the auto-gain TODO to avoid it getting lost.
If automatic gain selection is in scope, consider filing an issue or adding acceptance criteria. I can help draft the issue or implementation if useful.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.h` around lines 312 - 316, The TODO about
automatic gain selection in TurnerTurbidityPlus.h (the comment near GAIN_DEFAULT
and ADS1x15 gain) should be tracked outside the source to avoid being lost:
create a ticket/issue that describes auto-gain acceptance criteria (triggers,
voltage/gain mapping, tests) and add the issue ID or short URL to the TODO
comment in TurnerTurbidityPlus.h (so the TODO now reads with the issue
reference), or alternatively add a TODO_TRACKING constant or pragma with the
issue ID; ensure the referenced symbols include the ADS1x15 gain setting and
GAIN_DEFAULT so reviewers can find the exact location.
src/modems/SIMComSIM800.h (1)

81-81: Replace direct SIM800 header with standard TinyGSM include pattern.

The symbols TinyGsmClientSIM800.h and TinyGsmSim800 are confirmed to exist in TinyGSM. However, the direct inclusion of TinyGsmClientSIM800.h bypasses the standard initialization pattern. Use the recommended approach instead:

`#define` TINY_GSM_MODEM_SIM800
`#include` <TinyGsmClient.h>

This provides the proper typedefs (TinyGsm for TinyGsmSim800, TinyGsmClient for the client classes) and ensures correct macro-driven initialization across the library.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/SIMComSIM800.h` at line 81, Replace the direct include of
TinyGsmClientSIM800.h with the TinyGSM macro-based include pattern: define
TINY_GSM_MODEM_SIM800 before including TinyGsmClient.h so the library provides
the proper typedefs (TinyGsm for TinyGsmSim800 and TinyGsmClient for the client
class) instead of relying on TinyGsmClientSIM800.h; update any code referencing
TinyGsmClientSIM800.h to rely on TinyGsm and TinyGsmClient types after making
this change.
.coderabbit.yaml (1)

30-33: Consider extending path instructions to include .h files.

The path_instructions currently only target **/*.hpp files, but this repository appears to primarily use .h extension for C++ headers (e.g., SequansMonarch.h, DigiXBee3GBypass.h, LoggerBase.h). Consider updating the pattern to also include .h files for consistent review coverage.

📝 Proposed fix
   path_instructions:
-    - path: '**/*.hpp'
+    - path: '**/*.h'
       instructions: >-
         Review the C++ code, point out issues relative to principles of clean
         code, expressiveness, and performance.
+    - path: '**/*.hpp'
+      instructions: >-
+        Review the C++ code, point out issues relative to principles of clean
+        code, expressiveness, and performance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 30 - 33, Update the path_instructions pattern
to include .h headers in addition to .hpp so C++ header files like
SequansMonarch.h, DigiXBee3GBypass.h, and LoggerBase.h are covered; modify the
existing entry that currently reads '**/*.hpp' to include both patterns (for
example '**/*.h' and '**/*.hpp' or a combined glob like '**/*.{h,hpp}') so
reviewers will run on both header extensions.
src/sensors/PaleoTerraRedox.cpp (1)

147-153: Resolve or track the remaining failure-condition TODO.

The @todo suggests additional invalid-state checks are pending; with retries centralized, incomplete failure detection may still mark bad reads as successful. I can help draft concrete failure checks if useful.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` around lines 147 - 153, The current
PaleoTerraRedox read logic only treats NaN and a specific zero/i2c/config
combination as failures (res, i2c_status, config, success), leaving other
invalid states counted as success; add concrete failure conditions in the
PaleoTerraRedox read/parse path: treat out-of-range values (e.g., res <
MIN_VALID or res > MAX_VALID), known sentinel values, corrupted-format reads,
and repeated identical reads across retry attempts as failures, and set success
= false when any of these checks trigger; implement these checks where res is
computed (inside the function/method that sets res, i2c_status, config, and
success) and document MIN_VALID/MAX_VALID constants for maintainability.
src/sensors/TurnerTurbidityPlus.cpp (1)

170-180: Consider logging when voltage is out of range.

When adcVoltage falls outside the valid range (line 171), the code silently skips adding results. Adding a debug log would aid troubleshooting.

📝 Suggested debug logging
     if (adcVoltage < 5.3 && adcVoltage > -0.3) {
         // 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));
         verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM,
                                       adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage out of range:"), String(adcVoltage, 3));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.cpp` around lines 170 - 180, When adcVoltage
falls outside the valid ADS1X15 range the branch silently skips adding results;
add a debug log using MS_DBG to record the out-of-range adcVoltage and the
expected range (e.g. "adcVoltage out of range: <value>, expected -0.3..5.3")
just before skipping so you can trace failures in the calibration code that
computes calibResult using _conc_std, _volt_std, and _volt_blank and calls
verifyAndAddMeasurementResult for TURBIDITY_PLUS_VAR_NUM and
TURBIDITY_PLUS_VOLTAGE_VAR_NUM; ensure the log is emitted in the same
conditional logic that currently sets success = true so maintain the existing
behavior of not adding results but now with clear diagnostics.
src/sensors/EverlightALSPT19.cpp (1)

59-84: Consider whether sensor_adc == 0 should report valid measurements.

When the ADC reads exactly 0, the code sets all values to 0.0 and marks the measurement as successful. This could mask sensor connection issues (e.g., a disconnected sensor might consistently read 0).

The comment on lines 79-80 acknowledges there's no quality criteria, but a truly dark environment vs. a failed sensor are indistinguishable. Consider whether logging a warning for sensor_adc == 0 would be helpful for debugging.

📝 Optional: Add debug warning for zero ADC reading
     if (sensor_adc == 0) {
+        MS_DBG(F("  Warning: ADC reading is 0 (sensor may be disconnected or in complete darkness)"));
         volt_val    = 0.0;
         current_val = 0.0;
         lux_val     = 0.0;
     } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/EverlightALSPT19.cpp` around lines 59 - 84, When sensor_adc == 0
we should not silently record zero measurements; add a debug/warning log and
treat it as a failed reading: in the branch that currently sets
volt_val/current_val/lux_val to 0.0, call your logging macro (e.g., MS_DBG or
project warning macro) to note "ADC=0 possible sensor/disconnect", skip calling
verifyAndAddMeasurementResult(ALSPT19_VOLTAGE_VAR_NUM/..._CURRENT_VAR_NUM/..._ILLUMINANCE_VAR_NUM)
and return bumpMeasurementAttemptCount(false) instead of true so the failure is
recorded; keep the existing successful path unchanged for nonzero sensor_adc.
examples/menu_a_la_carte/menu_a_la_carte.ino (1)

3796-3798: Comment may be misleading after refactor.

The warning comment still says "PLEASE REMOVE AUTOBAUDING" but the code now uses forceModemBaud instead. Consider updating the comment to reflect the current behavior.

📝 Suggested comment update
     // WARNING: PLEASE REMOVE AUTOBAUDING FOR PRODUCTION CODE!
     if (!modem.gsmModem.testAT()) {
-        PRINTOUT(F("Attempting to force the modem baud rate."));
+        // Note: forceModemBaud should be removed for production code
+        // once the modem is configured to the correct baud rate.
+        PRINTOUT(F("Attempting to force the modem baud rate."));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/menu_a_la_carte.ino` around lines 3796 - 3798, The
existing inline warning comment that tells readers to "PLEASE REMOVE
AUTOBAUDING" is now misleading because the code calls
modem.gsmModem.forceModemBaud(modemSerial, static_cast<uint32_t>(modemBaud));;
update the comment near the PRINTOUT(...) and forceModemBaud invocation to
reflect that the code actively forces the modem baud rate (auto-bauding is being
overridden) and, if relevant, note any conditions or follow-up steps (e.g., when
to remove the forced-baud workaround) so the comment accurately describes the
current behavior of forceModemBaud.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modems/SIMComSIM7000.h`:
- Line 77: The code incorrectly includes a non-existent header
TinyGsmClientSIM7000SSL.h and references a non-existent type TinyGsmSim7000SSL;
instead, add a compile-time define TINY_GSM_MODEM_SIM7000SSL (via project build
flags or a header `#define`) and replace the custom header/type usage with
TinyGsmClientSecure from TinyGSM; update any declarations/constructors that
mention TinyGsmSim7000SSL to use TinyGsmClientSecure and ensure the modem
selection uses TINY_GSM_MODEM_SIM7000SSL so the SIM7000 SSL support is provided
by the standard TinyGsmClientSecure implementation.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 172: The assignment to result uses a uint16_t cast which can corrupt
negative values and wrap large positives; in MaxBotixSonar.cpp change the
assignment so you either cast parseInt() directly to int16_t (e.g., result =
static_cast<int16_t>(_stream->parseInt())) or keep the parsed value as a
long/long int (e.g., temp = _stream->parseInt()), validate its range against
_maxRange and non-negativity, then assign the validated value into the int16_t
result; update any subsequent checks (result <= 0, comparisons to _maxRange) to
operate on the validated signed value.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 32-57: The code in ProcessorAnalog::addSingleMeasurementResult
currently only checks _voltageMultiplier but not _operatingVoltage; add a guard
alongside the existing _voltageMultiplier check to validate _operatingVoltage is
> 0 (same place that checks _dataPin and _voltageMultiplier), log an error via
MS_DBG(F("No analog pin or voltage divider specified!")) or a clearer message,
and return bumpMeasurementAttemptCount(false) when invalid so volt calculations
using _operatingVoltage and PROCESSOR_ADC_MAX are not performed with
zero/negative values; keep existing behavior for
getStatusBit(MEASUREMENT_SUCCESSFUL) and other flow intact.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 103-121: Capture the return value of _i2c->requestFrom(...) into a
variable (e.g., bytesRequested) and validate it is either 1 or 4 before parsing;
if it is outside that set, log and return bumpMeasurementAttemptCount(false).
Also ensure the actual number of bytes read (byte_in) equals the requested count
(or the expected count) before using SerialBuffer—if fewer bytes were read,
treat it as an error and call bumpMeasurementAttemptCount(false). Update the
logic around _i2c->requestFrom, SerialBuffer, byte_in and the subsequent
parsing/version-detection to only proceed when the requested count and read
count are valid.

---

Outside diff comments:
In `@library.json`:
- Around line 154-359: Update the library version entries to only reference
releases that exist in the Arduino Library Manager or switch to GitHub ZIP/git
refs for unreleased versions: change "YosemitechModbus" version from "~0.5.4" to
"~0.5.3" (or a GitHub URL if you need 0.5.4), change "GroPointModbus" from
"~0.1.5" to "~0.1.4" (or use its repo ZIP), set "Adafruit ADS1X15" to the
indexed "~2.6.1" or replace the version field with the GitHub ZIP/git ref for
2.6.2, set "KellerModbus" to the indexed "~0.2.6" or use its GitHub ZIP for
0.2.7, and set "ANBSensorsModbus" to the indexed "~0.3.0" or use a GitHub
ZIP/git ref for 0.4.2; additionally update any code that consumes
DallasTemperature readings to handle the new error constants
DEVICE_POWER_ON_RESET_RAW and DEVICE_INSUFFICIENT_POWER_RAW (replace literal
85°C/127.94°C checks with comparisons against these constants) so consumers
aren’t broken by version 4.0.6.

In `@src/sensors/ANBpH.h`:
- Around line 478-509: The new fourth parameter loggingIntervalMinutes added to
the ANBpH constructors breaks existing call sites (e.g., menu_a_la_carte.ino)
where earlier positional arguments like alAdapterPower are now shifted into
loggingIntervalMinutes; update all ANBpH instantiations to insert the correct
logging interval (int16_t, 10–240 or 0 for continuous) at the fourth position or
switch to named/explicit factory/overload if desired, and verify setup() in
ANBpH.cpp still clamps out-of-range values (10–240, 0 allowed) and logs
adjustments via MS_DBG(); specifically search for ANBpH( ... ) usages and the
ANBpH constructors to correct the argument order and ensure powerPin2 and
enablePin remain correct.

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes filters (filter.smudgePasswords and
filter.disableDebug) are referenced but not defined for contributors; either
define these git filter drivers in the repo (e.g., provide a .git/config
template or include a setup script that registers filter.smudgePasswords and
filter.disableDebug with appropriate clean/smudge commands) or add a short note
to CONTRIBUTING/README with the exact git config commands contributors must run
to enable them (e.g., git config --local filter.smudgePasswords.clean/... and
filter.disableDebug.clean/...), and optionally add a pre-check script that warns
when these filters are not configured.

In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-118: The loop building non_menu_examples is comparing the full
root path against exclusion names and leaves the os.walk second variable unused;
change the comparison to use the folder basename (e.g., os.path.basename(root))
when checking against exclusions like menu_example_name, and either rename the
unused subdirs parameter to _subdirs or actually use it; update the condition in
the block that checks filename == os.path.split(root)[-1] + ".ino" to derive the
folder name via os.path.basename(root) so filtering works correctly (apply the
same fix to the similar logic in the other occurrence).

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: Replace the non-existent forceModemBaud call: remove
modem.gsmModem.forceModemBaud(modemSerial, static_cast<uint32_t>(modemBaud)) and
instead use TinyGSM's supported baud-flow — either perform an auto-baud step
with TinyGsmAutoBaud (e.g., construct TinyGsmAutoBaud using modemSerial and call
its auto-baud routine) or explicitly set the serial and modem baud by calling
modemSerial.begin(modemBaud) and then
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)); update both
occurrences that call forceModemBaud (the one using modemSerial/modemBaud at the
top and the second occurrence) to use one of these supported approaches.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: Change the possessive-style "UUID's" to the correct
plural "UUIDs" in the sentence that instructs users to paste the UUIDs copied
from the "View Token UUID List" link (the sentence: "be very, very, very careful
to make sure the order of your variables exactly matches the order of your
UUID's."); update that single word to "UUIDs" so the README uses the correct
plural form.

In `@examples/ReadMe.md`:
- Line 126: The sentence "The The EnviroDIY Sensor Station Kit is designed to be
used with the [EnviroDIY Monitoring Station
Kit](https://www.envirodiy.org/product/envirodiy-monitoring-station-kit/)."
contains a duplicated "The"; remove the extra leading "The" so the sentence
begins "The EnviroDIY Sensor Station Kit..." to fix the typo in the ReadMe.md
content.

In `@src/modems/SodaqUBeeU201.h`:
- Line 235: The change to use the concrete type TinyGsmUBLOX in the
SodaqUBeeU201 class is intentional and consistent with the header update; no
code changes required here—just ensure the API breaking change is documented in
release notes and migration guidance (referencing TinyGsmUBLOX and the
SodaqUBeeU201 class) so consumers know how to adapt from the prior
abstract/generic type to the concrete modem-specific type.

In `@src/ModSensorConfig.h`:
- Around line 274-284: In the comment block for the TINY_GSM_YIELD_MS macro in
ModSensorConfig.h, fix the typo by removing the extra space in the sentence "is
set  to prevent" so it reads "is set to prevent"; locate the comment near the
TINY_GSM_YIELD_MS definition and update the documentation text only (no code
logic changes).

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-55: Update the constructor doc for
MonitorMyWatershedPublisher() to state that it initializes default host, path,
and port rather than leaving members unset; specifically mention the defaults
used by the constructor: host "monitormywatershed.org", path
"/api/data-stream/", and port 80, so readers know these are applied when the
object is created with the no-argument constructor.

In `@src/SensorBase.cpp`:
- Around line 373-384: The clearValues() implementation is clearing power/wake
timestamps and status bits which breaks post-wake stabilization; modify
clearValues() to only reset measurement-related fields
(_measurementAttemptsCompleted, _retryAttemptsMade, _millisMeasurementRequested,
_millisMeasurementCompleted) and leave _millisPowerOn and _millisSensorActivated
untouched, and remove POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED,
WAKE_SUCCESSFUL from the clearStatusBits call so that only measurement-related
status bits (MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL) are cleared.

In `@src/sensors/ANBpH.cpp`:
- Around line 152-176: The code currently programs a 0 (continuous) interval
when _loggingIntervalMinutes == 0 even if the sensor is power-cycled (_powerPin
>= 0), which conflicts with power-cycling. Change the logic so that when
_powerPin >= 0 and _loggingIntervalMinutes == 0 you do not call
setIntervalTime(0); instead clamp programmedInterval to a sensible minimum
(e.g., 10) and log that continuous mode is not allowed with power-cycling, then
call _anb_sensor.setIntervalTime(programmedInterval); update any debug messages
accordingly and ensure intervalSet reflects the non-zero interval result (use
symbols: _powerPin, _loggingIntervalMinutes, programmedInterval,
_anb_sensor.setIntervalTime, intervalSet).

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 66-74: The comment block describing ADS library default settings
contains typos: "TI1115" and "TI1015" should be "ADS1115" and "ADS1015". Edit
that comment in the ApogeeSQ212.cpp comment block so each occurrence of TI1115
-> ADS1115 and TI1015 -> ADS1015, keeping the rest of the text (bit depth, mode,
sample rates, gain/range notes) unchanged to accurately document the ADC part
numbers.

In `@src/sensors/AtlasParent.cpp`:
- Around line 174-181: The loop currently treats truncated I2C payloads as
successes; when _i2c->available() == 0 inside the for-loop (in AtlasParent.cpp
where _numReturnedValues, _i2c->available(), parseFloat() and
verifyAndAddMeasurementResult() are used) set success = false and break so the
overall attempt is flagged as a failure and you do not proceed to parse/add
missing results; alternatively, if you must record placeholders, explicitly add
-9999 for the remaining indices via verifyAndAddMeasurementResult before
breaking but do not leave success true.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 199-260: The code currently creates/opens the image file
(imgFile.open / generateFileName) and sets _filename before validating
image_size and transfer success, which can leave empty/partial files and a stale
_filename; change the flow in the routine that uses generateFileName,
initializeSDCard, imgFile, _camera.getImageSize and _camera.transferImage so you
first call _camera.getImageSize and validate image_size != 0, then perform the
transfer into a temporary buffer or a temporary file handle (or open the file
only after successful transfer), only create/open the final SD file
(imgFile.open) and set _filename after bytes_transferred == image_size, and
ensure verifyAndAddMeasurementResult(HYDROCAM_SIZE_VAR_NUM, ...) and
verifyAndAddMeasurementResult(HYDROCAM_ERROR_VAR_NUM, ...) reflect the actual
transfer outcome; also make sure to close any imgFile handles on errors and do
not update _filename on failure.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: The header currently only provides OPERATING_VOLTAGE
fallback; add similar safe default fallbacks for LOGGER_BOARD, BATTERY_PIN, and
BATTERY_MULTIPLIER in KnownProcessors.h so builds don’t fail for unknown boards.
For each symbol (LOGGER_BOARD, BATTERY_PIN, BATTERY_MULTIPLIER) add an `#ifndef`
block that `#defines` a sensible default value and emits a `#pragma` message warning
(like the existing OPERATING_VOLTAGE block) so users are informed; keep the
messages clear and consistent with the OPERATING_VOLTAGE wording. Ensure the new
checks appear after existing processor-specific defines so they only apply when
no board matched.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop in MaxBotixSonar.cpp currently uses a
fixed max attempts (rangeAttempts < 25) and stream blocking that can
cumulatively exceed the allowed measurement budget; update the loop in the
function using rangeAttempts/_stream->parseInt()/_triggerPin so it stops based
on remaining time rather than a hardcoded 25 attempts: capture a start timestamp
(millis()), compute a deadline using HRXL_MEASUREMENT_TIME_MS (or configured
measurement budget), and replace the fixed-condition with a time-based check
that also respects a sensible minimum attempt limit; ensure each iteration
checks if millis() has passed the deadline and breaks early to avoid exceeding
HRXL_MEASUREMENT_TIME_MS.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 58-69: The code unconditionally treats rtc.getTemperature() as
success; validate the temperature (e.g., using std::isnan or isnan) after
calling rtc.getTemperature() and only call
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) and
bumpMeasurementAttemptCount(true) when tempVal is a valid number; if tempVal is
NaN/invalid, log an error (using MS_DBG or process logger), do not add the
measurement, and return bumpMeasurementAttemptCount(false) so the sensor retry
mechanism (in functions like rtc.getTemperature(),
verifyAndAddMeasurementResult(), bumpMeasurementAttemptCount()) behaves
consistently with other sensors (MaximDS18, BoschBMP3xx, AtlasParent).

In `@src/sensors/ProcessorAnalog.h`:
- Around line 1-14: Remove the stray asterisk in the file header comment on the
`@file` line of ProcessorAnalog.h so the line reads "@file ProcessorAnalog.h" (no
trailing "*"); edit the header comment block at the top of ProcessorAnalog.h to
delete that extra character.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 96-97: The counter_internal.Clear() call is unconditional and
wipes pending counts even when the I2C read failed; modify the code so that
counter_internal.Clear() is invoked only after a successful read/parse path
(e.g., after verifying the I2C read returned valid data in the function that
performs the read—look for the method handling the I2C transaction around
counter_internal.Clear()). Move the Clear() into the success branch (or add a
boolean success check) so retries or error handling paths do not lose pending
counts.
- Around line 84-89: The log text in the negative-response branch is inaccurate:
the if (events < 0) check only handles negative values but the MS_DBG message
mentions zeros; either widen the condition to include zero (change to if (events
<= 0)) or change the MS_DBG message to say "negative or bad response" (or
similar) so it matches the existing condition; update the code surrounding
events and the MS_DBG(getSensorNameAndLocation(), F("...")) call in
TallyCounterI2C.cpp accordingly.

In `@src/sensors/TIINA219.cpp`:
- Around line 88-93: The I2C success must be explicitly checked: when computing
success from current_mA, busV_V, power_mW, include ina219_phy.success() (e.g.,
success = ina219_phy.success() && !isnan(...)) so communication failures aren't
missed; also validate the return value of ina219_phy.begin() in both setup() and
wake() (check the boolean returned, handle failure by setting
success/initialized to false and returning/logging an error) to ensure
initialization failures are detected.

In `@src/sensors/YosemitechParent.cpp`:
- Around line 229-230: The conversion for Y520 conductivity should only run when
getValues() succeeded; update the condition guarding the conversion (the line
checking _model == Y520 and !isnan(parmValue)) to also require the existing
success flag used elsewhere (e.g., the same boolean used in the Y4000 branch).
In other words, change the check around the parmValue *= 1000 conversion so it
reads something like: if (_model == Y520 && success && !isnan(parmValue)) to
avoid converting the sentinel -9999 when getValues() failed.

---

Nitpick comments:
In @.coderabbit.yaml:
- Around line 30-33: Update the path_instructions pattern to include .h headers
in addition to .hpp so C++ header files like SequansMonarch.h,
DigiXBee3GBypass.h, and LoggerBase.h are covered; modify the existing entry that
currently reads '**/*.hpp' to include both patterns (for example '**/*.h' and
'**/*.hpp' or a combined glob like '**/*.{h,hpp}') so reviewers will run on both
header extensions.

In `@examples/menu_a_la_carte/menu_a_la_carte.ino`:
- Around line 3796-3798: The existing inline warning comment that tells readers
to "PLEASE REMOVE AUTOBAUDING" is now misleading because the code calls
modem.gsmModem.forceModemBaud(modemSerial, static_cast<uint32_t>(modemBaud));;
update the comment near the PRINTOUT(...) and forceModemBaud invocation to
reflect that the code actively forces the modem baud rate (auto-bauding is being
overridden) and, if relevant, note any conditions or follow-up steps (e.g., when
to remove the forced-baud workaround) so the comment accurately describes the
current behavior of forceModemBaud.

In `@src/modems/SIMComSIM7080.cpp`:
- Around line 50-58: The code in SIMComSIM7080.cpp issues duplicate
gsmModem.sendAT(F("+CNETLIGHT=1")) / gsmModem.waitResponse() calls: remove the
redundant call or replace the second call with the correct AT parameter that
enables netlight GPRS indication per the SIM7080 spec; locate the two
gsmModem.sendAT(...) / gsmModem.waitResponse() pairs and either delete the
duplicate or update the second sendAT to the proper command (consult the SIM7080
AT command reference) so the netlight GPRS behavior is configured correctly.

In `@src/modems/SIMComSIM800.h`:
- Line 81: Replace the direct include of TinyGsmClientSIM800.h with the TinyGSM
macro-based include pattern: define TINY_GSM_MODEM_SIM800 before including
TinyGsmClient.h so the library provides the proper typedefs (TinyGsm for
TinyGsmSim800 and TinyGsmClient for the client class) instead of relying on
TinyGsmClientSIM800.h; update any code referencing TinyGsmClientSIM800.h to rely
on TinyGsm and TinyGsmClient types after making this change.

In `@src/sensors/CampbellOBS3.cpp`:
- Line 56: The debug log MS_DBG(getSensorNameAndLocation(), F("is reporting:"));
is emitted before ADC initialization and readings; move this call so it runs
only after the ADC initialization succeeds (i.e., after the ADC begin/init/check
routine) and immediately before the measurement/read calls, or alternatively
change the message text to something like "is attempting measurement:" to
indicate a start rather than a successful report; update the call referencing
getSensorNameAndLocation() accordingly so logs reflect successful ADC init
before claiming a report.

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 59-84: When sensor_adc == 0 we should not silently record zero
measurements; add a debug/warning log and treat it as a failed reading: in the
branch that currently sets volt_val/current_val/lux_val to 0.0, call your
logging macro (e.g., MS_DBG or project warning macro) to note "ADC=0 possible
sensor/disconnect", skip calling
verifyAndAddMeasurementResult(ALSPT19_VOLTAGE_VAR_NUM/..._CURRENT_VAR_NUM/..._ILLUMINANCE_VAR_NUM)
and return bumpMeasurementAttemptCount(false) instead of true so the failure is
recorded; keep the existing successful path unchanged for nonzero sensor_adc.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 156: The TODO notes a custom internal retry loop in MaxBotixSonar.cpp
that should be unified with the library's new measurement-attempt
infrastructure; replace the bespoke retry logic inside the MaxBotixSonar
measurement method with calls into the shared
bumpMeasurementAttemptCount/measurement workflow used by other sensors (preserve
current retry semantics where needed), remove the internal loop and the TODO,
update the MaxBotixSonar measurement method (e.g., read/measure function in
MaxBotixSonar class) to use the common retry state and failure propagation, and
adjust any tests or callers to rely on the centralized attempt counting.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 147-153: The current PaleoTerraRedox read logic only treats NaN
and a specific zero/i2c/config combination as failures (res, i2c_status, config,
success), leaving other invalid states counted as success; add concrete failure
conditions in the PaleoTerraRedox read/parse path: treat out-of-range values
(e.g., res < MIN_VALID or res > MAX_VALID), known sentinel values,
corrupted-format reads, and repeated identical reads across retry attempts as
failures, and set success = false when any of these checks trigger; implement
these checks where res is computed (inside the function/method that sets res,
i2c_status, config, and success) and document MIN_VALID/MAX_VALID constants for
maintainability.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 99-107: The ADC read uses ads.readADC_SingleEnded(_adsChannel) but
doesn't explicitly check for a failure sentinel (e.g., -1); add an explicit
validation right after calling ads.readADC_SingleEnded by checking adcCounts for
error values and handle them (log via MS_DBG or process logger, skip further
computeVolts/processing, and return/mark sensor read as failed) instead of
relying solely on the subsequent adcVoltage range check; update the block around
ads.readADC_SingleEnded, adcCounts, adcVoltage and computeVolts to perform this
validation and clear, explicit error path.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 170-180: When adcVoltage falls outside the valid ADS1X15 range the
branch silently skips adding results; add a debug log using MS_DBG to record the
out-of-range adcVoltage and the expected range (e.g. "adcVoltage out of range:
<value>, expected -0.3..5.3") just before skipping so you can trace failures in
the calibration code that computes calibResult using _conc_std, _volt_std, and
_volt_blank and calls verifyAndAddMeasurementResult for TURBIDITY_PLUS_VAR_NUM
and TURBIDITY_PLUS_VOLTAGE_VAR_NUM; ensure the log is emitted in the same
conditional logic that currently sets success = true so maintain the existing
behavior of not adding results but now with clear diagnostics.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 312-316: The TODO about automatic gain selection in
TurnerTurbidityPlus.h (the comment near GAIN_DEFAULT and ADS1x15 gain) should be
tracked outside the source to avoid being lost: create a ticket/issue that
describes auto-gain acceptance criteria (triggers, voltage/gain mapping, tests)
and add the issue ID or short URL to the TODO comment in TurnerTurbidityPlus.h
(so the TODO now reads with the issue reference), or alternatively add a
TODO_TRACKING constant or pragma with the issue ID; ensure the referenced
symbols include the ADS1x15 gain setting and GAIN_DEFAULT so reviewers can find
the exact location.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
build-menu-configurations.ps1 (1)

147-152: ⚠️ Potential issue | 🔴 Critical

Critical: Variable name mismatch causes publisher loop to fail.

The array is defined as $publisherFlag (singular) on line 147, but the Foreach loop on line 152 iterates over $publisherFlags (plural), which is undefined. This will cause the publisher configuration builds to silently skip or fail.

🐛 Proposed fix
-$publisherFlag = @(`
+$publisherFlags = @(`
     'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', `
     'BUILD_PUB_DREAM_HOST_PUBLISHER', `
     'BUILD_PUB_THING_SPEAK_PUBLISHER')

Alternatively, change line 152 to use the singular variable name:

-Foreach ($publisherFlag in $publisherFlags)
+Foreach ($flag in $publisherFlag)

(Note: Using a different loop variable name like $flag to avoid shadowing the array variable.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 152, The loop is iterating
over an undefined variable ($publisherFlags) because the array was declared as
$publisherFlag; fix by making the names consistent and avoid shadowing: either
rename the array to $publisherFlags or change the Foreach to iterate like
Foreach ($flag in $publisherFlag) so the loop uses a distinct loop variable;
update references inside the loop accordingly (look for the array declaration
$publisherFlag and the Foreach statement) to ensure the publisher list is
correctly iterated.
src/sensors/EverlightALSPT19.cpp (1)

59-73: ⚠️ Potential issue | 🟠 Major

Inconsistent error handling: Zero ADC reading should use -9999 instead of 0.0.

When sensor_adc == 0, the code sets volt_val, current_val, and lux_val to 0.0 instead of the -9999 error marker used throughout the codebase and in the initialization of these variables (lines 44-46).

The codebase consistently uses -9999 to indicate invalid or error readings across 70+ sensor implementations. This inconsistency is problematic because a zero ADC reading could indicate either complete darkness (valid 0 lux) or a disconnected sensor/power issue (invalid), and there is currently no way to distinguish between them. The code's own comment (lines 79-80) acknowledges the lack of validation criteria. Align with the established pattern by keeping failed readings at -9999.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/EverlightALSPT19.cpp` around lines 59 - 73, When sensor_adc == 0
in the EverlightALSPT19 conversion block, replace the current assignments of 0.0
with the project-wide error marker -9999 so invalid readings match other
sensors; specifically set volt_val, current_val, and lux_val to -9999 in the
branch that handles sensor_adc == 0 (the same block referencing sensor_adc,
volt_val, current_val, lux_val), leaving the conversion calculations in the else
branch unchanged so valid ADC values still compute normally.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

208-219: ⚠️ Potential issue | 🟠 Major

Fix buffer‑fullness threshold ordering.

percent >= 50 runs before >= 75 and >= 90, so the higher‑severity branches are unreachable and the buffer never flushes more aggressively than /2.

🐛 Suggested reorder
-    if (percent >= 50) {
-        interval /= 2;
-    } else if (percent >= 75) {
-        interval /= 4;
-    } else if (percent >= 90) {
-        interval = 1;
-    }
+    if (percent >= 90) {
+        interval = 1;
+    } else if (percent >= 75) {
+        interval /= 4;
+    } else if (percent >= 50) {
+        interval /= 2;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 208 - 219, The
if/else chain in MonitorMyWatershedPublisher::connectionNeeded computes an
interval based on _sendEveryX and _logBuffer.getPercentFull(), but checks
percent >= 50 before >= 75 and >= 90 so the higher thresholds are never reached;
reorder the comparisons to test the highest thresholds first (check percent >=
90, then >= 75, then >= 50) and keep the interval adjustments (/4, /2, =1) tied
to their appropriate branches so the buffer flushes more aggressively as percent
increases.
src/sensors/ANBpH.cpp (1)

314-389: ⚠️ Potential issue | 🟠 Major

Initialize measurement values before use.

If getValues() fails, pH/temp/sal/spcond are uninitialized but logged and used in range checks, which is UB and can flip success. Initialize to a sentinel.

🐛 Suggested initialization
-    float             pH, temp, sal, spcond, raw_cond = -9999;
+    float             pH = -9999, temp = -9999, sal = -9999,
+                      spcond = -9999, raw_cond = -9999;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.cpp` around lines 314 - 389, In addSingleMeasurementResult,
pH, temp, sal and spcond may be used uninitialized if _anb_sensor.getValues()
fails; initialize pH, temp, sal, spcond (and raw_cond already has -9999) to a
clear sentinel (e.g., NAN or the same -9999 sentinel) before calling
_anb_sensor.getValues() so subsequent MS_DBG logging and the range check
(success &= ((0.0 < pH && pH < 14.00) || ...)) do not read garbage; update the
declarations of pH, temp, sal, spcond accordingly in the
ANBpH::addSingleMeasurementResult function.
🧹 Nitpick comments (10)
src/sensors/YosemitechParent.cpp (1)

83-83: Redundant but defensive: pinMode already set in setup().

This pinMode(_RS485EnablePin, OUTPUT) call duplicates line 61 in setup(). While harmless, consider whether this is intentional for scenarios where pin mode might be reset between setup and wake, or if it can be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/YosemitechParent.cpp` at line 83, The pinMode(_RS485EnablePin,
OUTPUT) call at the shown location duplicates the one in setup(); remove the
redundant call to avoid unnecessary repetition and rely on setup() to configure
_RS485EnablePin, or if this is intentionally defensive (to handle pin resets
between setup() and wake), keep it but add a clarifying comment above the call
stating that the extra pinMode is intentional; reference the _RS485EnablePin
symbol and the existing setup() pinMode configuration when making the change.
src/sensors/KellerParent.cpp (1)

89-131: Well-structured measurement flow with proper retry tracking.

The implementation correctly:

  • Exits early when measurement wasn't successfully started
  • Combines the sensor read success with value validity checks
  • Only stores results when all values are confirmed valid
  • Uses bumpMeasurementAttemptCount for consistent retry handling

One minor observation: calcWaterDepthM is called unconditionally at lines 105-108 even when getValues may have failed. This is safe since the validity check at lines 118-120 catches invalid waterDepthM values, but you could avoid the unnecessary calculation:

💡 Optional: Skip depth calculation when values are invalid
     // Get Values
     success     = _ksensor.getValues(waterPressureBar, waterTemperatureC);
-    waterDepthM = _ksensor.calcWaterDepthM(
-        waterPressureBar,
-        waterTemperatureC);  // float calcWaterDepthM(float
-                             // waterPressureBar, float waterTemperatureC)
+    if (success && !isnan(waterPressureBar) && waterPressureBar != -9999 &&
+        !isnan(waterTemperatureC) && waterTemperatureC != -9999) {
+        waterDepthM = _ksensor.calcWaterDepthM(waterPressureBar,
+                                               waterTemperatureC);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KellerParent.cpp` around lines 89 - 131, In
KellerParent::addSingleMeasurementResult, avoid calling _ksensor.calcWaterDepthM
when _ksensor.getValues failed; change the flow so that waterDepthM is only
computed if success (the return from _ksensor.getValues) is true, using the
existing waterPressureBar and waterTemperatureC variables, otherwise leave
waterDepthM as the sentinel and let the existing validity checks and
bumpMeasurementAttemptCount handle the failure; update references around
_ksensor.getValues and _ksensor.calcWaterDepthM to reflect this conditional
computation.
src/sensors/TIADS1x15.cpp (1)

94-102: Add a debug hint for out-of-range ADC voltage.

Right now the out-of-range case silently fails, which makes field diagnostics harder. A short log here would help trace sensor/power issues without changing behavior.

💡 Suggested logging addition
 if (adcVoltage < 3.6 && adcVoltage > -0.3) {
     // Apply the gain calculation, with a default gain of 10 V/V Gain
     calibResult = adcVoltage * _gain;
     MS_DBG(F("  calibResult:"), calibResult);
     verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult);
     success = true;
+} else {
+    MS_DBG(F("  ADC voltage out of expected range:"), adcVoltage);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 94 - 102, The out-of-range ADC
voltage path currently does nothing; add a concise debug log when adcVoltage is
outside the valid range to aid diagnostics without changing behavior. After the
existing if (adcVoltage < 3.6 && adcVoltage > -0.3) block, add an else branch
that calls the existing logging macro (e.g., MS_DBG or similar) to emit a short
message including the adcVoltage value and a note that it is out of range for
TIADS1x15 processing; leave calibResult, _gain, verifyAndAddMeasurementResult,
and TIADS1X15_VAR_NUM logic unchanged.
src/sensors/ApogeeSQ212.cpp (1)

96-105: Close the voltage-range TODO (or track it).
This range gates result creation; confirming it against the sensor spec would remove ambiguity.

If you want, I can draft a small note/issue or propose a validation approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 105, The voltage-range check
for the Apogee SQ-212 sensor (adcVoltage < 3.6 && adcVoltage > -0.3) is a TODO
that gates result creation; confirm the correct input voltage limits from the
SQ-212 datasheet and replace the magic bounds with a named constant (e.g.,
SQ212_VALID_VOLTAGE_MIN/MAX) used in the if-condition inside the SQ-212 handling
block, or if spec confirmation is pending, add a clear comment referencing the
datasheet link and create/attach a tracked issue ID; keep
verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, ...) behavior the same but
ensure the range constants and any units are documented next to the calib
calculation (SQ212_CALIBRATION_FACTOR) so future reviewers can validate or
update them.
src/sensors/PaleoTerraRedox.cpp (1)

147-152: Resolve or track the new failure‑conditions TODO.
Leaving a TODO in the measurement path risks shipping incomplete error handling. Please implement the missing failure conditions or convert this into a tracked issue before release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.cpp` around lines 147 - 152, The TODO indicates
incomplete failure handling in PaleoTerraRedox measurement logic: replace the
placeholder TODO by implementing explicit failure conditions and handling for
res, i2c_status, and config in the read/measure function in PaleoTerraRedox.cpp
(the code that currently sets res = -9999 when isnan(res) or when res == 0 &&
i2c_status == 0 && config == 0). Concretely, define a named sentinel or enum for
sensor errors instead of magic -9999, add checks for I2C errors (i2c_status
nonzero meaning bus errors), invalid config/firmware states, and NaN/overflow
results, and map each to a distinct error code or throw/report via the module’s
error-reporting path (and add a processLogger/LOG call where failures occur). If
you cannot implement full handling now, replace the TODO with a short TODO
comment that references a tracked issue ID (e.g., TODO: track `#ISSUE-NNN`) and
ensure the code still uses the named sentinel and logs the condition so it’s not
silently shipped.
src/sensors/TurnerTurbidityPlus.h (1)

314-316: Acknowledged TODO for future enhancement.

The @todo comment about auto-determining gain from board voltage is noted. This is a reasonable future enhancement.

Would you like me to open an issue to track this enhancement?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.h` around lines 314 - 316, Create a tracked
enhancement for the `@todo` about auto-determining gain from board voltage: open
an issue in the repo (e.g., "Auto-determine TurnerTurbidityPlus gain from board
voltage") and then update the comment in TurnerTurbidityPlus.h to reference the
issue number/URL instead of a bare `@todo` (or replace with a short FIXME with the
issue ID) so the intent is tracked and discoverable; ensure the updated comment
mentions the relevant symbol TurnerTurbidityPlus (class/driver) and the desired
behavior (derive gain from board voltage at runtime).
src/VariableArray.cpp (2)

473-549: Extract the power pin comparison logic to reduce duplication.

The complex power pin comparison condition (checking primary and secondary power pins) is duplicated between the canPowerDown check (lines 476-494) and the debug output block (lines 511-547). Consider extracting this to a helper function or lambda.

♻️ Suggested refactor
+                    // Helper lambda to check if two sensors share a power pin
+                    auto sharesPowerPin = [](Sensor* a, Sensor* b) {
+                        return (a->getPowerPin() >= 0 &&
+                                (a->getPowerPin() == b->getPowerPin() ||
+                                 a->getPowerPin() == b->getSecondaryPowerPin())) ||
+                               (a->getSecondaryPowerPin() >= 0 &&
+                                (a->getSecondaryPowerPin() == b->getPowerPin() ||
+                                 a->getSecondaryPowerPin() == b->getSecondaryPowerPin()));
+                    };
+
                     bool canPowerDown = true;
                     for (uint8_t k = 0; k < _sensorCount; k++) {
-                        if (((sensorList[i]->getPowerPin() >= 0 &&
-                              ...
+                        if (sharesPowerPin(sensorList[i], sensorList[k]) &&
+                            (sensorList[k]->getNumberCompleteMeasurementsAttempts() <
+                             sensorList[k]->getNumberMeasurementsToAverage())) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 473 - 549, Duplicate complex power-pin
comparison logic should be extracted into a single helper to avoid duplication;
create a small boolean helper (e.g., a static method or local lambda named
sharesPowerPin) that takes two Sensor* (or references) and returns true when
either getPowerPin or getSecondaryPowerPin of the first equals the other's
primary or secondary pin (respecting negative pin checks), then replace the
duplicated condition in the canPowerDown loop and the debug loop with calls to
sharesPowerPin(sensorList[i], sensorList[k]) and update the surrounding checks
that use
getNumberCompleteMeasurementsAttempts()/getNumberMeasurementsToAverage()
unchanged.

353-371: Consider resetting measurement state when wake fails.

When wake fails, the code sets _measurementAttemptsCompleted = _measurementsToAverage to skip the sensor. However, the comment at lines 367-368 notes these are protected members accessed via friend access. This approach works but relies on internal knowledge of the Sensor class.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 353 - 371, The code directly modifies
protected members (_measurementAttemptsCompleted and _measurementsToAverage) of
Sensor via friend access when wake fails; instead add a Sensor public helper
(e.g., Sensor::markMeasurementsComplete() or
Sensor::skipRemainingMeasurements()) that encapsulates setting
_measurementAttemptsCompleted = _measurementsToAverage and any related status
bits, then call sensorList[i]->skipRemainingMeasurements() (or the chosen name)
in VariableArray.cpp where you currently assign those members; also ensure any
status bits (Sensor::WAKE_ATTEMPTED / Sensor::WAKE_SUCCESSFUL) are updated
consistently inside that new Sensor method.
ChangeLog.md (1)

73-79: Consider consolidating the repeated “Removed …” bullets.
Minor readability polish for the Removed section.

✏️ Possible consolidation
-- Removed references to the EnviroDIY data portal.
-- Removed all defines from examples sketches.
-- Defining values to be used by TinyGSM and/or the MQTT library here in addition to any defines in the 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.
+- Removed references to the EnviroDIY data portal and example-level TinyGSM/MQTT defines; keep those in ModSensorConfig/build flags to avoid ODR issues during template instantiation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ChangeLog.md` around lines 73 - 79, Consolidate the repeated "Removed ..."
bullets in ChangeLog.md into a single succinct bullet summarizing all removals
(e.g., "Removed unused parameters, unnecessary doc/copy calls, redundant
powerUp/powerDown overrides, references to the EnviroDIY data portal, and
example defines"), preserving the separate paragraph about TinyGSM/MQTT define
issues; update the bullet list so it reads as one consolidated "Removed ..."
entry and remove redundant lines while keeping the final sentence about One
Definition Rule violations unchanged.
src/sensors/ANBpH.h (1)

470-505: Validate loggingIntervalMinutes at runtime to fail fast.
Docs require 10–240 minutes; consider clamping or warning in the constructors to prevent invalid configuration.

🛡️ Example guard (apply to both constructors)
 ANBpH::ANBpH(byte modbusAddress, Stream* stream, int8_t powerPin,
              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_2ND_VALUE_LOW_SALT, powerPin,
              -1, measurementsToAverage, ANB_PH_INC_CALC_VARIABLES),
       _anb_sensor(moduleAddress, stream, enablePin),
       _modbusAddress(modbusAddress),
       _stream(stream),
       _loggingIntervalMinutes(loggingIntervalMinutes),
       _RS485EnablePin(enablePin) {
+    if (_loggingIntervalMinutes < 10 || _loggingIntervalMinutes > 240) {
+        MS_DBG(F("ANBpH logging interval out of range; clamping to 10–240 min"));
+        _loggingIntervalMinutes =
+            (_loggingIntervalMinutes < 10) ? 10 : 240;
+    }
 `#ifdef` MS_ANB_SENSORS_PH_DEBUG_DEEP
     _anb_sensor.setDebugStream(&MS_SERIAL_OUTPUT);
 `#endif`
     setSecondaryPowerPin(powerPin2);
     setAllowedMeasurementRetries(5);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 470 - 505, The constructors for ANBpH
(ANBpH(byte, Stream*, int8_t, int16_t, int8_t, int8_t, uint8_t) and its
overload) currently accept loggingIntervalMinutes without validation; add a
runtime guard in both constructors to validate that loggingIntervalMinutes is
between 10 and 240 (inclusive) and fail fast if not—either clamp to the valid
range or (preferably) assert/throw/return an error (consistent with project
error-handling patterns) with a clear message including the invalid value and
the constructor name (ANBpH) so misconfiguration is detected immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md`:
- Line 5: Update the README sentence that currently reads "This examples also
makes use of the on-board light, temperature, and humidity sensors on the Mayfly
1.x." to correct the grammar to "This example also makes use of the on-board
light, temperature, and humidity sensors on the Mayfly 1.x." — locate and edit
that exact phrase in ReadMe.md for the DRWI_Mayfly1 example.

In `@src/LoggerBase.h`:
- Around line 235-245: Add deprecated, backward-compatible wrapper functions
that forward the old short-interval API names to the new implementations:
provide an inline wrapper for the getter getInitialShortIntervals() that returns
the same value by calling the new getter (or directly returning the underlying
storage _remainingShortIntervals if the new API is a setter/getter pair), and a
wrapper for setInitialShortIntervals(int16_t) that calls the renamed setter;
mark both wrappers with a deprecation attribute (e.g., [[deprecated("use
<newName> instead")]] or MACRO_DEPRECATED) and add a brief comment noting they
exist for compatibility so downstream code continues to build while clients
migrate.

In `@src/modems/EspressifESP32.h`:
- Line 83: The include for a non-existent TinyGSM header should be corrected:
replace the incorrect include "TinyGsmClientESP32.h" in the EspressifESP32.h
file with the official TinyGSM header used for ESP AT support (e.g.,
"TinyGsmClientESP8266.h") or adjust to the header that matches the pinned
TinyGSM version; update the include statement where TinyGsmClientESP32.h appears
so the project uses the correct TinyGSM header for ESP32 support.

In `@src/sensors/BoschBME280.cpp`:
- Around line 146-156: The code marks success = true unconditionally after
calling verifyAndAddMeasurementResult, which lets attempts with sentinel/invalid
readings (e.g., NaN converted to -9999) count as successful and suppress
retries; change the logic so success is only set when all readings are valid —
either by checking that verifyAndAddMeasurementResult(...) returned true for
each of BME280_TEMP_VAR_NUM, BME280_HUMIDITY_VAR_NUM, BME280_PRESSURE_VAR_NUM
and BME280_ALTITUDE_VAR_NUM and only then set success = true, or by explicitly
validating temp/press/humid/alt are not NaN and not equal to the invalid
sentinel (e.g., -9999) before setting success.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 107-115: The temp_event and humidity_event structs are read into
temp_val and humid_val even if sht4x_internal.getEvent(...) fails, causing
potential undefined reads; fix by zero-initializing sensors_event_t temp_event
and humidity_event at declaration or by only assigning temp_val and humid_val
after ret_val indicates success from getEvent, e.g., initialize
temp_event/humidity_event to {} (or call memset) and/or move the temp_val =
temp_event.temperature and humid_val = humidity_event.relative_humidity
assignments into the success branch that checks ret_val from
sht4x_internal.getEvent.

---

Outside diff comments:
In `@build-menu-configurations.ps1`:
- Around line 147-152: The loop is iterating over an undefined variable
($publisherFlags) because the array was declared as $publisherFlag; fix by
making the names consistent and avoid shadowing: either rename the array to
$publisherFlags or change the Foreach to iterate like Foreach ($flag in
$publisherFlag) so the loop uses a distinct loop variable; update references
inside the loop accordingly (look for the array declaration $publisherFlag and
the Foreach statement) to ensure the publisher list is correctly iterated.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 208-219: The if/else chain in
MonitorMyWatershedPublisher::connectionNeeded computes an interval based on
_sendEveryX and _logBuffer.getPercentFull(), but checks percent >= 50 before >=
75 and >= 90 so the higher thresholds are never reached; reorder the comparisons
to test the highest thresholds first (check percent >= 90, then >= 75, then >=
50) and keep the interval adjustments (/4, /2, =1) tied to their appropriate
branches so the buffer flushes more aggressively as percent increases.

In `@src/sensors/ANBpH.cpp`:
- Around line 314-389: In addSingleMeasurementResult, pH, temp, sal and spcond
may be used uninitialized if _anb_sensor.getValues() fails; initialize pH, temp,
sal, spcond (and raw_cond already has -9999) to a clear sentinel (e.g., NAN or
the same -9999 sentinel) before calling _anb_sensor.getValues() so subsequent
MS_DBG logging and the range check (success &= ((0.0 < pH && pH < 14.00) ||
...)) do not read garbage; update the declarations of pH, temp, sal, spcond
accordingly in the ANBpH::addSingleMeasurementResult function.

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 59-73: When sensor_adc == 0 in the EverlightALSPT19 conversion
block, replace the current assignments of 0.0 with the project-wide error marker
-9999 so invalid readings match other sensors; specifically set volt_val,
current_val, and lux_val to -9999 in the branch that handles sensor_adc == 0
(the same block referencing sensor_adc, volt_val, current_val, lux_val), leaving
the conversion calculations in the else branch unchanged so valid ADC values
still compute normally.

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes references two Git filter drivers
(smudgePasswords and disableDebug) that are not defined; either define them or
remove the entries. Fix by adding Git config entries for
filter.smudgePasswords.clean and filter.smudgePasswords.smudge and for
filter.disableDebug.clean and filter.disableDebug.smudge (e.g., via git config
--local or documenting required settings) to implement the intended behavior, or
delete the corresponding attribute lines if filtering is not desired; ensure you
reference the exact filter names smudgePasswords and disableDebug when updating
configuration or documentation.

In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-118: The os.walk loop building non_menu_examples wrongly
compares the full path variable root to folder names (archive, tests, etc.) and
also leaves the subdirs loop variable unused; change comparisons to use the
directory basename (e.g., os.path.basename(root)) when checking against the
exclusion list and remove or rename the unused subdirs variable (use _ if
unused) to avoid lint issues; update the conditional that checks filename ==
os.path.split(root)[-1] and the exclusion check to use the basename variable,
and apply the same basename-based fix to the similar block around the other
occurrence (the block at 316-322).

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to the nonexistent API
modem.gsmModem.forceModemBaud(...) should be removed and replaced with a
supported approach: after establishing AT sync with the modem (where you
currently interact with modem.gsmModem and modemSerial), call
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) to set the baud, or for
initial autobaud detection use TinyGsmAutoBaud(modemSerial, minBaud, maxBaud)
before AT sync; update the code paths that reference forceModemBaud to use
setBaud or TinyGsmAutoBaud with modemSerial and modemBaud accordingly.
- Around line 496-498: The call to modem.gsmModem.forceModemBaud(...) is invalid
for the cellular modem path; remove that call and instead set the hardware
serial port baud directly (e.g., call the modemSerial begin/update-baud method
with static_cast<uint32_t>(modemBaud)) or simply skip forcing the modem baud and
log that forcing is not supported for gsmModem; update the code around symbols
modem.gsmModem, forceModemBaud, modemSerial, and modemBaud accordingly so the
cellular path does not call the non-existent API.

In `@examples/examples.dox`:
- Around line 67-70: The cross-reference uses the old anchor name
`examples_outdated` in the `@ref` call and must be updated to the renamed anchor
`page_examples_outdated`; edit the `@ref examples_outdated` occurrence so it
reads `@ref page_examples_outdated` (ensure the `@subpage`/`@page` declarations
remain `page_examples_outdated`) to restore the Doxygen link.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: Replace the incorrect possessive plural "UUID's" with
the correct plural "UUIDs" in the ReadMe sentence that references the "View
Token UUID List" and the phrase "paste the UUID's all together" so the line
reads using "UUIDs" consistently (e.g., "paste the UUIDs all together" and
"order of your variables exactly matches the order of your UUIDs").

In `@examples/ReadMe.md`:
- Line 126: Fix the duplicated word in the kit description by removing the extra
leading "The" so the sentence reads "The EnviroDIY Sensor Station Kit is
designed to be used with the EnviroDIY Monitoring Station Kit." Locate the
sentence containing "The The EnviroDIY Sensor Station Kit..." in
examples/ReadMe.md (the line shown in the diff) and delete the second "The" to
eliminate the repetition.

In `@src/modems/EspressifESP32.h`:
- Line 162: The declaration changed gsmModem from a generic TinyGsm type to the
concrete TinyGsmESP32 which can break consumers that expect the generic
interface; restore compatibility by adding a compatibility accessor on the
EspressifESP32 class (e.g., TinyGsm& getGsmModem() or TinyGsm* getGsmModemPtr())
that returns the gsmModem instance as the generic TinyGsm type, or alternatively
provide a typedef/using alias mapping TinyGsmType to TinyGsmESP32 and keep a
member named gsmModem of the generic alias; update the class to expose
getGsmModem() (or the alias) so downstream code using gsmModem as a TinyGsm
continues to compile without changing call sites.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The include and types referencing
TinyGsmClientSIM7000SSL/TinyGsmSim7000SSL are invalid; change to the standard
TinyGSM pattern: remove TinyGsmClientSIM7000SSL.h and instead `#define`
TINY_GSM_MODEM_SIM7000SSL before including "TinyGsmClient.h", and use the
standard client and modem types (e.g. TinyGsmClient and the TinyGsm modem
instance) rather than TinyGsmSim7000SSL; update any uses of
TinyGsmSim7000SSL/TinyGsmClientSIM7000SSL to the canonical TinyGsm/TinyGsmClient
names (and ensure the TINY_GSM_MODEM_SIM7000SSL define is present so SSL support
is enabled).

In `@src/ModSensorConfig.h`:
- Around line 274-285: In the TINY_GSM_YIELD_MS documentation comment (the block
above the TINY_GSM_YIELD_MS `#define`), fix the typographical double space in the
sentence "is set  to prevent" by removing the extra space so it reads "is set to
prevent"; locate the comment block that mentions TinyGSM and the yield
milliseconds (around the comment preceding the TINY_GSM_YIELD_MS macro) and
update that sentence only.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-55: The constructor documentation for
MonitorMyWatershedPublisher incorrectly claims it constructs an object "with no
members set" while the MonitorMyWatershedPublisher() implementation initializes
default host/path/port; update the doc comment on the
MonitorMyWatershedPublisher() declaration to state that it initializes the
object with default host, path, and port values (or list the actual defaults)
instead of saying no members are set so it matches the constructor behavior.

In `@src/SensorBase.cpp`:
- Around line 373-384: clearValues() is resetting power/wake timing and status
bits which causes waitForStability() to see zeroed
_millisPowerOn/_millisSensorActivated and return immediately; modify
clearValues() so it does not reset _millisPowerOn and _millisSensorActivated and
does not clear the POWER_ATTEMPTED, POWER_SUCCESSFUL, WAKE_ATTEMPTED,
WAKE_SUCCESSFUL status bits (or add an optional parameter like
preservePowerWakeTiming) so that update() can call clearValues() after wake()
without breaking stabilization timing used by waitForStability().

In `@src/sensors/ANBpH.cpp`:
- Around line 152-176: The code allows a zero logging interval to be used while
the sensor is power-cycled which results in continuous scanning despite power
control; update the logic around _loggingIntervalMinutes/_powerPin so that when
_powerPin >= 0 and _loggingIntervalMinutes == 0 you do not leave
programmedInterval as 0 — either set programmedInterval = 10 (the minimum) or
return a setup failure; specifically modify the block that computes
programmedInterval (and before calling _anb_sensor.setIntervalTime) to clamp
zero to the minimum (e.g., programmedInterval = 10) when _powerPin >= 0, then
call _anb_sensor.setIntervalTime(programmedInterval) and set intervalSet
accordingly.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 62-74: Comments in the ADS ADC block use incorrect part-number
prefixes ("TI1115"/"TI1015"); update the comment text to the correct model names
ADS1115 and ADS1015 and ensure any surrounding explanatory lines reference the
ADS part numbers consistently (the block around the
Adafruit_ADS1115/Adafruit_ADS1015 declarations and the "ADS Library default
settings" section that mentions TI1115/TI1015 should be changed to
ADS1115/ADS1015 and keep the rest of the settings text intact).

In `@src/sensors/AtlasParent.cpp`:
- Around line 174-183: The code currently breaks out of the value-parsing loop
when _i2c->available() == 0 but leaves success true, so
bumpMeasurementAttemptCount(success) will treat an incomplete read as
successful; update the loop in AtlasParent.cpp so that if _i2c->available() == 0
(or any early-exit condition) you set success = false before breaking to mark
the attempt as failed; keep verifyAndAddMeasurementResult(i, result) behavior
unchanged for already-parsed values and ensure
bumpMeasurementAttemptCount(success) receives the corrected success flag.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 199-261: The code currently creates the SD file via
imgFile.open(...) before verifying the captured image size and sets _filename
before confirming a successful transfer, which allows empty or incomplete files
to remain on the card; fix by first calling _camera.getImageSize() and
validating image_size != 0 before opening/creating the file (or if you must open
earlier, delete/unlink the file when image_size == 0), only perform
imgFile.open(...) once image_size is valid, and only assign _filename after
bytes_transferred == image_size (i.e. after a successful
_camera.transferImage(...) and imgFile.close()); ensure any opened imgFile is
closed and removed on failure to avoid leaving empty files.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: Add fallback defines for LOGGER_BOARD, BATTERY_PIN, and
BATTERY_MULTIPLIER next to the existing OPERATING_VOLTAGE fallback in
KnownProcessors.h: wrap each in an `#ifndef` ... `#define` ... `#endif` with a `#pragma`
message analogous to the OPERATING_VOLTAGE block. Use safe neutral defaults (for
example LOGGER_BOARD = 0, BATTERY_PIN = a sentinel like -1 or an invalid pin
constant, and BATTERY_MULTIPLIER = 1.0) so code that references LOGGER_BOARD,
BATTERY_PIN, or BATTERY_MULTIPLIER will compile for unsupported boards and the
pragma message warns the user to provide board-specific values.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 170-186: The current code casts _stream->parseInt() directly to
uint16_t (result) which can wrap negative/garbage values before validation;
change the sequence in the MaxBotixSonar read flow to first read parseInt() into
a signed wider type (e.g., long or int32_t), call _stream->read() as before,
validate that the parsed value is >0 and < _maxRange, and only after validation
assign/convert to the sensor result variable (int16_t or uint16_t as
appropriate); if validation fails set result to -9999 and increment
rangeAttempts exactly as done now, keeping MS_DBG messages and checks that
reference _stream, parseInt, _maxRange, and rangeAttempts.
- Around line 154-196: The inner retry loop currently uses a fixed rangeAttempts
< 25 which can block far longer than HRXL_MEASUREMENT_TIME_MS; change it to
honor the measurement time budget by computing a maxAttempts from the stream
timeout (or using an elapsed-time guard). For example, capture a start timestamp
(millis()) before the loop and replace the condition with something like while
(!success && rangeAttempts < maxAttempts && (millis() - start) <
HRXL_MEASUREMENT_TIME_MS), where maxAttempts is derived from
HRXL_MEASUREMENT_TIME_MS / streamTimeout (or a safe cap), so functions
referencing rangeAttempts, _stream->parseInt(), result handling,
verifyAndAddMeasurementResult(HRXL_VAR_NUM, result), and
bumpMeasurementAttemptCount(success) remain unchanged but the loop will never
exceed the overall time budget.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 57-68: The temperature read can be NaN and we must not mark the
attempt successful in that case: in MaximDS3231::addSingleMeasurementResult
after calling rtc.getTemperature(), check for isnan(tempVal) (or equivalent) and
if it is NaN do NOT call verifyAndAddMeasurementResult or
bumpMeasurementAttemptCount(true); instead log the failure and return
bumpMeasurementAttemptCount(false) so the attempt is retried; only when tempVal
is a valid number call verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM,
tempVal) and then return bumpMeasurementAttemptCount(true).

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 37-39: The code currently computes volts without checking
_operatingVoltage; ensure you validate that _operatingVoltage > 0 before using
it to compute voltage and treat the measurement as failed if not. Locate the
validation block that checks _dataPin and _voltageMultiplier (the same area that
calls MS_DBG and bumpMeasurementAttemptCount(false)) and add a similar guard for
_operatingVoltage <= 0 so you call bumpMeasurementAttemptCount(false) and return
early instead of recording a successful measurement; reference the
_operatingVoltage, _voltageMultiplier, and bumpMeasurementAttemptCount symbols
when making the change.

In `@src/sensors/ProcessorAnalog.h`:
- Around line 1-3: The file header comment in ProcessorAnalog.h has a stray
asterisk in the `@file` tag; edit the top comment block (the `@file` line) to remove
the extra '*' so it reads the filename cleanly (i.e., fix the `@file`
ProcessorAnalog.h line in the header comment).

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 103-121: The code must validate that _i2c->requestFrom actually
returned exactly 1 or 4 bytes before proceeding; capture its return value, store
it (e.g., into a bytesRead variable), and reject any count that is not 1 or 4 by
logging with getSensorNameAndLocation()/MS_DBG and returning
bumpMeasurementAttemptCount(false). Update the read loop that fills SerialBuffer
and the byte_in usage to rely on the captured bytesRead (and still guard byte_in
< 4), so partial reads of 2–3 bytes are treated as errors and do not reach the
version detection logic.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 86-89: The log message in TallyCounterI2C.cpp is misleading: the
branch checks for events < 0 but the MS_DBG message says "returns all values 0
or bad"; update either the condition or the message. Either change the if
condition around events to "events <= 0" if you intend to treat zero as
non-response, or revise the MS_DBG text to mention only negative/bad values
(e.g., "returns negative/bad, assuming sensor non-response") so the message
matches the check; adjust the code around the events variable, MS_DBG call, and
getSensorNameAndLocation usage accordingly.
- Around line 96-97: The current code unconditionally calls
counter_internal.Clear() even when a sensor read fails, which can drop
unreported events; modify the logic so counter_internal.Clear() is only invoked
after a successful read/ack path. Locate the read sequence that calls
counter_internal.Clear() (the Clear call shown as counter_internal.Clear()) and
move it into the branch that executes on successful read/processing (e.g., after
verifying read success/ack and before returning success), and ensure any error
paths return/propagate failure without clearing the internal counter so retries
can see the pending events.

In `@src/sensors/TIINA219.cpp`:
- Around line 75-107: Reads from INA219 are only guarded by isnan(), which can
let stale values through on I2C failures; check the sensor library's success
flag after performing the reads and handle failed reads as measurement failures.
Modify the measurement flow in the TIINA219 logic: after calling
ina219_phy.getCurrent_mA(), ina219_phy.getBusVoltage_V(), and
ina219_phy.getPower_mW(), call ina219_phy.success() and treat success =
ina219_phy.success() && !isnan(...) (or replace the isnan checks with
ina219_phy.success() if the library covers all reads); if success is false, do
not call verifyAndAddMeasurementResult(...) and return
bumpMeasurementAttemptCount(false). Also ensure the INA219 is validated at
init/wake by checking ina219_phy.begin() (or its equivalent) and failing early
if begin() reports failure so sensors with failed init are not treated as
working.

In `@src/sensors/YosemitechParent.cpp`:
- Line 230: The conversion for Y520 should only run when getValues() succeeded;
update the conditional that multiplies parmValue by 1000 to also check the
boolean success flag (i.e., use _model == Y520 && success && !isnan(parmValue))
so the sentinel -9999 isn't converted when getValues() fails; locate this in
YosemitechParent.cpp near where parmValue, _model, Y520 and the success variable
from getValues() are used.

---

Nitpick comments:
In `@ChangeLog.md`:
- Around line 73-79: Consolidate the repeated "Removed ..." bullets in
ChangeLog.md into a single succinct bullet summarizing all removals (e.g.,
"Removed unused parameters, unnecessary doc/copy calls, redundant
powerUp/powerDown overrides, references to the EnviroDIY data portal, and
example defines"), preserving the separate paragraph about TinyGSM/MQTT define
issues; update the bullet list so it reads as one consolidated "Removed ..."
entry and remove redundant lines while keeping the final sentence about One
Definition Rule violations unchanged.

In `@src/sensors/ANBpH.h`:
- Around line 470-505: The constructors for ANBpH (ANBpH(byte, Stream*, int8_t,
int16_t, int8_t, int8_t, uint8_t) and its overload) currently accept
loggingIntervalMinutes without validation; add a runtime guard in both
constructors to validate that loggingIntervalMinutes is between 10 and 240
(inclusive) and fail fast if not—either clamp to the valid range or (preferably)
assert/throw/return an error (consistent with project error-handling patterns)
with a clear message including the invalid value and the constructor name
(ANBpH) so misconfiguration is detected immediately.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-105: The voltage-range check for the Apogee SQ-212 sensor
(adcVoltage < 3.6 && adcVoltage > -0.3) is a TODO that gates result creation;
confirm the correct input voltage limits from the SQ-212 datasheet and replace
the magic bounds with a named constant (e.g., SQ212_VALID_VOLTAGE_MIN/MAX) used
in the if-condition inside the SQ-212 handling block, or if spec confirmation is
pending, add a clear comment referencing the datasheet link and create/attach a
tracked issue ID; keep verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, ...)
behavior the same but ensure the range constants and any units are documented
next to the calib calculation (SQ212_CALIBRATION_FACTOR) so future reviewers can
validate or update them.

In `@src/sensors/KellerParent.cpp`:
- Around line 89-131: In KellerParent::addSingleMeasurementResult, avoid calling
_ksensor.calcWaterDepthM when _ksensor.getValues failed; change the flow so that
waterDepthM is only computed if success (the return from _ksensor.getValues) is
true, using the existing waterPressureBar and waterTemperatureC variables,
otherwise leave waterDepthM as the sentinel and let the existing validity checks
and bumpMeasurementAttemptCount handle the failure; update references around
_ksensor.getValues and _ksensor.calcWaterDepthM to reflect this conditional
computation.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 147-152: The TODO indicates incomplete failure handling in
PaleoTerraRedox measurement logic: replace the placeholder TODO by implementing
explicit failure conditions and handling for res, i2c_status, and config in the
read/measure function in PaleoTerraRedox.cpp (the code that currently sets res =
-9999 when isnan(res) or when res == 0 && i2c_status == 0 && config == 0).
Concretely, define a named sentinel or enum for sensor errors instead of magic
-9999, add checks for I2C errors (i2c_status nonzero meaning bus errors),
invalid config/firmware states, and NaN/overflow results, and map each to a
distinct error code or throw/report via the module’s error-reporting path (and
add a processLogger/LOG call where failures occur). If you cannot implement full
handling now, replace the TODO with a short TODO comment that references a
tracked issue ID (e.g., TODO: track `#ISSUE-NNN`) and ensure the code still uses
the named sentinel and logs the condition so it’s not silently shipped.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 94-102: The out-of-range ADC voltage path currently does nothing;
add a concise debug log when adcVoltage is outside the valid range to aid
diagnostics without changing behavior. After the existing if (adcVoltage < 3.6
&& adcVoltage > -0.3) block, add an else branch that calls the existing logging
macro (e.g., MS_DBG or similar) to emit a short message including the adcVoltage
value and a note that it is out of range for TIADS1x15 processing; leave
calibResult, _gain, verifyAndAddMeasurementResult, and TIADS1X15_VAR_NUM logic
unchanged.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 314-316: Create a tracked enhancement for the `@todo` about
auto-determining gain from board voltage: open an issue in the repo (e.g.,
"Auto-determine TurnerTurbidityPlus gain from board voltage") and then update
the comment in TurnerTurbidityPlus.h to reference the issue number/URL instead
of a bare `@todo` (or replace with a short FIXME with the issue ID) so the intent
is tracked and discoverable; ensure the updated comment mentions the relevant
symbol TurnerTurbidityPlus (class/driver) and the desired behavior (derive gain
from board voltage at runtime).

In `@src/sensors/YosemitechParent.cpp`:
- Line 83: The pinMode(_RS485EnablePin, OUTPUT) call at the shown location
duplicates the one in setup(); remove the redundant call to avoid unnecessary
repetition and rely on setup() to configure _RS485EnablePin, or if this is
intentionally defensive (to handle pin resets between setup() and wake), keep it
but add a clarifying comment above the call stating that the extra pinMode is
intentional; reference the _RS485EnablePin symbol and the existing setup()
pinMode configuration when making the change.

In `@src/VariableArray.cpp`:
- Around line 473-549: Duplicate complex power-pin comparison logic should be
extracted into a single helper to avoid duplication; create a small boolean
helper (e.g., a static method or local lambda named sharesPowerPin) that takes
two Sensor* (or references) and returns true when either getPowerPin or
getSecondaryPowerPin of the first equals the other's primary or secondary pin
(respecting negative pin checks), then replace the duplicated condition in the
canPowerDown loop and the debug loop with calls to sharesPowerPin(sensorList[i],
sensorList[k]) and update the surrounding checks that use
getNumberCompleteMeasurementsAttempts()/getNumberMeasurementsToAverage()
unchanged.
- Around line 353-371: The code directly modifies protected members
(_measurementAttemptsCompleted and _measurementsToAverage) of Sensor via friend
access when wake fails; instead add a Sensor public helper (e.g.,
Sensor::markMeasurementsComplete() or Sensor::skipRemainingMeasurements()) that
encapsulates setting _measurementAttemptsCompleted = _measurementsToAverage and
any related status bits, then call sensorList[i]->skipRemainingMeasurements()
(or the chosen name) in VariableArray.cpp where you currently assign those
members; also ensure any status bits (Sensor::WAKE_ATTEMPTED /
Sensor::WAKE_SUCCESSFUL) are updated consistently inside that new Sensor method.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/sensors/GroPointParent.h (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Incorrect file extension in Doxygen @file tag.

The header comment declares @file GroPointParent.cpp but this is a header file (.h).

📝 Proposed fix
-* `@file` GroPointParent.cpp
+* `@file` GroPointParent.h
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` at line 2, The Doxygen header in
GroPointParent.h incorrectly uses "@file GroPointParent.cpp"; update the `@file`
tag to "@file GroPointParent.h" in the top-of-file comment so the documented
filename matches this header (search for the Doxygen block in GroPointParent.h
and replace the .cpp extension with .h).
build-menu-configurations.ps1 (1)

147-152: ⚠️ Potential issue | 🟠 Major

Fix publisher flags array name to avoid skipping publisher builds.

Line 147 defines $publisherFlag, but the loop iterates $publisherFlags, so the loop never runs and publisher builds are skipped. Rename the array to match the foreach variable.

🛠️ Proposed fix
-$publisherFlag = @(`
+$publisherFlags = @(`
     'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', `
     'BUILD_PUB_DREAM_HOST_PUBLISHER', `
     'BUILD_PUB_THING_SPEAK_PUBLISHER')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 152, The array of publisher
flag names is declared as $publisherFlag but the loop uses $publisherFlags,
causing the Foreach ($publisherFlag in $publisherFlags) to never iterate; rename
the declared variable to $publisherFlags (or change the loop to use
$publisherFlag) so the array identifier matches the Foreach, ensuring the
Foreach over publisher flags runs and publisher builds are not skipped—update
the declaration that currently reads $publisherFlag = @(...) to $publisherFlags
= @(...) to match the Foreach usage.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

214-219: ⚠️ Potential issue | 🟠 Major

connectionNeeded threshold order blocks 75%/90% handling.

Line 214 checks >= 50 first, so 75% and 90% never reach the stricter branches. Reorder from highest to lowest.

🛠️ Proposed fix
-    if (percent >= 50) {
-        interval /= 2;
-    } else if (percent >= 75) {
-        interval /= 4;
-    } else if (percent >= 90) {
-        interval = 1;
-    }
+    if (percent >= 90) {
+        interval = 1;
+    } else if (percent >= 75) {
+        interval /= 4;
+    } else if (percent >= 50) {
+        interval /= 2;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 214 - 219, The
branching for adjusting interval based on percent is ordered incorrectly so
percent values >=75 and >=90 never reach their branches; in the function/block
that updates interval (variables percent and interval, currently checking if
(percent >= 50) then else if (percent >= 75) then else if (percent >= 90)),
reorder the comparisons from highest to lowest (check percent >= 90 first, then
>= 75, then >= 50) so the stricter thresholds are applied correctly.
🧹 Nitpick comments (9)
docs/Getting-Started/Getting-Started.md (1)

144-145: Consider linking “Monitor My Watershed” to its docs or example README.

This helps users quickly find the publishing setup details referenced in the example description.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/Getting-Started/Getting-Started.md` around lines 144 - 145, Update the
paragraph that mentions "Monitor My Watershed" to include a direct link to its
documentation or example README so readers can easily find the publishing/setup
details; locate the string "Monitor My Watershed" in the Getting-Started content
and replace it with a Markdown link pointing to the project's docs or the
example README (use the appropriate relative path if the docs live in-repo or
the canonical external URL if hosted), and ensure the link text remains "Monitor
My Watershed" for clarity.
src/sensors/PaleoTerraRedox.h (1)

127-131: Document the rationale for the 300ms conversion wait.

A brief note (datasheet reference or empirical basis) will help future maintainers keep this aligned with the actual conversion timing and avoid drift relative to PTR_MEASUREMENT_TIME_MS.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/PaleoTerraRedox.h` around lines 127 - 131, Add a short
explanatory comment next to PTR_CONVERSION_WAIT_TIME_MS (and referencing
PTR_MEASUREMENT_TIME_MS) that documents why the 300 ms wait was chosen — e.g.,
cite the PaleoTerra sensor datasheet conversion time spec (include section or
page if available) or note that 300 ms was selected as a conservative empirical
margin over the 67 ms nominal measurement time based on bench testing; update
the comment block near the macro definitions (PTR_CONVERSION_WAIT_TIME_MS and
PTR_MEASUREMENT_TIME_MS) to include that reference and the rationale so future
maintainers understand the origin and can adjust if the datasheet or measured
timings change.
src/sensors/MeaSpecMS5803.cpp (1)

63-86: Consider using epsilon comparison for pressure validation.

Line 79 uses direct floating-point equality (press != 0.0). While comparing to exactly 0.0 is generally safe since zero has an exact IEEE 754 representation, a more defensive approach would check for values near zero, in case the sensor returns a very small non-zero value when disconnected:

-    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
-        press != 0.0) {
+    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
+        press > 0.001) {

This would also filter out small negative pressure readings if the sensor ever produces them due to calibration drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MeaSpecMS5803.cpp` around lines 63 - 86, The pressure validation
uses a strict equality check (press != 0.0) which can miss tiny near-zero
values; change the condition to test magnitude against a small epsilon (e.g.
fabs(press) > 1e-3) so near-zero or small negative drifts are rejected. Update
the if condition that currently references press and temp (around
MS5803_internal.getPressure, temp/press checks and verifyAndAddMeasurementResult
calls) to use fabs(press) > EPS where EPS is a locally defined constant (or a
constexpr) and ensure <cmath> is available for fabs.
src/sensors/MaxBotixSonar.cpp (1)

172-172: Suspicious type cast from uint16_t to int16_t.

_stream->parseInt() returns a long, which is cast to uint16_t and then implicitly assigned to the int16_t variable result. While the downstream validation (result <= 0 || result >= _maxRange) catches most edge cases, the intermediate uint16_t cast is misleading since result is signed. Consider casting directly to int16_t or using the return type directly with appropriate bounds checking.

💡 Suggested simplification
-        result = static_cast<uint16_t>(_stream->parseInt());
+        result = static_cast<int16_t>(_stream->parseInt());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.cpp` at line 172, The cast to uint16_t is
misleading because _stream->parseInt() returns a long and result is an int16_t;
update the assignment so you either capture the parseInt() return in a signed
type (e.g., long or int16_t) and then assign to result, or cast directly to
int16_t, ensuring subsequent checks against _maxRange still work; change the
occurrence at the assignment to result (the variable assigned from
_stream->parseInt()) to use a signed cast/temporary instead of
static_cast<uint16_t>.
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md (1)

43-43: Consider linking directly to the signup page for better user experience.

The URL https://monitormywatershed.org/ is accessible, but users registering sites and sensors would benefit from a direct link to https://monitormywatershed.org/signup/, which is the account registration page.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md` at line
43, Update the Monitor My Watershed link in the README so it points directly to
the signup page; replace the existing URL "https://monitormywatershed.org/" with
"https://monitormywatershed.org/signup/" in the line that currently reads
"Register a site and sensors on [Monitor My
Watershed](https://monitormywatershed.org/)" to improve user experience when
registering sites and sensors.
src/sensors/TurnerTurbidityPlus.cpp (1)

113-122: Minor typo in comments: "TI1115" and "TI1015" should be "ADS1115" and "ADS1015".

The Texas Instruments ADC parts are named ADS1115 and ADS1015, not TI1115/TI1015.

📝 Suggested fix
     // ADS Library default settings:
-    //  - TI1115 (16 bit)
+    //  - 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)
-    //  - TI1015 (12 bit)
+    //  - 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)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.cpp` around lines 113 - 122, Replace the
incorrect ADC part names in the comment block describing ADS library defaults in
TurnerTurbidityPlus.cpp: change "TI1115" to "ADS1115" and "TI1015" to "ADS1015"
so the comments correctly reference the device names (update the comment near
the ADS Library default settings in the TurnerTurbidityPlus.cpp file).
src/sensors/AnalogElecConductivity.cpp (1)

91-95: Consider using reading validity to drive retry outcome.

Line 95 always calls bumpMeasurementAttemptCount(true), which effectively disables retries even if the reading is invalid (e.g., -9999 or NaN). If you want retries to kick in on invalid data, compute a simple validity flag and pass it to bumpMeasurementAttemptCount.

Suggested change
-    // 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.
     verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM,
                                   sensorEC_uScm);
-    return bumpMeasurementAttemptCount(true);
+    const bool ok =
+        (sensorEC_uScm != -9999) && !isnan(sensorEC_uScm);
+    return bumpMeasurementAttemptCount(ok);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AnalogElecConductivity.cpp` around lines 91 - 95, The code
currently always calls bumpMeasurementAttemptCount(true) even when sensorEC_uScm
is invalid; compute a boolean validity flag (e.g., valid = sensorEC_uScm !=
-9999 && !isnan(sensorEC_uScm)) after you obtain sensorEC_uScm and before
calling bumpMeasurementAttemptCount, keep the
verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, sensorEC_uScm)
call as-is, then call bumpMeasurementAttemptCount(valid) so retries occur when
the reading is invalid.
src/LoggerBase.cpp (1)

200-203: Consider a deprecated shim for external API compatibility.
The rename from setinitialShortIntervals to setInitialShortIntervals is a breaking change for external user sketches. All internal references have been properly updated, but to preserve backward compatibility with existing user code, consider adding a deprecated wrapper in LoggerBase that forwards to the new method.

♻️ Optional backward-compat shim (LoggerBase.cpp + LoggerBase.h)
+// Backward-compat shim (declare in LoggerBase.h as well)
+void Logger::setinitialShortIntervals(int16_t initialShortIntervals) {
+    setInitialShortIntervals(initialShortIntervals);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LoggerBase.cpp` around lines 200 - 203, Add a deprecated shim named
setinitialShortIntervals that forwards to the new
Logger::setInitialShortIntervals to preserve binary/source compatibility for
external sketches; declare the deprecated method in the LoggerBase header
(matching the original signature) and implement it in LoggerBase.cpp to call
setInitialShortIntervals(_ same parameter_), and annotate it with the compiler
deprecation attribute or macro (e.g., [[deprecated]] or DEPRECATED) so users get
a warning while existing code continues to work.
ChangeLog.md (1)

38-41: Optional: tighten wording in Unreleased bullets for readability.

A couple of sentences start with the same word and one phrase is a bit wordy; a small rephrase would read smoother.

Also applies to: 73-78

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ChangeLog.md` around lines 38 - 41, Tighten the Unreleased bullet wording by
removing repetition and wordiness: rephrase the two sentences about
updateAllSensors() and completeUpdate() to one concise sentence that states they
were consolidated into a single function with four boolean arguments (power on,
wake, sleep, power off) and note that setting all flags false reproduces the old
updateAllSensors() behavior; similarly shorten and clarify the parallel bullets
around lines 73-78 to use varied sentence starts and more direct phrasing while
preserving the same technical details and function names (updateAllSensors(),
completeUpdate()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md`:
- Line 5: Fix the grammar in the README sentence that currently reads "This
examples also makes use of..." by changing "examples" to the singular "example"
and adjusting surrounding verb agreement if needed so the phrase reads "This
example also makes use of the on-board light, temperature, and humidity sensors
on the Mayfly 1.x."; update the sentence in the ReadMe.md content block
accordingly to ensure correct singular noun-verb agreement.

In `@src/publishers/EnviroDIYPublisher.h`:
- Around line 17-33: Update the file-level brief to reflect that
EnviroDIYPublisher is now a typedef alias of MonitorMyWatershedPublisher and
remove or replace any references to the old data.enviroDIY.org portal;
specifically edit the top comment/block that describes EnviroDIYPublisher so it
notes "EnviroDIYPublisher is a deprecated typedef for
MonitorMyWatershedPublisher — use MonitorMyWatershedPublisher in new code" and
remove guidance pointing to the legacy EnviroDIY portal (or replace with the
current Monitor My Watershed documentation) while keeping the typedef typedef
MonitorMyWatershedPublisher EnviroDIYPublisher for backwards compatibility.

In `@src/publishers/UbidotsPublisher.cpp`:
- Around line 109-111: The user-facing error string in UbidotsPublisher.cpp
currently ends with the awkward punctuation sequence "!."; update both
occurrences of that UUID error message to use proper punctuation (e.g., "Ubidots
UUID missing." or "Ubidots UUID missing!") so it reads naturally; locate and
modify the messages in the UbidotsPublisher class/method where the UUID
check/logging happens (the two occurrences around the current message locations)
and replace the "!." ending with the chosen single punctuation mark
consistently.

In `@src/sensors/GroPointParent.cpp`:
- Around line 165-238: The current addSingleMeasurementResult function only
writes measurements when both getValues and getTemperatureValues succeed,
discarding valid partial data; change it to independently call
verifyAndAddMeasurementResult for moisture when success is true and for
temperature when successT is true, and compute the overall call result passed to
bumpMeasurementAttemptCount as (success || successT) instead of (success &&
successT); update any related debug logs if needed and keep function names
getValues, getTemperatureValues, verifyAndAddMeasurementResult, and
bumpMeasurementAttemptCount to locate changes.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 37-56: The code computes sensorValue_analog using
_operatingVoltage and PROCESSOR_ADC_MAX without validating them; add a guard
after reading rawAnalog that checks _operatingVoltage > 0, PROCESSOR_ADC_MAX > 0
(and optionally _voltageMultiplier > 0) and if any are invalid log an error (use
MS_DBG with getSensorNameAndLocation()) and return
bumpMeasurementAttemptCount(false) to mark the measurement as failed; update
references to PROCESSOR_ADC_MAX, _operatingVoltage, _voltageMultiplier,
rawAnalog, and bumpMeasurementAttemptCount in ProcessorAnalog.cpp to implement
this check before performing the division and scaling.

In `@src/sensors/ProcessorStats.cpp`:
- Around line 22-39: Add an inline comment near the ARDUINO_SODAQ_ONE /
ARDUINO_SODAQ_ONE_BETA conditional (around the _batteryMultiplier assignments)
that explicitly states that only SODAQ ONE versions "v0.1" and "v0.2" are
documented/supported and that unknown versions fall back to _batteryMultiplier =
-1; if you can confirm newer SODAQ ONE versions require different multipliers,
update the conditional to handle them instead of using -1 and document the added
versions similarly. Use the existing symbols (_batteryMultiplier, _version,
ARDUINO_SODAQ_ONE, ARDUINO_SODAQ_ONE_BETA) to place the comment next to the
relevant code block.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 107-118: The range-check branch currently only records results
when adcVoltage is valid, allowing stale values to persist when adcVoltage is
out of range; update the failure path in TurnerCyclops.cpp (around the
adcVoltage check and the calls to verifyAndAddMeasurementResult,
CYCLOPS_VAR_NUM, CYCLOPS_VOLTAGE_VAR_NUM, and bumpMeasurementAttemptCount) to
explicitly write "bad"/cleared values (e.g., NAN or a defined sentinel, or zero
plus a status flag) for both the concentration variable (CYCLOPS_VAR_NUM) and
the measured voltage variable (CYCLOPS_VOLTAGE_VAR_NUM) when the voltage is out
of range, then call bumpMeasurementAttemptCount(false) so downstream consumers
cannot read stale measurements.
- Around line 90-92: Add an explicit C++ math header so the use of fabs in the
calibration check is portable: include <cmath> at the top of the translation
unit that defines epsilon and the comparison using fabs(_volt_std - _volt_blank)
(symbols: fabs, epsilon, _volt_std, _volt_blank, TurnerCyclops.cpp) to ensure
fabs is declared across toolchains; no other code changes required.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 165-179: The ADC voltage returned by ads.computeVolts (variable
adcVoltage) is not scaled by the stored _voltageDividerFactor before calibration
or reporting; update addSingleMeasurementResult (or the block where adcVoltage
is used) to multiply adcVoltage by _voltageDividerFactor immediately after
computeVolts so the calibration math (calibResult = ... (adcVoltage -
_volt_blank)) and the value sent to
verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM, adcVoltage) use
the corrected voltage; this uses the constructor-stored voltage divider value
and preserves existing calibration constants.

---

Outside diff comments:
In `@build-menu-configurations.ps1`:
- Around line 147-152: The array of publisher flag names is declared as
$publisherFlag but the loop uses $publisherFlags, causing the Foreach
($publisherFlag in $publisherFlags) to never iterate; rename the declared
variable to $publisherFlags (or change the loop to use $publisherFlag) so the
array identifier matches the Foreach, ensuring the Foreach over publisher flags
runs and publisher builds are not skipped—update the declaration that currently
reads $publisherFlag = @(...) to $publisherFlags = @(...) to match the Foreach
usage.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 214-219: The branching for adjusting interval based on percent is
ordered incorrectly so percent values >=75 and >=90 never reach their branches;
in the function/block that updates interval (variables percent and interval,
currently checking if (percent >= 50) then else if (percent >= 75) then else if
(percent >= 90)), reorder the comparisons from highest to lowest (check percent
>= 90 first, then >= 75, then >= 50) so the stricter thresholds are applied
correctly.

In `@src/sensors/GroPointParent.h`:
- Line 2: The Doxygen header in GroPointParent.h incorrectly uses "@file
GroPointParent.cpp"; update the `@file` tag to "@file GroPointParent.h" in the
top-of-file comment so the documented filename matches this header (search for
the Doxygen block in GroPointParent.h and replace the .cpp extension with .h).

---

Duplicate comments:
In @.gitattributes:
- Around line 36-38: The .gitattributes file references filters smudgePasswords
and disableDebug but there are no git filter driver definitions; add
instructions or a setup step that registers these drivers by configuring git
config filter.smudgePasswords.smudge and filter.smudgePasswords.clean (and
similarly filter.disableDebug.smudge and filter.disableDebug.clean) with the
appropriate commands or scripts, and document or include a repo setup script
(e.g., setup.sh or CONTRIBUTING.md section) that runs those git config commands
so contributors get no warnings and filtering works on checkout/commit for the
referenced filters.

In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-118: The exclusion check in the os.walk loop is comparing full
paths to folder basenames so exclusions never match; update the logic in the
non_menu_examples collection (inside the loop that uses os.walk over
examples_path) to compare the basename of root (os.path.basename(root)) against
the exclusion list (e.g., ".history","logger_test","archive","tests",
menu_example_name) instead of comparing root directly, add a filter that uses
os.path.realpath(root) consistently when later filtering (lines ~316-322) so
both places use the same canonical path form, and remove the unused variable
subdirs (B007) or use it if intended; ensure use_verbose and file_path/debug
print remain correct.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to the nonexistent TinyGSM method
modem.gsmModem.forceModemBaud(...) will not compile; replace its usage with the
supported APIs: for initial bring-up use TinyGsmAutoBaud(SerialAT, min, max)
where appropriate, and after AT sync use
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) (or call setBaud on the
same modem/gsmModem object that owns modemSerial) so that the code uses
TinyGSM-compatible methods (search for forceModemBaud, modemSerial, modemBaud
and update those call sites accordingly).

In `@examples/examples.dox`:
- Around line 67-70: The cross-reference is still using the old anchor name
"examples_outdated" on the reference line; update that reference to the new
anchor "page_examples_outdated" so the `@ref` matches the renamed `@subpage/`@page
directives (look for the `@ref` examples_outdated usage and change it to `@ref`
page_examples_outdated).

In `@examples/menu_a_la_carte/ReadMe.md`:
- Line 1267: Replace the incorrect plural "UUID's" with "UUIDs" in the README
sentence that reads "paste the UUID's all together as copied from the "View
Token UUID List" link for a site" so it correctly uses the plural form; update
the string containing "View Token UUID List" accordingly to read "paste the
UUIDs all together..." to fix the pluralization.

In `@examples/ReadMe.md`:
- Around line 122-131: Remove the duplicated word in the first sentence of the
"The EnviroDIY Sensor Station Kit" section: edit the paragraph that currently
starts "The The EnviroDIY Sensor Station Kit is designed..." to read "The
EnviroDIY Sensor Station Kit is designed..." so there is only one "The" (look
for the heading "The EnviroDIY Sensor Station Kit" and the sentence starting
with "The The EnviroDIY Sensor Station Kit is designed..." to locate the text).

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-55: Update the constructor comment for
MonitorMyWatershedPublisher() to accurately reflect that the default constructor
initializes members (host, path, port) to default values rather than leaving
them unset; locate the MonitorMyWatershedPublisher() declaration and replace the
phrase "with no members set" with a brief statement like "initializes host,
path, and port to their default values" (or remove the misleading clause) so the
doc matches the implementation.

In `@src/SensorBase.cpp`:
- Around line 373-384: In clearValues (SensorBase.cpp) do not reset power/wake
timing or clear their status bits: remove or avoid zeroing _millisPowerOn and
_millisSensorActivated and do not clear POWER_ATTEMPTED, POWER_SUCCESSFUL,
WAKE_ATTEMPTED or WAKE_SUCCESSFUL in the clearStatusBits call; only reset
measurement-specific fields (_measurementAttemptsCompleted, _retryAttemptsMade,
_millisMeasurementRequested, _millisMeasurementCompleted and the MEASUREMENT_*
status bits) so existing stabilization/wake state used by VariableArray
concurrent updates remains intact.

In `@src/sensors/ANBpH.cpp`:
- Around line 152-176: When the sensor is power‑cycled (_powerPin >= 0) we must
not program a 0 (continuous) interval; guard the case where
_loggingIntervalMinutes == 0 and clamp or fail before calling
_anb_sensor.setIntervalTime(0). Update the logic around
programmedInterval/_powerPin so that when _powerPin >= 0 and
_loggingIntervalMinutes == 0 you set programmedInterval to the minimum allowed
(e.g. 10) and emit a warning via MS_DBG, then call
_anb_sensor.setIntervalTime(programmedInterval) (affecting intervalSet);
alternatively return or set an error if you prefer failing instead of
clamping—ensure the check happens before calling _anb_sensor.setIntervalTime and
references _loggingIntervalMinutes, _powerPin, programmedInterval,
setIntervalTime, and intervalSet.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 66-72: The comment block in src/sensors/ApogeeSQ212.cpp
incorrectly refers to "TI1115" and "TI1015"; update the ADC part-number typos to
the correct chip names "ADS1115" and "ADS1015" in that comment (the ADS Library
default settings block) so the documentation matches the actual hardware
references used in the code.

In `@src/sensors/AtlasParent.cpp`:
- Around line 174-182: The loop that reads values using _i2c->available() can
exit early but leaves success true, so update the logic in the read loop that
iterates up to _numReturnedValues: when _i2c->available() == 0 is detected, set
success = false before breaking to mark the attempt as truncated/failed; keep
using parseFloat(), isnan() sanitization and verifyAndAddMeasurementResult(i,
result) as before for any values already read. Ensure you reference the
variables and methods _i2c, available(), parseFloat(), success,
_numReturnedValues, and verifyAndAddMeasurementResult() when making the change.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 210-261: The code currently creates the SD file before checking
image size and sets _filename even on failure; change the flow so you call
_camera.getImageSize() and validate image_size > 0 before calling
imgFile.open(filename.c_str(), ...), only open the file when image_size is
valid, perform _camera.transferImage(imgFile, image_size) and set _filename only
when bytes_transferred == image_size; ensure imgFile.close() is still called on
all code paths and keep the watchdog disable/enable and
verifyAndAddMeasurementResult(HYDROCAM_SIZE_VAR_NUM, ...) semantics inside the
successful-transfer branch (refer to imgFile.open, _camera.getImageSize,
_camera.transferImage, _filename, verifyAndAddMeasurementResult).

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: Unknown boards only get a default OPERATING_VOLTAGE which
leaves LOGGER_BOARD, BATTERY_PIN, and BATTERY_MULTIPLIER undefined and can break
builds; add fallback defines for LOGGER_BOARD, BATTERY_PIN, and
BATTERY_MULTIPLIER in the same conditional block in KnownProcessors.h (the
section that currently defines OPERATING_VOLTAGE) so each macro is given a safe
default value and a pragma message warning (similar to the existing
OPERATING_VOLTAGE block), ensuring the identifiers LOGGER_BOARD, BATTERY_PIN,
and BATTERY_MULTIPLIER are always defined for unsupported boards.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop in MaxBotixSonar.cpp uses a hardcoded
rangeAttempts < 25 and a long blocking stream timeout, which can exceed the
measurement time budget; replace the internal fixed retry logic with the
framework retry counters and a non-blocking time budget check: stop using the
magic 25 and instead consult/advance bumpMeasurementAttemptCount() and compare
against _allowedMeasurementRetries (or return early) while measuring elapsed
time (e.g. record start millis and abort the loop if elapsed >=
HRXL_MEASUREMENT_TIME_MS) before each attempt; also reduce or remove long
blocking waits on _stream->parseInt() (use shorter per-attempt timeout or check
available() and break to let the main loop/watchdog run) and keep existing
behavior for triggering (_triggerPin), result validation, and _convertCm
handling in the same function so callers see the same outputs.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 57-69: The addSingleMeasurementResult in MaximDS3231
unconditionally marks success; change it to validate rtc.getTemperature() (call
to rtc.getTemperature) for NaN or other invalid values before calling
verifyAndAddMeasurementResult and bumpMeasurementAttemptCount(true); if the
temperature is NaN/invalid, do not call verifyAndAddMeasurementResult, instead
log the failure and call bumpMeasurementAttemptCount(false) so retries are
allowed—ensure the success path still calls
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) and
bumpMeasurementAttemptCount(true).

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 107-113: The variable i2c_status is declared as type byte
(unsigned) but initialized to -1 which becomes 255; change the initializer to an
explicit unsigned value (e.g., 0xFF or 255) or to 0 depending on desired
semantics, or alternatively change the type to a signed integer if you need
negative sentinel values; update the declaration of i2c_status accordingly so
its initial value is explicit and correct.

In `@src/sensors/ProcessorAnalog.h`:
- Around line 1-14: The file header in ProcessorAnalog.h contains a stray
asterisk after the filename on the second line; remove that extraneous '*' so
the file comment reads cleanly (affecting the top-of-file docblock for
ProcessorAnalog.h which documents the ProcessorAnalog and
ProcessorAnalog_Voltage classes) and ensure the comment formatting remains
consistent with the surrounding block.

In `@src/sensors/TallyCounterI2C.cpp`:
- Around line 84-89: The log text and the check are inconsistent: either broaden
the check to include zero or change the message to only mention negative values.
Update the condition around the variable "events" in TallyCounterI2C.cpp (the if
handling events < 0) to "events <= 0" if you intend to treat zero as
non-response, and adjust the comment and MS_DBG(...) message (which uses
getSensorNameAndLocation()) to say "returns 0 or bad/negative, assuming sensor
non-response" (or alternatively keep the existing negative-only message if you
choose not to include zero).
- Around line 96-98: The call to counter_internal.Clear() is currently executed
even when the read failed, which can drop unreported events; update the code so
counter_internal.Clear() is only invoked after a successful read. Locate where
counter_internal.Clear() is called (the existing Clear() call) and move it into
the success branch of the I2C read/parse logic (e.g., after the read returns
success or after successful parsing/validation), ensuring all error/early-return
paths skip Clear() so the internal counter is preserved on failures.

In `@src/sensors/TIINA219.cpp`:
- Around line 74-107: The code in addSingleMeasurementResult currently only
checks for NaN values and can accept stale readings when I2C fails; also
setup()/wake() ignore ina219_phy.begin() return. Fix by gating reads on the
INA219 driver success state and by checking begin() results: in
addSingleMeasurementResult call ina219_phy.success() (or equivalent status
method) before calling getCurrent_mA/getBusVoltage_V/getPower_mW and treat
failure as reading failure (pass false to bumpMeasurementAttemptCount), and in
setup() and wake() check the return value of ina219_phy.begin() and handle a
false result (log/error and mark sensor init/wake as failed) so
verifyAndAddMeasurementResult only runs when the driver indicates a successful
I2C read.

In `@src/sensors/YosemitechParent.cpp`:
- Around line 229-231: The conductivity scaling for model Y520 multiplies
parmValue regardless of whether the sensor read succeeded, so the sentinel -9999
can get scaled; modify the code around the _ysensor.getValues() usage to only
apply the Y520 conversion when the read actually succeeded (i.e., the boolean
result from _ysensor.getValues() or the existing success flag is true) in
addition to the !isnan(parmValue) check, ensuring the conversion for _model ==
Y520 is skipped on failed reads.

---

Nitpick comments:
In `@ChangeLog.md`:
- Around line 38-41: Tighten the Unreleased bullet wording by removing
repetition and wordiness: rephrase the two sentences about updateAllSensors()
and completeUpdate() to one concise sentence that states they were consolidated
into a single function with four boolean arguments (power on, wake, sleep, power
off) and note that setting all flags false reproduces the old updateAllSensors()
behavior; similarly shorten and clarify the parallel bullets around lines 73-78
to use varied sentence starts and more direct phrasing while preserving the same
technical details and function names (updateAllSensors(), completeUpdate()).

In `@docs/Getting-Started/Getting-Started.md`:
- Around line 144-145: Update the paragraph that mentions "Monitor My Watershed"
to include a direct link to its documentation or example README so readers can
easily find the publishing/setup details; locate the string "Monitor My
Watershed" in the Getting-Started content and replace it with a Markdown link
pointing to the project's docs or the example README (use the appropriate
relative path if the docs live in-repo or the canonical external URL if hosted),
and ensure the link text remains "Monitor My Watershed" for clarity.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md`:
- Line 43: Update the Monitor My Watershed link in the README so it points
directly to the signup page; replace the existing URL
"https://monitormywatershed.org/" with "https://monitormywatershed.org/signup/"
in the line that currently reads "Register a site and sensors on [Monitor My
Watershed](https://monitormywatershed.org/)" to improve user experience when
registering sites and sensors.

In `@src/LoggerBase.cpp`:
- Around line 200-203: Add a deprecated shim named setinitialShortIntervals that
forwards to the new Logger::setInitialShortIntervals to preserve binary/source
compatibility for external sketches; declare the deprecated method in the
LoggerBase header (matching the original signature) and implement it in
LoggerBase.cpp to call setInitialShortIntervals(_ same parameter_), and annotate
it with the compiler deprecation attribute or macro (e.g., [[deprecated]] or
DEPRECATED) so users get a warning while existing code continues to work.

In `@src/sensors/AnalogElecConductivity.cpp`:
- Around line 91-95: The code currently always calls
bumpMeasurementAttemptCount(true) even when sensorEC_uScm is invalid; compute a
boolean validity flag (e.g., valid = sensorEC_uScm != -9999 &&
!isnan(sensorEC_uScm)) after you obtain sensorEC_uScm and before calling
bumpMeasurementAttemptCount, keep the
verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, sensorEC_uScm)
call as-is, then call bumpMeasurementAttemptCount(valid) so retries occur when
the reading is invalid.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 172: The cast to uint16_t is misleading because _stream->parseInt()
returns a long and result is an int16_t; update the assignment so you either
capture the parseInt() return in a signed type (e.g., long or int16_t) and then
assign to result, or cast directly to int16_t, ensuring subsequent checks
against _maxRange still work; change the occurrence at the assignment to result
(the variable assigned from _stream->parseInt()) to use a signed cast/temporary
instead of static_cast<uint16_t>.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 63-86: The pressure validation uses a strict equality check (press
!= 0.0) which can miss tiny near-zero values; change the condition to test
magnitude against a small epsilon (e.g. fabs(press) > 1e-3) so near-zero or
small negative drifts are rejected. Update the if condition that currently
references press and temp (around MS5803_internal.getPressure, temp/press checks
and verifyAndAddMeasurementResult calls) to use fabs(press) > EPS where EPS is a
locally defined constant (or a constexpr) and ensure <cmath> is available for
fabs.

In `@src/sensors/PaleoTerraRedox.h`:
- Around line 127-131: Add a short explanatory comment next to
PTR_CONVERSION_WAIT_TIME_MS (and referencing PTR_MEASUREMENT_TIME_MS) that
documents why the 300 ms wait was chosen — e.g., cite the PaleoTerra sensor
datasheet conversion time spec (include section or page if available) or note
that 300 ms was selected as a conservative empirical margin over the 67 ms
nominal measurement time based on bench testing; update the comment block near
the macro definitions (PTR_CONVERSION_WAIT_TIME_MS and PTR_MEASUREMENT_TIME_MS)
to include that reference and the rationale so future maintainers understand the
origin and can adjust if the datasheet or measured timings change.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 113-122: Replace the incorrect ADC part names in the comment block
describing ADS library defaults in TurnerTurbidityPlus.cpp: change "TI1115" to
"ADS1115" and "TI1015" to "ADS1015" so the comments correctly reference the
device names (update the comment near the ADS Library default settings in the
TurnerTurbidityPlus.cpp file).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
build-menu-configurations.ps1 (1)

147-152: ⚠️ Potential issue | 🔴 Critical

Critical bug: Variable name mismatch causes broken loop.

Line 147 defines $publisherFlag (singular), but Line 152's Foreach iterates over $publisherFlags (plural). This typo means the loop iterates over an undefined/empty variable, so none of the publisher configurations will be built.

🐛 Proposed fix
-$publisherFlag = @(`
+$publisherFlags = @(`
     'BUILD_PUB_MONITOR_MY_WATERSHED_PUBLISHER', `
     'BUILD_PUB_DREAM_HOST_PUBLISHER', `
     'BUILD_PUB_THING_SPEAK_PUBLISHER')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-menu-configurations.ps1` around lines 147 - 152, The loop variable name
is mismatched: you define the array as $publisherFlag but the Foreach uses
$publisherFlags; update either the declaration or the Foreach so both use the
same identifier (e.g., rename the declaration to $publisherFlags to match the
Foreach) so the Foreach ($publisherFlags) iterates the actual array of
publishers; ensure any subsequent references inside the loop use the singular
element variable $publisherFlag consistently.
src/sensors/GroPointParent.h (1)

1-2: ⚠️ Potential issue | 🟡 Minor

Incorrect filename in file documentation header.

The @file documentation comment states GroPointParent.cpp but this is a header file (GroPointParent.h).

📝 Proposed fix
 /**
- * `@file` GroPointParent.cpp
+ * `@file` GroPointParent.h
  * `@copyright` Stroud Water Research Center
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` around lines 1 - 2, The file header comment in
GroPointParent.h incorrectly tags the file as `GroPointParent.cpp`; update the
`@file` documentation header to the correct filename `GroPointParent.h` (or
remove the `@file` tag if your style omits it) so the top-of-file doc matches
the actual header; look for the `@file GroPointParent.cpp` line near the top of
GroPointParent.h (around the header block that documents the GroPointParent
class) and replace it with `@file GroPointParent.h`.
src/LoggerBase.cpp (1)

1278-1283: ⚠️ Potential issue | 🔴 Critical

Add null check before strlen(_samplingFeatureUUID).

The member variable is initialized to nullptr by default (LoggerBase.h:519) and can remain nullptr if a Logger is constructed with a constructor that does not call setSamplingFeatureUUID(), such as the default Logger() constructor. The code at line 1798-1799 already checks for nullptr in a similar context, confirming this is a valid concern. Without the guard, strlen() will crash if called on a nullptr.

Suggested fix
-    if (strlen(_samplingFeatureUUID) > 1) {
+    if (_samplingFeatureUUID != nullptr && strlen(_samplingFeatureUUID) > 1) {
         stream->print(F("Sampling Feature UUID: "));
         stream->print(_samplingFeatureUUID);
         stream->println(',');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LoggerBase.cpp` around lines 1278 - 1283, Add a nullptr guard before
calling strlen on _samplingFeatureUUID in the printing block: change the
condition to first check _samplingFeatureUUID is not null (e.g.,
_samplingFeatureUUID != nullptr) and then check strlen(_samplingFeatureUUID) >
1, and leave the existing stream->print/_samplingFeatureUUID printing lines
intact; mirror the null-check pattern used elsewhere for _samplingFeatureUUID in
this class.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

214-220: ⚠️ Potential issue | 🔴 Critical

Critical: Condition order bug causes unreachable branches.

The else if chain checks thresholds in ascending order, but conditions are >= comparisons. When percent >= 75, the first condition percent >= 50 is already true, so the 75% and 90% branches are never reached.

This bug prevents the buffer from properly accelerating transmissions as it fills up, potentially causing data loss.

🐛 Fix: Check thresholds from highest to lowest
-    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 /= 4;
+    } else if (percent >= 50) {
+        interval /= 2;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 214 - 220, The
if/else-if chain that adjusts interval using the variables percent and interval
in MonitorMyWatershedPublisher.cpp is ordered incorrectly so higher thresholds
are shadowed; change the condition order to check highest thresholds first (if
percent >= 90 -> interval = 1; else if percent >= 75 -> interval /= 4; else if
percent >= 50 -> interval /= 2) so the >=75 and >=90 branches can be reached and
the buffer accelerates correctly.
🧹 Nitpick comments (14)
src/modems/DigiXBeeCellularTransparent.h (1)

169-172: Missing override specifier on virtual methods.

deleteClient (line 170) and deleteSecureClient (line 172) are declared virtual but lack the override specifier, unlike createClient, createSecureClient, and other overridden methods in this class. If these override base class methods, add override for consistency and compile-time safety. If they are new virtual methods introduced here, consider whether virtual is needed at all.

♻️ Proposed fix if these override base class methods
     virtual Client* createClient() override;
-    virtual void    deleteClient(Client* client);
+    virtual void    deleteClient(Client* client) override;
     virtual Client* createSecureClient() override;
-    virtual void    deleteSecureClient(Client* client);
+    virtual void    deleteSecureClient(Client* client) override;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/DigiXBeeCellularTransparent.h` around lines 169 - 172, In class
DigiXBeeCellularTransparent ensure consistency and compile-time safety by adding
the missing override specifier to the two methods that override base-class
virtuals: change the declarations of deleteClient(Client* client) and
deleteSecureClient(Client* client) to use override (matching createClient() and
createSecureClient()), or if they are not overrides remove virtual; update the
signatures in DigiXBeeCellularTransparent.h so deleteClient and
deleteSecureClient are explicitly marked override when they implement base-class
methods.
examples/menu_a_la_carte/ReadMe.md (1)

1268-1268: Consider simplifying the emphasis.

The phrase "very, very, very careful" is emphatic but could be more concise. Consider alternatives like "extremely careful" or "exceptionally careful" for a more professional tone while maintaining the warning's importance.

✨ Suggested alternative phrasing
-If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUIDs.
+If using this method, be extremely careful to make sure the order of your variables exactly matches the order of your UUIDs.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/ReadMe.md` at line 1268, The sentence warning about
variable order uses the informal repetition "very, very, very careful"; replace
that phrase with a concise, professional alternative (e.g., "extremely careful"
or "exceptionally careful") in the line that begins "If using this method, be
very, very, very careful to make sure the order of your variables exactly
matches the order of your UUIDs." to preserve the warning while improving tone.
src/sensors/MaximDS18.cpp (1)

184-208: Refactored control flow looks good; consider using library constant for clarity.

The restructured early-return pattern with bumpMeasurementAttemptCount() is cleaner and aligns with the centralized success-handling pattern in this PR.

One minor suggestion: the DallasTemperature library defines DEVICE_DISCONNECTED_C for the -127 error value. Using this constant would improve readability and maintain consistency if the library ever changes this value.

♻️ Optional: Use library constant
     // 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) {
+    if (result != 85.0 && result != DEVICE_DISCONNECTED_C) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaximDS18.cpp` around lines 184 - 208, Replace the literal -127
sentinel with the DallasTemperature library constant to improve clarity: in the
measurement handling code (check using getStatusBit(MEASUREMENT_SUCCESSFUL) then
calling _internalDallasTemp.getTempC(_OneWireAddress) and deciding success via
result), change the comparison that currently checks "result != 85 && result !=
-127" to use DEVICE_DISCONNECTED_C for the disconnected-case check; keep the
same logic path that calls verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM,
result) and returns bumpMeasurementAttemptCount(success).
src/sensors/TurnerCyclops.cpp (1)

107-118: Consider logging when voltage is out of range.

When adcVoltage fails the range check (line 107), no diagnostic message is logged. While verifyAndAddMeasurementResult handles bad values gracefully, adding a debug log for out-of-range voltages would aid troubleshooting.

🔧 Suggested improvement
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // Apply the unique calibration curve for the given sensor
         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);
         success = true;
+    } else {
+        MS_DBG(F("  adcVoltage out of valid range:"), adcVoltage);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 107 - 118, The range-check branch
for adcVoltage in TurnerCyclops.cpp currently only logs and adds results when
inside range; when the check fails there is no diagnostic output—add a debug log
(using MS_DBG or the existing logging facility) that prints the out-of-range
adcVoltage value and reason before calling bumpMeasurementAttemptCount(success)
so operators can see when adcVoltage is outside the (-0.3, 3.6) window; update
the code around the adcVoltage check (the block using calibResult,
verifyAndAddMeasurementResult and the final return calling
bumpMeasurementAttemptCount(success)) to emit this log when success is false.
src/sensors/ProcessorAnalog.h (1)

144-156: Consider adding a compiler warning for unspecified ADC resolution.

The #else block at line 152-154 silently defaults to 10-bit resolution without warning the user. This could lead to unexpected precision if MS_PROCESSOR_ADC_RESOLUTION is set to an unsupported value (e.g., 8 or 14).

🔧 Suggested improvement
 `#elif` MS_PROCESSOR_ADC_RESOLUTION == 10
 /// `@brief` Decimals 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
+#warning "MS_PROCESSOR_ADC_RESOLUTION not set to 10 or 12, defaulting to 10-bit resolution"
 `#define` PROCESSOR_ANALOG_RESOLUTION 3
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.h` around lines 144 - 156, The else branch
currently silently defaults PROCESSOR_ANALOG_RESOLUTION to 3 when
MS_PROCESSOR_ADC_RESOLUTION is unspecified/unsupported; update the `#else` block
(near the existing MS_PROCESSOR_ADC_RESOLUTION checks and
PROCESSOR_ANALOG_RESOLUTION definition) to emit a compiler diagnostic (e.g.,
`#warning` or `#pragma` message with a clear text like "Unsupported
MS_PROCESSOR_ADC_RESOLUTION; defaulting to 10-bit precision") so users are
warned of the fallback, then keep the default define; ensure the diagnostic
references MS_PROCESSOR_ADC_RESOLUTION and PROCESSOR_ANALOG_RESOLUTION for
clarity.
src/sensors/ProcessorAnalog.cpp (1)

47-51: Consider removing redundant priming read.

Two priming reads are performed (lines 48 and 50) before the actual measurement on line 51. While priming can help stabilize ADC readings, two priming reads may be excessive. Consider whether a single priming read is sufficient to reduce measurement time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.cpp` around lines 47 - 51, The code performs two
redundant ADC priming reads before capturing rawAnalog from
analogRead(_dataPin); remove one of the priming analogRead(_dataPin) calls so
only a single priming read remains (keep pinMode(_dataPin, INPUT) and then one
analogRead for stabilization followed by the actual read that assigns float
rawAnalog). Update any comments to reflect a single priming read and ensure no
other logic depends on the second priming call.
src/modems/LoggerModemMacros.h (1)

543-551: Potential type mismatch in deleteClient cast.

The deleteClient function casts the Client* to TinyGsm##TinyGSMType::GsmClient##TinyGSMType* before deleting. If a secure client was accidentally passed to deleteClient instead of deleteSecureClient, this could lead to undefined behavior due to incorrect cast. Consider adding runtime type checking or documentation warning about this constraint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/LoggerModemMacros.h` around lines 543 - 551, The macro-generated
specificModem::deleteClient currently static_casts Client* to
TinyGsm##TinyGSMType::GsmClient##TinyGSMType* which can UB if a secure client
was passed; update the macro MS_MODEM_DELETE_CLIENT to perform a runtime-checked
cast (use dynamic_cast) and verify the result before deleting, and if the
dynamic_cast fails (or it resolves to a secure client type) log/ASSERT and skip
deletion to avoid incorrect deletes; reference the function
specificModem::deleteClient and the TinyGsm##TinyGSMType::GsmClient##TinyGSMType
and corresponding secure client types when adding the checks.
src/sensors/ANBpH.h (1)

478-509: Consider validating loggingIntervalMinutes bounds.

The documentation states the parameter "Must be between 10 and 240 minutes," but the constructor doesn't validate this constraint. Invalid values could cause unexpected sensor behavior.

🛡️ Optional: Add bounds validation

In the constructor implementation (ANBpH.cpp), consider adding:

// After initializer list, in constructor body:
if (_powerPin >= 0 && (loggingIntervalMinutes < 10 || loggingIntervalMinutes > 240)) {
    MS_DBG(F("Warning: loggingIntervalMinutes should be between 10 and 240"));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 509, The ANBpH constructors accept
loggingIntervalMinutes but do not enforce the documented 10–240 minute bounds;
add validation in the ANBpH constructor implementation (ANBpH.cpp) after the
initializer list to check loggingIntervalMinutes and handle out-of-range values
(e.g., clamp to the nearest valid value or log a warning and use a default) when
the sensor power is controlled (_powerPin >= 0 or similar), referencing the
ANBpH(byte, Stream*, int8_t, int16_t, int8_t, int8_t, uint8_t) and ANBpH(byte,
Stream&, ...) constructors to locate the code to modify.
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md (1)

104-104: Consider simplifying wording (optional).

The phrase "all of the UUIDs" could be shortened to "all UUIDs" for conciseness, though this is a minor style preference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md` at line 104,
Update the sentence in the README that instructs users to copy UUIDs so it uses
the more concise phrase "all UUIDs" instead of "all of the UUIDs"—specifically
the line mentioning the variable section and the teal "Copy" button; keep the
rest of the wording unchanged.
examples/EnviroDIY_Monitoring_Kit/ReadMe.md (1)

114-114: Verify line number references remain accurate.

The documentation references specific line numbers (e.g., "lines 45-56", "lines 93-108"). These may become stale if the corresponding .ino file is modified in the future.

Consider using section markers or descriptive identifiers rather than line numbers, which are more resilient to code changes. For example, referencing "the Data Logging Options section" instead of specific line numbers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/EnviroDIY_Monitoring_Kit/ReadMe.md` at line 114, The README
currently references fragile line numbers ("lines 45-56", "lines 93-108") which
can become stale; update the documentation to reference stable section headings
or anchor IDs instead (e.g., replace "Customize your data logging options in
lines 45-56" with "Customize your data logging options in the 'Data Logging
Options' section" or an explicit README anchor like "Data Logging Options" and
do the same for other references), and ensure the example .ino file contains
corresponding section comments or anchors (e.g., a "Data Logging Options"
comment block) so readers can reliably locate the code even after edits.
src/sensors/EverlightALSPT19.h (1)

233-239: Consider updating the Doxygen comment to be board-agnostic.

The comment mentions "EnviroDIY Mayfly 1.x" specifically, but the implementation now supports multiple boards with built-in ALS (e.g., Stonefly) via KnownProcessors.h. Consider updating to "boards with built-in ALS" or similar.

📝 Suggested documentation update
     /**
      * `@brief` Construct a new EverlightALSPT19 object with pins and resistors
-     * for the EnviroDIY Mayfly 1.x.
+     * for boards with built-in ALS (e.g., EnviroDIY Mayfly 1.x, Stonefly).
      *
      * This is a short-cut constructor to help users of our own board so they
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/EverlightALSPT19.h` around lines 233 - 239, Update the Doxygen
text on the EverlightALSPT19 constructor to be board-agnostic: replace the
specific "EnviroDIY Mayfly 1.x" wording with a generic phrase like "boards with
built-in ALS" (or "boards with built-in ALS such as EnviroDIY Mayfly or
Stonefly") so the comment better reflects support via KnownProcessors.h; change
the brief description above the EverlightALSPT19 constructor accordingly to
reference supported boards with built-in ALS instead of a single board model.
src/publishers/MonitorMyWatershedPublisher.h (2)

147-149: Inline getEndpoint() creates temporary String objects.

This is acceptable for occasional use, but note that each call allocates and concatenates two String objects. If called frequently, consider caching or using a buffer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 147 - 149,
getEndpoint() currently concatenates monitorMWHost and monitorMWPath into two
temporary String objects on each call; change this to compute and store the full
endpoint once (either during object construction or lazily on first call) into a
member String (e.g., add a private member like endpoint_), and have
getEndpoint() simply return that cached endpoint String (or return a const
reference if signature can be changed) to avoid repeated allocations; ensure the
cache is updated if monitorMWHost or monitorMWPath can change.

264-266: Protected pointer members lack default initialization.

While the constructors set these via setters, consider adding default initialization to prevent potential use of uninitialized pointers if a derived class or future code path bypasses initialization.

♻️ Suggested initialization
-    const char*        monitorMWPath;        ///< The api path
-    const char*        monitorMWHost;        ///< The host name
-    int                monitorMWPort;        ///< The host port
+    const char*        monitorMWPath = nullptr;   ///< The api path
+    const char*        monitorMWHost = nullptr;   ///< The host name
+    int                monitorMWPort = 80;        ///< The host port
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 264 - 266, The
member pointers monitorMWPath and monitorMWHost and the integer monitorMWPort in
class MonitorMyWatershedPublisher are not default-initialized; update the class
definition (MonitorMyWatershedPublisher) to provide safe defaults (e.g., nullptr
for monitorMWPath and monitorMWHost and 0 or -1 for monitorMWPort) so they
cannot be used uninitialized if a derived class or alternate construction path
is added; ensure any constructors/setters (the existing constructor methods that
call setters) remain compatible with these defaults.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

290-292: _initialTransmissionsRemaining decremented even on failed record add.

If record < 0 (line 284), meaning the buffer couldn't add a record, the counter is still decremented. This may be intentional to prevent infinite retries, but could cause premature exhaustion of initial transmissions if the buffer is having issues.

Consider guarding the decrement:

-    if (_initialTransmissionsRemaining > 0) {
+    if (_initialTransmissionsRemaining > 0 && record >= 0) {
         _initialTransmissionsRemaining -= 1;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 290 - 292, The
decrement of _initialTransmissionsRemaining happens unconditionally even when
the buffer add failed (record < 0); change the logic to only decrement
_initialTransmissionsRemaining when the add succeeded (i.e., when record >= 0 or
when the function that returns record indicates success). Locate the spot where
record is set (the check "record < 0") and move or wrap the
"_initialTransmissionsRemaining -= 1;" statement so it executes only in the
success path (or after a confirmed successful add), leaving the failure path
intact (optionally add a log on failure).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ChangeLog.md`:
- Around line 73-79: The ChangeLog.md bullets are repetitive due to many lines
starting with "Removed"; revise the list to improve readability by combining
related removals into single bullets and rephrasing to vary sentence starts—for
example merge the `_maxSamplesToAverage` and `countMaxToAverage()` removal into
one line referencing those symbols, group the doc-copy and override removals by
mentioning `powerUp`/`powerDown` and inherited docs together, and consolidate
the ModSensorConfig.h / TinyGSM / MQTT define warning into a single explanatory
bullet that mentions ModSensorConfig.h, TinyGSM, and MQTT to avoid repetition
while preserving all details.
- Around line 15-41: Edit the ChangeLog entry that explains migrating from
updateAllSensors() to completeUpdate(...) and replace the phrase "all of the
arguments" with the more concise "all the arguments"; ensure this change occurs
in the sentence that references updateAllSensors() and completeUpdate(false,
false, false, false) so the wording reads "set all the arguments to false." Keep
references to updateAllSensors() and completeUpdate(...) unchanged.

In `@docs/Getting-Started/Getting-Started.md`:
- Line 145: The sentence claiming RS485 power management in the double_logger
example is inaccurate; update the Getting-Started.md description to remove or
replace that line so it correctly describes the actual example
(double_logger.ino) which demonstrates logging at two timing intervals (1-minute
and 5-minute) and does not include RS485 code, automatic flow detection, or
power-management logic. Locate the sentence "This example also shows how to stop
power draw from an RS485 adapter with automatic flow detection." and either
delete it or replace it with a brief accurate description referencing
double_logger.ino and its two-interval logging behavior.

In `@examples/example_dependencies.json`:
- Line 29: The dependency entry for Adafruit NeoPixel uses a non-existent
version "~1.15.4"; update the version string in
examples/example_dependencies.json (the Adafruit NeoPixel dependency entry) to a
valid release such as "~1.15.2" (or the current latest) so dependency resolution
succeeds.

In `@src/LoggerModem.h`:
- Around line 991-992: Fix the typo in the doc comment in LoggerModem.h by
inserting the missing word "to" so the brief reads "attached to the hard or
panic reset pin of the modem"; update the comment block describing the digital
pin number (the brief above the hard/panic reset pin member) to use the
corrected phrase to maintain clear documentation.

In `@src/modems/LoggerModemMacros.h`:
- Around line 441-448: The MS_MODEM_CREATE_CLIENT macro (and any similar macros
generating nested TinyGsm types) incorrectly appends the modem type to the
nested class name (e.g., producing TinyGsmSim800::GsmClientSim800); update the
expansion so the nested class uses the fixed name (e.g.,
TinyGsm##TinyGSMType::GsmClient and TinyGsm##TinyGSMType::GsmClientSecure)
instead of GsmClient##TinyGSMType/GsmClientSecure##TinyGSMType, leaving the
outer TinyGsm##TinyGSMType as-is; change the createClient implementation in
MS_MODEM_CREATE_CLIENT (and any other macros referencing
GsmClient/GsmClientSecure) to construct new
TinyGsm##TinyGSMType::GsmClient(gsmModem) (or ::GsmClientSecure) so the nested
class name no longer includes the type suffix.

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-274: The new GeoluxHydroCam constructor signature changed
parameter order (powerPin2 inserted before imageResolution) which breaks
existing positional calls; restore backward compatibility by adding overloads
that match the old signature(s) and delegate to the new constructor (or
alternatively move powerPin2 to the end of the parameter list). Specifically,
add overloads for GeoluxHydroCam(Stream* stream, int8_t powerPin, const char*
imageResolution = "1600x1200", const char* filePrefix = nullptr, bool
alwaysAutoFocus = false) and the matching variant that includes powerPin2 where
appropriate, and implement them in the .cpp to forward-call the new constructor
(e.g., construct with powerPin2 = -1 or pass through) so existing call sites
compile without change; update the corresponding constructors referenced around
lines with the other variant(s) (the ones near 278-282) in the same manner.

In `@src/sensors/TIINA219.cpp`:
- Around line 52-57: Capture and combine the result of Sensor::setup() with the
ina219_phy.begin() call: call Sensor::setup() and assign its boolean result to
success (or success &= pattern) instead of ignoring it, then set success =
success && ina219_phy.begin(_i2c); if the combined success is false, clear the
SETUP_SUCCESSFUL status bit (the same way other sensors do) before returning;
keep the existing wasOn check and powerDown() behavior intact.

In `@src/VariableArray.cpp`:
- Around line 340-362: The variables sName and cycCount are declared only under
the MS_VARIABLEARRAY_DEBUG / MS_VARIABLEARRAY_DEBUG_DEEP guard but are
referenced unconditionally in MS_DBG() calls, causing compile errors when
MS_DEBUGGING_STD is enabled without the VariableArray flags; fix by moving the
declarations of sName and cycCount (built from
sensorList[i]->getSensorNameAndLocation(),
sensorList[i]->getNumberCompleteMeasurementsAttempts(), and
sensorList[i]->getNumberRetryAttemptsMade()) outside the `#if` block so they are
always defined before MS_DBG usage, or alternatively wrap each MS_DBG that uses
sName/cycCount with the same MS_VARIABLEARRAY_DEBUG /
MS_VARIABLEARRAY_DEBUG_DEEP guard to keep them paired with their declarations.

---

Outside diff comments:
In `@build-menu-configurations.ps1`:
- Around line 147-152: The loop variable name is mismatched: you define the
array as $publisherFlag but the Foreach uses $publisherFlags; update either the
declaration or the Foreach so both use the same identifier (e.g., rename the
declaration to $publisherFlags to match the Foreach) so the Foreach
($publisherFlags) iterates the actual array of publishers; ensure any subsequent
references inside the loop use the singular element variable $publisherFlag
consistently.

In `@src/LoggerBase.cpp`:
- Around line 1278-1283: Add a nullptr guard before calling strlen on
_samplingFeatureUUID in the printing block: change the condition to first check
_samplingFeatureUUID is not null (e.g., _samplingFeatureUUID != nullptr) and
then check strlen(_samplingFeatureUUID) > 1, and leave the existing
stream->print/_samplingFeatureUUID printing lines intact; mirror the null-check
pattern used elsewhere for _samplingFeatureUUID in this class.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 214-220: The if/else-if chain that adjusts interval using the
variables percent and interval in MonitorMyWatershedPublisher.cpp is ordered
incorrectly so higher thresholds are shadowed; change the condition order to
check highest thresholds first (if percent >= 90 -> interval = 1; else if
percent >= 75 -> interval /= 4; else if percent >= 50 -> interval /= 2) so the
>=75 and >=90 branches can be reached and the buffer accelerates correctly.

In `@src/sensors/GroPointParent.h`:
- Around line 1-2: The file header comment in GroPointParent.h incorrectly tags
the file as `GroPointParent.cpp`; update the `@file` documentation header to the
correct filename `GroPointParent.h` (or remove the `@file` tag if your style
omits it) so the top-of-file doc matches the actual header; look for the `@file
GroPointParent.cpp` line near the top of GroPointParent.h (around the header
block that documents the GroPointParent class) and replace it with `@file
GroPointParent.h`.

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes references two git filter drivers
(smudgePasswords and disableDebug) that are not defined; add concrete
definitions and setup guidance by either: (a) adding repository-level
documentation (e.g., CONTRIBUTING.md) that instructs contributors to run git
config --local filter.smudgePasswords.smudge /path/to/smudge-script and
filter.smudgePasswords.clean /path/to/clean-script and similarly for
filter.disableDebug, or (b) provide a setup script that registers these
filter.<name>.smudge and filter.<name>.clean commands and/or commits
platform-neutral helper scripts for the smudge/clean actions; ensure the
documentation names the exact filter identifiers smudgePasswords and
disableDebug so contributors can configure them correctly.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to modem.gsmModem.forceModemBaud(...) uses a
method not present in upstream TinyGSM; replace these occurrences (e.g., the
forceModemBaud calls around the modem.gsmModem / modemSerial / modemBaud usage)
with the supported API—either call TinyGsmAutoBaud(modemSerial) to auto-detect
baud or perform an AT sync and then call
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) (or the equivalent
setBaud on the modem object used in this repo). Verify against the repo’s pinned
TinyGSM version and apply the same replacement at the other locations mentioned
(lines ~496-498 and ~509).

In `@examples/examples.dox`:
- Line 70: Update the Doxygen cross-reference on the line referencing `@ref`
examples_outdated to use the renamed page anchor `@ref` page_examples_outdated
(or, if the intent is to link a group, ensure a `@defgroup` named
examples_outdated exists); specifically edit the reference in
examples/examples.dox to replace `@ref` examples_outdated with `@ref`
page_examples_outdated (or add the missing `@defgroup` definition) so the link
resolves correctly.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Line 93: The portal link in ReadMe.md (the line "Go back to the web page for
your site on [Monitor My Watershed](https://monitormywatershed.org)") is already
correctly formatted without the extraneous trailing slash, so no code changes
are required—leave the link text and URL as-is and approve/merge the change.

In `@src/LoggerBase.h`:
- Around line 235-245: Public API methods were renamed from
setinitialShortIntervals/getinitialShortIntervals to
setInitialShortIntervals/getInitialShortIntervals, which will break downstream
users; add backward-compatible wrapper methods named setinitialShortIntervals
and getinitialShortIntervals that forward to the new implementations
(setinitialShortIntervals should call setInitialShortIntervals, and
getinitialShortIntervals should return getInitialShortIntervals or directly
_remainingShortIntervals) and mark them deprecated in the header comment so
callers get a deprecation warning while preserving behavior.

In `@src/modems/EspressifESP32.h`:
- Line 83: The include "TinyGsmClientESP32.h" in EspressifESP32.h may not exist
in upstream TinyGSM; verify whether your project depends on a fork that provides
TinyGsmClientESP32.h or replace it with the upstream header
TinyGsmClientESP8266.h; update EspressifESP32.h to either (a) conditionally
include the correct header based on a defined macro (e.g., USE_TINYGSM_ESP32) or
(b) switch to TinyGsmClientESP8266.h and document the dependency change in
project config (check platformio.ini or library.json) so builds don’t fail when
using the official vshymanskyy/TinyGSM package.
- Line 162: The change of the modem instance type from TinyGsm to TinyGsmESP32
(symbol: gsmModem in EspressifESP32.h / class EspressifESP32) is a public API
breaking change; update the release notes and changelog to document this
breaking change, explicitly state the old type (TinyGsm) and new type
(TinyGsmESP32), list affected headers/APIs (EspressifESP32.h and any public
factory/ctor signatures that expose gsmModem), and include migration guidance
for users on how to update code that relied on the previous TinyGsm type.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The include TinyGsmClientSIM7000SSL.h and type TinyGsmSim7000SSL
appear nonstandard; replace or verify them: if you use the upstream TinyGSM API,
remove the device-specific include and include TinyGsmClient.h, then switch
usages of TinyGsmSim7000SSL to the standard TinyGsm and TinyGsmClientSecure
types (ensure TINY_GSM_MODEM_SIM7000SSL remains defined), otherwise confirm your
forked TinyGSM actually exports TinyGsmClientSIM7000SSL.h and the
TinyGsmSim7000SSL type and update dependency metadata so the header is present
at compile time.

In `@src/modems/SodaqUBeeU201.h`:
- Line 78: Update the modem-specific include and type to match TinyGSM's UBLOX
driver: replace the previous include with "TinyGsmClientUBLOX.h" and ensure the
modem instance uses the TinyGsmUBLOX type (reference the gsmModem variable) so
the SARA U201 builds against the UBLOX implementation; verify the same change is
applied where gsmModem is declared/constructed to keep types consistent.

In `@src/publishers/EnviroDIYPublisher.h`:
- Around line 8-10: The documentation incorrectly calls EnviroDIYPublisher a
"subclass" when it is actually a typedef alias; update the comment to state that
EnviroDIYPublisher is a typedef alias of MonitorMyWatershedPublisher (or an
alias of dataPublisher via typedef) rather than a subclass, and mention it
exists for backward compatibility; reference the typedef name EnviroDIYPublisher
and the target types dataPublisher and MonitorMyWatershedPublisher so readers
can quickly locate and understand the alias.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-55: Update the constructor documentation for
MonitorMyWatershedPublisher() to state that it initializes default host, path,
and port values rather than leaving members unset; locate the
MonitorMyWatershedPublisher() declaration and replace the phrase "with no
members set" with a concise note like "initializes default host, path, and port
values" so the comment matches the constructor's behavior.

In `@src/SensorBase.cpp`:
- Around line 373-384: clearValues() is currently clearing power/wake timing and
status which causes waitForStability() to think the sensor is already stable;
update clearValues() so it does NOT reset _millisPowerOn or
_millisSensorActivated and does NOT clear the POWER_ATTEMPTED, POWER_SUCCESSFUL,
WAKE_ATTEMPTED or WAKE_SUCCESSFUL status bits in the call to clearStatusBits();
only reset measurement-related counters/timings (e.g.,
_measurementAttemptsCompleted, _retryAttemptsMade, _millisMeasurementRequested,
_millisMeasurementCompleted) and clear measurement status bits
(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL) in clearValues().

In `@src/sensors/ANBpH.cpp`:
- Around line 152-176: When _loggingIntervalMinutes == 0 and the sensor is
power-cycled (_powerPin >= 0) the code currently programs interval 0
(continuous) which desyncs timing; change the logic in the block that computes
programmedInterval so that if _loggingIntervalMinutes == 0 and _powerPin >= 0
you set programmedInterval to the minimum allowed (e.g., 10) and log a warning
instead of calling _anb_sensor.setIntervalTime(0). Update the checks around
_loggingIntervalMinutes, ensuring the zero case is handled before applying the
<10 / >240 clamps, and then call _anb_sensor.setIntervalTime(programmedInterval)
(and set intervalSet accordingly) rather than setting 0 when power-cycled.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 66-72: The comment block labeled "ADS Library default settings"
contains ADC part-number typos: replace occurrences of "TI1115" with "ADS1115"
and "TI1015" with "ADS1015" so the comments accurately reflect the
ADS1115/ADS1015 chips; update the three lines referencing TI1115/TI1015 in that
comment block to use ADS1115/ADS1015 respectively.

In `@src/sensors/AtlasParent.cpp`:
- Around line 138-186: The addSingleMeasurementResult() flow currently leaves
success=true even if _i2c->available() becomes 0 while reading values; modify
AtlasParent::addSingleMeasurementResult so that inside the for loop when
_i2c->available() == 0 you set success = false (treat as truncated read), break
the loop, and ensure you do not call verifyAndAddMeasurementResult for missing
entries; finally return bumpMeasurementAttemptCount(success) as before so
truncated I2C reads count as failures.

In `@src/sensors/BoschBME280.cpp`:
- Around line 146-156: The current validity check only guards against all-zero
or very low temp, but allows success = true when pressure/humidity/alt are
invalid (e.g., converted sentinel -9999); update the condition around
verifyAndAddMeasurementResult and success so you only mark success when each
reading is valid: check temp, press, humid, and alt against the invalid sentinel
(e.g., -9999) and temp >= -40 before calling verifyAndAddMeasurementResult for
BME280_TEMP_VAR_NUM, BME280_HUMIDITY_VAR_NUM, BME280_PRESSURE_VAR_NUM and
BME280_ALTITUDE_VAR_NUM and setting success = true; if any value is invalid skip
setting success (and avoid adding that measurement) so retries are not
suppressed.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 210-261: The code currently creates the SD file via
imgFile.open(...) before validating image_size and assigns _filename before
verifying the transfer; to fix, defer creating/opening the file until after you
check image_size != 0 (i.e., move the imgFile.open call to after success &=
image_size != 0 or only open when image_size > 0) and only assign _filename
after confirming success = (bytes_transferred == image_size) (i.e., set
_filename after transferImage(...) and the success check); ensure
imgFile.close() still runs on all error paths and keep watchdog disable/enable
and verifyAndAddMeasurementResult calls around transferImage as now.

In `@src/sensors/GroPointParent.cpp`:
- Around line 210-237: The code currently only writes measurements when both
getValues() and getTemperatureValues() succeed; change it to store partial
results by handling each success flag independently: if success is true, call
verifyAndAddMeasurementResult for M1..M8; if successT is true, call
verifyAndAddMeasurementResult for T1..T13; and update the return to call
bumpMeasurementAttemptCount with (success || successT) instead of (success &&
successT) so a partially successful read is recorded. Reference
functions/variables: verifyAndAddMeasurementResult, success, successT, and
bumpMeasurementAttemptCount.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-266: The file KnownProcessors.h only provides a fallback for
OPERATING_VOLTAGE; you should add similar safe defaults and warning messages for
LOGGER_BOARD, BATTERY_PIN, and BATTERY_MULTIPLIER to avoid build failures on
unknown boards. Add `#ifndef` checks for LOGGER_BOARD, BATTERY_PIN, and
BATTERY_MULTIPLIER and `#define` sensible defaults (e.g., a safe "unknown" board
token for LOGGER_BOARD and neutral numeric defaults for BATTERY_PIN and
BATTERY_MULTIPLIER) plus a `#pragma` message warning that the board is unknown and
instructing how to add real values; mirror the existing pattern used for
OPERATING_VOLTAGE in KnownProcessors.h so compilation proceeds on unsupported
boards.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 172: The current cast masks negative parse errors by converting
_stream->parseInt() to uint16_t before assigning to result; change the flow to
first capture the raw parse value (e.g. use a signed wider type from
_stream->parseInt()), check for negative/error (parsed < 0) and handle that
error case explicitly, then only convert/assign to int16_t result when parsed is
non-negative and within _maxRange; reference _stream->parseInt(), the variable
result, and _maxRange when updating the logic so errors don't wrap into large
positive values.
- Around line 154-192: The loop using rangeAttempts (and blocking
_stream->parseInt()) can run up to 25 attempts and block for seconds; add a
time-budget guard using millis() so the loop exits if elapsed time exceeds the
allowed measurement window (e.g., HRXL_MEASUREMENT_TIME_MS) in addition to the
existing rangeAttempts limit. Implement by capturing unsigned long start =
millis() before the while and change the loop condition to also check (millis()
- start) < HRXL_MEASUREMENT_TIME_MS (or check inside the loop and break),
ensuring when the time budget is exceeded you set result = -9999 and leave
success=false so callers handle the timeout; update references to rangeAttempts,
_stream->parseInt(), _triggerPin, _maxRange, _convertCm, and success
accordingly.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 49-68: In MaximDS3231::addSingleMeasurementResult, guard treating
the read as successful by validating the temperature from rtc.getTemperature()
before calling bumpMeasurementAttemptCount(true); if the temp is NaN (use
std::isnan/::isnan) then do not call bumpMeasurementAttemptCount(true) — instead
report the bad read and call bumpMeasurementAttemptCount(false) or return false
so retries are preserved; keep
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) only for valid
numeric temps and retain the existing debug logs to show the bad-read path.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 37-40: The current guard only checks _dataPin and
_voltageMultiplier but not _operatingVoltage, so ensure you validate
_operatingVoltage > 0 before computing volts: extend the existing conditional
that checks _dataPin and _voltageMultiplier to also check _operatingVoltage
(e.g. _operatingVoltage <= 0), log a clear message via MS_DBG (mentioning
operating voltage), and return bumpMeasurementAttemptCount(false) as done for
the other invalid cases; update any downstream assumptions in the same function
that compute voltage so they only run when _operatingVoltage is valid.

In `@src/sensors/ProcessorStats.cpp`:
- Around line 33-39: Add an inline comment next to the fallback assignment that
explains why unknown SODAQ ONE versions set _batteryMultiplier = -1 (silencing
battery reads) and list the supported versions ("v0.1" and "v0.2"); update the
block that checks _version and assigns _batteryMultiplier so the fallback has a
clear explanatory comment referencing _version and _batteryMultiplier in
ProcessorStats.cpp.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 107-117: The temp_event and humidity_event objects are
uninitialized before calling sht4x_internal.getEvent(), causing undefined reads
if getEvent() fails; initialize both sensors_event_t variables (e.g., zero/clear
them) before the call, check the return value ret_val from
sht4x_internal.getEvent() and only assign temp_val and humid_val (and call
MS_DBG for temperature/humidity) when ret_val indicates success, otherwise set
safe default values or log an error; update the block around temp_event,
humidity_event, sht4x_internal.getEvent(), temp_val, humid_val, and MS_DBG
accordingly.

In `@src/sensors/TIINA219.cpp`:
- Around line 74-108: The TIINA219::addSingleMeasurementResult implementation
already fixes the prior issue by returning early if
getStatusBit(MEASUREMENT_SUCCESSFUL) is false, validating I2C read success with
ina219_phy.success() plus isnan checks for current_mA, busV_V and power_mW, only
calling verifyAndAddMeasurementResult for INA219_CURRENT_MA_VAR_NUM /
INA219_BUS_VOLTAGE_VAR_NUM / INA219_POWER_MW_VAR_NUM when all checks pass, and
reporting final state via bumpMeasurementAttemptCount(success); no code changes
needed—approve this change as-is.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 90-94: The code calls fabs() in TurnerCyclops.cpp (around the
epsilon check comparing _volt_std and _volt_blank) but does not include the math
header; add an explicit `#include` <cmath> at the top of the translation unit so
fabs is declared portably; ensure the include is placed with other system
includes so the comparison logic and the bumpMeasurementAttemptCount(false) call
remain unchanged.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 165-179: The voltage divider factor stored as
_voltageDividerFactor is never applied to adc readings, so adjust the code path
in the measurement handling (the block that uses ads.computeVolts, adcVoltage,
calibResult and calls
verifyAndAddMeasurementResult/TURBIDITY_PLUS_VOLTAGE_VAR_NUM) to scale the raw
voltage: compute adcVoltage = ads.computeVolts(adcCounts), then multiply by
_voltageDividerFactor to get the corrected voltage used for the bounds check,
calibration (calibResult calculation), and the verifyAndAddMeasurementResult
call that stores TURBIDITY_PLUS_VOLTAGE_VAR_NUM; ensure the scaled voltage is
the one logged via MS_DBG and used consistently in
addSingleMeasurementResult/this measurement routine.

---

Nitpick comments:
In `@examples/EnviroDIY_Monitoring_Kit/ReadMe.md`:
- Line 114: The README currently references fragile line numbers ("lines 45-56",
"lines 93-108") which can become stale; update the documentation to reference
stable section headings or anchor IDs instead (e.g., replace "Customize your
data logging options in lines 45-56" with "Customize your data logging options
in the 'Data Logging Options' section" or an explicit README anchor like "Data
Logging Options" and do the same for other references), and ensure the example
.ino file contains corresponding section comments or anchors (e.g., a "Data
Logging Options" comment block) so readers can reliably locate the code even
after edits.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Line 1268: The sentence warning about variable order uses the informal
repetition "very, very, very careful"; replace that phrase with a concise,
professional alternative (e.g., "extremely careful" or "exceptionally careful")
in the line that begins "If using this method, be very, very, very careful to
make sure the order of your variables exactly matches the order of your UUIDs."
to preserve the warning while improving tone.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Line 104: Update the sentence in the README that instructs users to copy UUIDs
so it uses the more concise phrase "all UUIDs" instead of "all of the
UUIDs"—specifically the line mentioning the variable section and the teal "Copy"
button; keep the rest of the wording unchanged.

In `@src/modems/DigiXBeeCellularTransparent.h`:
- Around line 169-172: In class DigiXBeeCellularTransparent ensure consistency
and compile-time safety by adding the missing override specifier to the two
methods that override base-class virtuals: change the declarations of
deleteClient(Client* client) and deleteSecureClient(Client* client) to use
override (matching createClient() and createSecureClient()), or if they are not
overrides remove virtual; update the signatures in DigiXBeeCellularTransparent.h
so deleteClient and deleteSecureClient are explicitly marked override when they
implement base-class methods.

In `@src/modems/LoggerModemMacros.h`:
- Around line 543-551: The macro-generated specificModem::deleteClient currently
static_casts Client* to TinyGsm##TinyGSMType::GsmClient##TinyGSMType* which can
UB if a secure client was passed; update the macro MS_MODEM_DELETE_CLIENT to
perform a runtime-checked cast (use dynamic_cast) and verify the result before
deleting, and if the dynamic_cast fails (or it resolves to a secure client type)
log/ASSERT and skip deletion to avoid incorrect deletes; reference the function
specificModem::deleteClient and the TinyGsm##TinyGSMType::GsmClient##TinyGSMType
and corresponding secure client types when adding the checks.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 290-292: The decrement of _initialTransmissionsRemaining happens
unconditionally even when the buffer add failed (record < 0); change the logic
to only decrement _initialTransmissionsRemaining when the add succeeded (i.e.,
when record >= 0 or when the function that returns record indicates success).
Locate the spot where record is set (the check "record < 0") and move or wrap
the "_initialTransmissionsRemaining -= 1;" statement so it executes only in the
success path (or after a confirmed successful add), leaving the failure path
intact (optionally add a log on failure).

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 147-149: getEndpoint() currently concatenates monitorMWHost and
monitorMWPath into two temporary String objects on each call; change this to
compute and store the full endpoint once (either during object construction or
lazily on first call) into a member String (e.g., add a private member like
endpoint_), and have getEndpoint() simply return that cached endpoint String (or
return a const reference if signature can be changed) to avoid repeated
allocations; ensure the cache is updated if monitorMWHost or monitorMWPath can
change.
- Around line 264-266: The member pointers monitorMWPath and monitorMWHost and
the integer monitorMWPort in class MonitorMyWatershedPublisher are not
default-initialized; update the class definition (MonitorMyWatershedPublisher)
to provide safe defaults (e.g., nullptr for monitorMWPath and monitorMWHost and
0 or -1 for monitorMWPort) so they cannot be used uninitialized if a derived
class or alternate construction path is added; ensure any constructors/setters
(the existing constructor methods that call setters) remain compatible with
these defaults.

In `@src/sensors/ANBpH.h`:
- Around line 478-509: The ANBpH constructors accept loggingIntervalMinutes but
do not enforce the documented 10–240 minute bounds; add validation in the ANBpH
constructor implementation (ANBpH.cpp) after the initializer list to check
loggingIntervalMinutes and handle out-of-range values (e.g., clamp to the
nearest valid value or log a warning and use a default) when the sensor power is
controlled (_powerPin >= 0 or similar), referencing the ANBpH(byte, Stream*,
int8_t, int16_t, int8_t, int8_t, uint8_t) and ANBpH(byte, Stream&, ...)
constructors to locate the code to modify.

In `@src/sensors/EverlightALSPT19.h`:
- Around line 233-239: Update the Doxygen text on the EverlightALSPT19
constructor to be board-agnostic: replace the specific "EnviroDIY Mayfly 1.x"
wording with a generic phrase like "boards with built-in ALS" (or "boards with
built-in ALS such as EnviroDIY Mayfly or Stonefly") so the comment better
reflects support via KnownProcessors.h; change the brief description above the
EverlightALSPT19 constructor accordingly to reference supported boards with
built-in ALS instead of a single board model.

In `@src/sensors/MaximDS18.cpp`:
- Around line 184-208: Replace the literal -127 sentinel with the
DallasTemperature library constant to improve clarity: in the measurement
handling code (check using getStatusBit(MEASUREMENT_SUCCESSFUL) then calling
_internalDallasTemp.getTempC(_OneWireAddress) and deciding success via result),
change the comparison that currently checks "result != 85 && result != -127" to
use DEVICE_DISCONNECTED_C for the disconnected-case check; keep the same logic
path that calls verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result) and
returns bumpMeasurementAttemptCount(success).

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 47-51: The code performs two redundant ADC priming reads before
capturing rawAnalog from analogRead(_dataPin); remove one of the priming
analogRead(_dataPin) calls so only a single priming read remains (keep
pinMode(_dataPin, INPUT) and then one analogRead for stabilization followed by
the actual read that assigns float rawAnalog). Update any comments to reflect a
single priming read and ensure no other logic depends on the second priming
call.

In `@src/sensors/ProcessorAnalog.h`:
- Around line 144-156: The else branch currently silently defaults
PROCESSOR_ANALOG_RESOLUTION to 3 when MS_PROCESSOR_ADC_RESOLUTION is
unspecified/unsupported; update the `#else` block (near the existing
MS_PROCESSOR_ADC_RESOLUTION checks and PROCESSOR_ANALOG_RESOLUTION definition)
to emit a compiler diagnostic (e.g., `#warning` or `#pragma` message with a clear
text like "Unsupported MS_PROCESSOR_ADC_RESOLUTION; defaulting to 10-bit
precision") so users are warned of the fallback, then keep the default define;
ensure the diagnostic references MS_PROCESSOR_ADC_RESOLUTION and
PROCESSOR_ANALOG_RESOLUTION for clarity.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 107-118: The range-check branch for adcVoltage in
TurnerCyclops.cpp currently only logs and adds results when inside range; when
the check fails there is no diagnostic output—add a debug log (using MS_DBG or
the existing logging facility) that prints the out-of-range adcVoltage value and
reason before calling bumpMeasurementAttemptCount(success) so operators can see
when adcVoltage is outside the (-0.3, 3.6) window; update the code around the
adcVoltage check (the block using calibResult, verifyAndAddMeasurementResult and
the final return calling bumpMeasurementAttemptCount(success)) to emit this log
when success is false.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
examples/ReadMe.md (1)

192-193: ⚠️ Potential issue | 🟠 Major

Fix the GitHub link for the EnviroDIY LTEbee DRWI example.

Line 193 links to DRWI_DigiLTE, which is the same folder referenced at line 183 for the Digi LTE example. This is a copy-paste error. Verify the correct example folder for EnviroDIY LTEbee and update the link accordingly. The available DRWI example folders in the repository are: DRWI_2G, DRWI_Mayfly1, DRWI_NoCellular, DRWI_SIM7080LTE, DRWI_DigiLTE, and DRWI_Mayfly1_WiFi.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ReadMe.md` around lines 192 - 193, The GitHub link currently points
to the wrong folder (DRWI_DigiLTE); update the link target to the correct
EnviroDIY LTEbee DRWI example folder by replacing DRWI_DigiLTE with
DRWI_SIM7080LTE in the README entry (the markdown list item referencing the
EnviroDIY LTEbee DRWI Citizen Science example).
src/sensors/ANBpH.h (1)

478-502: ⚠️ Potential issue | 🟡 Minor

Consider validating logging interval bounds at construction time.

Validation already exists in the setup() method (lines 153–169 of ANBpH.cpp) where out-of-range values are clamped to 10–240 minutes. However, the unclamped value is stored in the constructor without validation, leaving a window where invalid state could exist before setup() is called. Moving the guard to the constructor ensures the invariant is enforced immediately and simplifies the class contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 502, The constructor ANBpH::ANBpH
currently stores the raw loggingIntervalMinutes without bounds checking while
ANBpH::setup() clamps it to 10–240 (or allows 0 for always‑powered); move or
duplicate the same validation logic into the ANBpH::ANBpH constructor so
loggingIntervalMinutes is validated/clamped immediately (preserve the 0
special-case for always‑powered), leaving setup() either to assume the invariant
or to keep a defensive no-op clamp; update any constructor parameters or member
initialization that set loggingIntervalMinutes so the class never holds an
out‑of‑range value before setup() is called.
🧹 Nitpick comments (11)
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md (1)

108-110: Tighten phrasing for concision.

Consider dropping “of” in “containing all of the UUIDs” → “containing all the UUIDs” for a cleaner sentence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md`
around lines 108 - 110, Update the phrasing in the README paragraph that
instructs pasting the copied code: change "containing all of the UUIDs" to
"containing all the UUIDs" in the sentence that follows the instruction to use
the teal "Copy" button and paste into the variable array section, leaving the
rest of the wording unchanged.
examples/menu_a_la_carte/ReadMe.md (1)

1267-1268: Consider toning down repeated intensifier.

“very, very, very careful” reads informal; consider a single intensifier to keep the docs crisp.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/ReadMe.md` around lines 1267 - 1268, Replace the
informal repeated intensifier "very, very, very careful" in the README text with
a single, crisper intensifier such as "very careful" or "extremely careful" (the
exact phrase appears in the sentence starting "If using this method, be very,
very, very careful..."); update that sentence to read e.g. "If using this
method, be very careful to make sure the order of your variables exactly matches
the order of your UUIDs." to keep the docs concise and professional.
src/sensors/FreescaleMPL115A2.h (1)

38-39: Minor: Documentation references wrong sensor.

Line 38 mentions "AM2315" but this file documents the MPL115A2 sensor. This appears to be a pre-existing copy-paste error in the documentation.

-* `@note` Software I2C is *not* supported for the AM2315.
+* `@note` Software I2C is *not* supported for the MPL115A2.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/FreescaleMPL115A2.h` around lines 38 - 39, Update the header
documentation in FreescaleMPL115A2.h to correct the mistaken sensor name:
replace the reference to "AM2315" with "MPL115A2" in the comment block that
discusses I2C support (the file-level docs for the FreescaleMPL115A2 sensor).
Ensure the note now correctly reads that Software I2C is not supported for the
MPL115A2 (or remove the sentence if inaccurate) so the documentation matches the
FreescaleMPL115A2 implementation.
src/sensors/FreescaleMPL115A2.cpp (1)

80-90: Consider extracting validation range constants.

The magic numbers for range validation reduce expressiveness. Defining named constants would improve maintainability and make the expected sensor operating ranges explicit.

♻️ Suggested refactor

Add constants near the top of the file or in the header:

// MPL115A2 valid measurement ranges
constexpr float MPL115A2_TEMP_MIN    = -20.0;
constexpr float MPL115A2_TEMP_MAX    =  85.0;
constexpr float MPL115A2_PRESS_MIN   =  50.0;
constexpr float MPL115A2_PRESS_MAX   = 115.0;

Then update the validation:

-    if (!isnan(temp) && !isnan(press) && press >= 50.0 && press <= 115.0 &&
-        temp >= -20.0 && temp <= 85.0) {
+    if (!isnan(temp) && !isnan(press) &&
+        press >= MPL115A2_PRESS_MIN && press <= MPL115A2_PRESS_MAX &&
+        temp >= MPL115A2_TEMP_MIN && temp <= MPL115A2_TEMP_MAX) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/FreescaleMPL115A2.cpp` around lines 80 - 90, Extract the
hard-coded validation bounds into named constants and use them in the
measurement check: add constexpr floats for MPL115A2_TEMP_MIN,
MPL115A2_TEMP_MAX, MPL115A2_PRESS_MIN, MPL115A2_PRESS_MAX (either at the top of
this source file or in the corresponding header), then replace the numeric
literals in the if-condition that calls
verifyAndAddMeasurementResult(MPL115A2_TEMP_VAR_NUM, temp) and
verifyAndAddMeasurementResult(MPL115A2_PRESSURE_VAR_NUM, press) so the condition
uses the new constants before returning bumpMeasurementAttemptCount(success).
src/sensors/ApogeeSQ212.cpp (1)

96-108: Add debug output when voltage is out of expected range.

When adcVoltage falls outside the valid range (-0.3, 3.6), the function silently fails without logging why the measurement was rejected. This makes debugging sensor issues difficult, especially given the @todo note about unverified voltage ranges for 3.3V supply.

📝 Proposed fix to add debug logging
     // `@todo` Verify the voltage range for the SQ-212 sensor
     // Here we are using the range of the ADS when it is powered at 3.3V
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage"), adcVoltage, F("out of expected range (-0.3, 3.6)"));
     }

Regarding the @todo comment about verifying voltage range for the SQ-212 with 3.3V supply - would you like me to open an issue to track this verification task?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 108, The code currently
ignores adcVoltage values outside (-0.3, 3.6); add an explicit debug log so
out-of-range readings are visible: inside the same function in ApogeeSQ212.cpp,
after the if (adcVoltage < 3.6 && adcVoltage > -0.3) block add an else (or an
early check) that calls MS_DBG (or the same logging macro used in this file) to
print a clear message including the adcVoltage value and the expected range
(e.g. "ApogeeSQ212: adcVoltage out of range", adcVoltage, "expected
(-0.3,3.6)"), then let the function proceed to
bumpMeasurementAttemptCount(false) as before; reference symbols:
SQ212_CALIBRATION_FACTOR, verifyAndAddMeasurementResult, SQ212_PAR_VAR_NUM,
SQ212_VOLTAGE_VAR_NUM, and bumpMeasurementAttemptCount to locate the surrounding
code.
src/sensors/SensirionSHT4x.cpp (2)

112-123: Consider extracting values only after confirming getEvent success.

The current flow extracts and logs values (lines 112-117) before checking ret_val (line 119). If getEvent fails, the debug output will show 0.0 for both readings (from zero-initialized structs), followed by "getEvent failed" — potentially confusing since 0°C and 0% humidity are valid sensor values.

A cleaner approach would move extraction and debug logging inside the success path:

♻️ Suggested restructure for clearer debug output
     ret_val = sht4x_internal.getEvent(&humidity_event, &temp_event);

-    // get the values from the sensor events
-    temp_val  = temp_event.temperature;
-    humid_val = humidity_event.relative_humidity;
-
-    MS_DBG(F("  Temp:"), temp_val, F("°C"));
-    MS_DBG(F("  Humidity:"), humid_val, '%');
-
-    if (!ret_val) {
+    if (!ret_val) {
         MS_DBG(F("  getEvent failed; no values read!"));
-    } else if (isnan(temp_val) || isnan(humid_val)) {
-        MS_DBG(F("  Invalid measurement values"));
-    }
-
-    if (ret_val && !isnan(temp_val) && !isnan(humid_val)) {
+    } else {
+        // get the values from the sensor events
+        temp_val  = temp_event.temperature;
+        humid_val = humidity_event.relative_humidity;
+
+        MS_DBG(F("  Temp:"), temp_val, F("°C"));
+        MS_DBG(F("  Humidity:"), humid_val, '%');
+
+        if (isnan(temp_val) || isnan(humid_val)) {
+            MS_DBG(F("  Invalid measurement values"));
+        } else {
             verifyAndAddMeasurementResult(SHT4X_TEMP_VAR_NUM, temp_val);
             verifyAndAddMeasurementResult(SHT4X_HUMIDITY_VAR_NUM, humid_val);
             success = true;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/SensirionSHT4x.cpp` around lines 112 - 123, Move the extraction
and debug logging of sensor readings so they only run after a successful
getEvent call: after checking ret_val (the result of getEvent), assign temp_val
= temp_event.temperature and humid_val = humidity_event.relative_humidity and
then call MS_DBG for Temp and Humidity; keep the failure branch (when !ret_val)
to log "getEvent failed" and the isnan check in the success branch to handle
invalid measurements. Ensure you reference temp_event, humidity_event, temp_val,
humid_val, ret_val and MS_DBG when relocating the code.

165-167: Informational: Inconsistent initialization in sleep() function.

The sensor events in sleep() (lines 165-166) are not brace-initialized, unlike the fix applied to addSingleMeasurementResult. While the comment (lines 158-164) notes these values aren't used, applying consistent initialization would align with the fix pattern. This is low-priority since the values are discarded.

♻️ Optional consistency fix
-    sensors_event_t temp_event;
-    sensors_event_t humidity_event;
+    sensors_event_t temp_event{};
+    sensors_event_t humidity_event{};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/SensirionSHT4x.cpp` around lines 165 - 167, In the sleep()
routine the local variables temp_event and humidity_event are declared without
brace-initialization before calling sht4x_internal.getEvent(&humidity_event,
&temp_event); update their declarations to use brace-initialization (matching
the pattern used in addSingleMeasurementResult) so they are zero-initialized
(e.g., declare sensors_event_t temp_event{} and sensors_event_t
humidity_event{}), then call getEvent as before; no other logic changes
required.
src/sensors/TurnerTurbidityPlus.cpp (1)

170-182: Voltage divider factor is now correctly applied.

The previous review identified that _voltageDividerFactor was stored but never used. This is now fixed at line 174 where the factor is applied after ADC bounds validation but before calibration—the correct sequence since the bounds check validates ADC hardware output range while the factor converts to actual sensor voltage.

Consider validating _voltageDividerFactor: If this value is zero or negative (due to misconfiguration), it would produce invalid results without warning. A runtime check or constructor validation could improve robustness.

🛡️ Optional: Add validation for voltage divider factor
     // The ADS1X15 outputs a max value corresponding to Vcc + 0.3V
     if (adcVoltage < 5.3 && adcVoltage > -0.3) {
+        if (_voltageDividerFactor <= 0) {
+            MS_DBG(F("  Invalid voltage divider factor:"), _voltageDividerFactor);
+            return bumpMeasurementAttemptCount(false);
+        }
         // Apply voltage divider factor if using a voltage divider to step down
         // the voltage
         adcVoltage *= _voltageDividerFactor;

Alternatively, validate in the constructor to fail early.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.cpp` around lines 170 - 182, Add a runtime
validation for _voltageDividerFactor before it's applied: check that
_voltageDividerFactor > 0 immediately before the adcVoltage *=
_voltageDividerFactor line (in the block that computes calibResult); if the
factor is <= 0, log an error/warning (using existing logging like MS_DBG or
MS_ERROR), skip applying the factor and skip adding results (set success to
false or return early) to avoid producing invalid measurements—this keeps
verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, ...) and
TURBIDITY_PLUS_VOLTAGE_VAR_NUM from being called with bad values.
src/sensors/MeaSpecMS5803.cpp (1)

58-87: Use _maxPressure instead of a hard-coded ceiling.

This keeps validation aligned with the configured sensor range.
As per coding guidelines: "Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance."

♻️ Suggested change
-    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
-        press != 0.0 && press <= 14000.0) {
+    if (!isnan(temp) && !isnan(press) && temp >= -40.0 && temp <= 85.0 &&
+        press != 0.0 && press <= _maxPressure) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MeaSpecMS5803.cpp` around lines 58 - 87, The pressure validation
currently uses a hard-coded ceiling (14000.0); update the conditional in the
measurement check to use the sensor-configured upper bound _maxPressure instead
(i.e. replace the "press <= 14000.0" check with "press <= _maxPressure") so
verifyAndAddMeasurementResult(MS5803_PRESSURE_VAR_NUM, press) uses the
configured range; keep the rest of the checks (isnan, temp range, press != 0.0)
intact and ensure _maxPressure is in the same units as the returned press value
before comparison.
src/modems/LoggerModemMacros.h (1)

460-471: Note: TinyGSMType parameter unused in null client macro.

The MS_MODEM_CREATE_NULL_SECURE_CLIENTS macro accepts TinyGSMType but doesn't use it since it only returns nullptr. This is intentional for API consistency across modem types, but consider adding a comment or (void)TinyGSMType to suppress potential unused parameter warnings if any arise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/LoggerModemMacros.h` around lines 460 - 471, The
MS_MODEM_CREATE_NULL_SECURE_CLIENTS macro declares a TinyGSMType parameter that
is never referenced, which may trigger unused-parameter warnings; modify the
macro body to explicitly mark TinyGSMType as used (for example by adding a noop
cast or comment referencing TinyGSMType) so the compiler won't warn, keeping the
existing createSecureClient implementations in specificModem unchanged (symbols:
MS_MODEM_CREATE_NULL_SECURE_CLIENTS, TinyGSMType,
specificModem::createSecureClient).
src/sensors/ProcessorAnalog.cpp (1)

52-56: Consider reducing to a single priming read.

Two priming reads are performed (lines 53 and 55) before the actual read. While this may improve ADC settling, most implementations use a single priming read. The extra read adds marginal overhead.

Also, rawAnalog is declared as float but analogRead() returns an integer. Consider using an explicit cast for clarity:

-    analogRead(_dataPin);  // another priming reading
-    float rawAnalog = analogRead(_dataPin);
+    analogRead(_dataPin);  // priming reading
+    float rawAnalog = static_cast<float>(analogRead(_dataPin));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.cpp` around lines 52 - 56, Reduce the redundant
ADC priming by keeping only one priming call to analogRead() after
pinMode(_dataPin, INPUT) (remove the extra priming call) and then perform the
actual read into rawAnalog; also make the type/intent explicit by either
declaring rawAnalog as an int (e.g., int rawAnalog) or keep float but cast the
analogRead() result when assigning (e.g., rawAnalog =
static_cast<float>(analogRead(_dataPin))) so the analogRead() integer return is
clear in ProcessorAnalog.cpp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/Getting-Started/Getting-Started.md`:
- Line 144: Update the sentence that currently claims one logger writes all
variables to SD and another sends unique results to Monitor My Watershed to
accurately describe the implemented double-logger behavior: replace the line
beginning "Because each sensor outputs temperature..." with text stating that
both loggers write to the SD card, each records different sets of variables at
different intervals, and they create separate CSV files on the SD card; remove
references to publishing to Monitor My Watershed and to unique-result filtering
so the wording matches the actual double_logger implementation.

In `@examples/AWS_IoT_Core/ReadMe.md`:
- Line 64: The README contains inconsistent casing: change the all-caps word
"UUIDS" to "UUIDs" where it appears in the sentence that instructs replacing
"12345678-abcd-1234-ef00-1234567890ab" with your variables' UUIDs; locate the
string "12345678-abcd-1234-ef00-1234567890ab" and update the trailing word
"UUIDS" to "UUIDs" so casing matches the rest of the document.

In `@src/ModSensorConfig.h`:
- Around line 9-15: Update the note comment in ModSensorConfig.h to fix the
grammar: change "must be include" to "must be included" and ensure the sentence
reads clearly (e.g., "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."). Locate the comment block containing the note in
ModSensorConfig.h and replace the incorrect phrase while preserving the original
intent and punctuation.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 105-106: The comment incorrectly spells "ADD" instead of "ADC";
update the comment string that currently reads "auxiliary ADD object" to
"auxiliary ADC object" wherever the ADC setup comment appears (the block that
starts "// Create an auxiliary ADD object" describing creation/setup of the ADC
object in TurnerTurbidityPlus.cpp), leaving the rest of the comment unchanged.

---

Outside diff comments:
In `@examples/ReadMe.md`:
- Around line 192-193: The GitHub link currently points to the wrong folder
(DRWI_DigiLTE); update the link target to the correct EnviroDIY LTEbee DRWI
example folder by replacing DRWI_DigiLTE with DRWI_SIM7080LTE in the README
entry (the markdown list item referencing the EnviroDIY LTEbee DRWI Citizen
Science example).

In `@src/sensors/ANBpH.h`:
- Around line 478-502: The constructor ANBpH::ANBpH currently stores the raw
loggingIntervalMinutes without bounds checking while ANBpH::setup() clamps it to
10–240 (or allows 0 for always‑powered); move or duplicate the same validation
logic into the ANBpH::ANBpH constructor so loggingIntervalMinutes is
validated/clamped immediately (preserve the 0 special-case for always‑powered),
leaving setup() either to assume the invariant or to keep a defensive no-op
clamp; update any constructor parameters or member initialization that set
loggingIntervalMinutes so the class never holds an out‑of‑range value before
setup() is called.

---

Duplicate comments:
In @.gitattributes:
- Around line 34-38: Add definitions and contributor setup for the git filter
drivers referenced in .gitattributes so the smudgePasswords and disableDebug
filters are recognized; specifically, define filter.smudgePasswords and
filter.disableDebug driver settings (or document how contributors should
configure them) and include example git config commands or a CONTRIBUTING note
so clones won’t emit warnings. Update the repository config or docs to show the
exact filter.<symbol>smudgePasswords</symbol> and <symbol>disableDebug</symbol>
(and any <symbol>filter.*</symbol>) must be declared and instructions provided
for maintainers/contributors.

In `@ChangeLog.md`:
- Around line 74-80: The listed "Removed" bullets are repetitive; edit
ChangeLog.md to consolidate them into one or two combined sentences—for example,
one sentence that groups the related removals (e.g., "Removed unused
`_maxSamplesToAverage` parameter and its `countMaxToAverage()` setter,
eliminated redundant doc copy calls and unneeded overrides of
`powerUp`/`powerDown` for sensors with built-in dual power pins, and removed
references to the EnviroDIY data portal") and keep the final note about
defines/TinyGSM/ModSensorConfig.h as a separate explanatory bullet; ensure you
reference `_maxSamplesToAverage`, `countMaxToAverage()`, `powerUp`, `powerDown`,
TinyGSM, and ModSensorConfig.h so reviewers can locate the affected items.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 475-498: The call to modem.gsmModem.forceModemBaud(...) is not a
standard TinyGSM API and will fail to compile; replace that call with the
correct TinyGSM method (e.g., use modem.gsmModem.setBaud(modemBaud) or
TinyGsmAutoBaud(modemSerial, modemBaud) depending on which API your TinyGSM
version supports), ensure the argument types match (replace the
static_cast<uint32_t> if the replacement expects an int or long), and update
both occurrences (the ESP block and the USE_CELLULAR_BEE block) so the code uses
the supported API consistently and handles a failed baud-change attempt
gracefully.

In `@examples/example_dependencies.json`:
- Around line 26-30: The dependency entry for "Adafruit NeoPixel" currently pins
"version": "~1.15.4"; confirm that tag "1.15.4" is actually published in the
Adafruit_NeoPixel repo (via GitHub tags or package registry) and if it is not,
replace "~1.15.4" with a valid published tag/version (or the latest stable tag)
and ensure the version string format matches the dependency resolver (remove or
change the tilde if your resolver requires exact tags); update the "version"
field in the same entry so dependency resolution succeeds.

In `@examples/examples.dox`:
- Around line 67-77: Update the broken cross-reference by replacing the old
anchor name "examples_outdated" with the renamed anchor "page_examples_outdated"
in the reference; specifically change the reference token `@ref` examples_outdated
to `@ref` page_examples_outdated so it matches the existing page anchor symbol
page_examples_outdated used elsewhere in the file.

In `@src/LoggerBase.h`:
- Around line 246-254: Add proper Doxygen comments to the deprecated
compatibility shims so documentation generation stops warning: document the
parameter for setinitialShortIntervals (int16_t initialShortIntervals) with a
brief description and mark the method `@deprecated` pointing to
setInitialShortIntervals, and document the return value for
getinitialShortIntervals (int16_t) with a brief description and mark it
`@deprecated` pointing to getInitialShortIntervals; place the comments immediately
above the setinitialShortIntervals and getinitialShortIntervals declarations so
Doxygen recognizes the parameter and return tags.

In `@src/modems/EspressifESP32.h`:
- Line 162: Public member gsmModem was changed to the concrete TinyGsmESP32 type
which may break downstream code; search and audit all call sites that reference
gsmModem, update callers to either use the concrete TinyGsmESP32 API or change
the EspressifESP32 declaration back to a more generic type (or provide a
non-breaking accessor). Specifically: locate uses of gsmModem, TinyGsmESP32, and
class EspressifESP32, verify each caller compiles against TinyGsmESP32 methods,
and if any callers expect a generic modem interface, either (A) change the
member back to the generic/base modem type and expose it (e.g., a base class or
interface), or (B) make gsmModem private and add a typed getter that returns the
appropriate abstraction to preserve ABI/compile-time compatibility.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The code includes and uses TinyGsmClientSIM7000SSL and
TinyGsmSim7000SSL which may not exist in the EnviroDIY TinyGSM fork; verify
those symbols exist and if not, change to the generic TinyGsmClient.h approach:
use the TINY_GSM_MODEM_SIM7000SSL macro (or appropriate modem macro present in
the fork) and the standard TinyGsmClientSecure/TinyGsmClient types instead of
TinyGsmClientSIM7000SSL/TinyGsmSim7000SSL; update the include from
TinyGsmClientSIM7000SSL.h to TinyGsmClient.h and replace uses of
TinyGsmSim7000SSL with the standard TinyGsmClientSecure (or the modem typedef
provided by the fork) so compilation works against the EnviroDIY fork.

In `@src/modems/SodaqUBeeU201.h`:
- Around line 78-79: The include swap is correct and requires no code changes:
keep the header TinyGsmClientUBLOX.h in src/modems/SodaqUBeeU201.h and retain
LoggerModem.h as-is (no modifications needed to the includes or related
initialization code referencing the u-blox modem family).
- Line 235: The public API now exposes TinyGsmUBLOX instead of TinyGsm (see the
variable declaration TinyGsmUBLOX gsmModem), which is a breaking change; update
the project release notes and changelog to explicitly state the type change from
TinyGsm to TinyGsmUBLOX, include migration guidance for downstream users (what
to replace, any constructor or API differences), and mark the change in the
version bump/compatibility section so consumers are aware.

In `@src/sensors/AtlasParent.cpp`:
- Around line 174-190: The parsing loop in AtlasParent.cpp correctly marks
overall success=false when `_i2c->available() == 0` and continues to parse
remaining values, so no code changes are required; keep the logic in the loop
that sets `success = false` on incomplete reads, continues on individual invalid
results (using `isnan(result)` or `result < -1020`), and still calls
`verifyAndAddMeasurementResult(i, result)` so that `bumpMeasurementAttemptCount`
receives the correct failure status.

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-282: The constructors GeoluxHydroCam(Stream* stream, int8_t
powerPin, Logger& baseLogger, int8_t powerPin2 = -1, const char* imageResolution
= "1600x1200", const char* filePrefix = nullptr, bool alwaysAutoFocus = false)
and GeoluxHydroCam(Stream& stream, int8_t powerPin, Logger& baseLogger, int8_t
powerPin2 = -1, const char* imageResolution = "1600x1200", const char*
filePrefix = nullptr, bool alwaysAutoFocus = false) changed the parameter order
and break callers that pass the resolution as the 4th argument; restore
backwards compatibility by either (A) adding overloads that match the old
signature (Stream*/Stream& , int8_t powerPin, Logger&, const char*
imageResolution, const char* filePrefix = nullptr, bool alwaysAutoFocus = false)
which internally forward to the new constructors, or (B) move powerPin2 to the
end of the parameter list so the existing signature GeoluxHydroCam(..., Logger&,
const char* imageResolution, ...) continues to work; update both Stream* and
Stream& constructor declarations (GeoluxHydroCam(...)) accordingly and ensure
default values and forwarding preserve existing behavior.

In `@src/sensors/GroPointParent.cpp`:
- Around line 165-239: The function addSingleMeasurementResult currently returns
bumpMeasurementAttemptCount((success && successT)) which treats partial reads as
overall failures; if partial success should be considered a successful
measurement for retry logic, change the final return to use logical OR by
passing (success || successT) to bumpMeasurementAttemptCount (reference
addSingleMeasurementResult and bumpMeasurementAttemptCount), leaving the
existing per-value verifyAndAddMeasurementResult calls intact so moisture and
temperature results are still stored when available.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop in MaxBotixSonar (the while using
rangeAttempts and _stream->parseInt()) can exceed the allowed measurement time
because it unconditionally allows up to 25 attempts while each parse/timeout may
be long; change this to enforce a total time budget rather than a fixed attempt
count: capture a start timestamp (millis or micros) before the loop and break if
elapsed time exceeds the sensor measurement budget, and reduce or adapt
rangeAttempts accordingly; keep the existing trigger logic that toggles
_triggerPin, but stop retrying when the total elapsed time approaches the
measurement timeout or when success is true, and ensure result handling
(_maxRange, _convertCm) remains the same.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 58-69: The call to rtc.getTemperature() may return NaN on I2C
failure, but the code always treats the measurement as successful by calling
bumpMeasurementAttemptCount(true); update the logic in the measurement routine
so you validate the reading (use std::isnan() or std::isfinite() on the float
returned by rtc.getTemperature()), only call
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) when the value is
valid, and call bumpMeasurementAttemptCount(false) when the temperature is
invalid to allow retries; keep the existing debug logs but add an error/debug
log branch for NaN/invalid temperatures before returning.

In `@src/sensors/ProcessorStats.cpp`:
- Around line 22-48: Constructor initialization is correct and no changes are
needed: defaults are set via _operatingVoltage, _batteryPin, and
_batteryMultiplier from macros then overridden per-board in the version-specific
blocks (checks using strcmp(_version, ...) within the
ARDUINO_AVR_ENVIRODIY_MAYFLY, ARDUINO_SODAQ_ONE / ARDUINO_SODAQ_ONE_BETA, and
ARDUINO_SODAQ_AUTONOMO sections); leave these checks and assignments as-is and
keep the comment documenting SODAQ ONE v0.1/v0.2 support.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 105-121: Capture the return value of _i2c->requestFrom(...) into a
variable (e.g., uint8_t readCount or int readCount) and validate it before
parsing: accept only expected counts (1 or 4), log and call
bumpMeasurementAttemptCount(false) on unexpected counts; then read exactly
readCount bytes into SerialBuffer (limit to 4) and ensure byte_in equals
readCount before proceeding with version or tip-total parsing; update the
while/read logic that currently uses _i2c->available() and byte_in to instead
bound reads by readCount, and use getSensorNameAndLocation() in the error log to
aid debugging.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 107-110: The previous undefined-initialization issue is fixed by
brace-initializing sensors_event_t temp_event{} and sensors_event_t
humidity_event{} before calling sht4x_internal.getEvent(&humidity_event,
&temp_event); — keep these brace-initializers (or an explicit zeroing) and
ensure the getEvent call uses the addresses of humidity_event and temp_event as
shown; no other changes required to the temp_event/humidity_event initialization
or the getEvent invocation in SensirionSHT4x.cpp.

In `@src/sensors/TIINA219.cpp`:
- Around line 40-64: TIINA219::setup currently ignores the boolean return from
Sensor::setup(); capture that return (e.g., bool baseOk = Sensor::setup()) and
combine it with the ina219_phy.begin(_i2c) result (final success = baseOk &&
ina219_phy.begin(...)); preserve the power-on/warm-up logic (checkPowerOn(),
powerUp(), waitForWarmUp(), powerDown()) and when overall success is false
setStatusBit(ERROR_OCCURRED) and clearStatusBit(SETUP_SUCCESSFUL) as before,
returning the combined success value.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 107-118: The failure path in TurnerCyclops::... leaves
success=false but doesn't record explicit bad values; update the out-of-range
branch so when adcVoltage is not between  -0.3 and 3.6 you call
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, -9999) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, -9999) (or the same
sentinel used by clearValues()), ensure calibResult is not used in that branch,
and then return bumpMeasurementAttemptCount(success) as before; this keeps
behavior consistent with clearValues() and other sensors that record sentinel
bad values.
- Around line 87-94: The code uses fabs() in TurnerCyclops.cpp (near the
calibration check comparing _volt_std and _volt_blank using epsilon) without
explicitly including <cmath>, which can hurt portability; add an explicit
`#include` <cmath> at the top of the translation unit (or the header that defines
TurnerCyclops symbols) so fabs() is guaranteed to be declared, then rebuild to
ensure the calibration validation (epsilon, fabs, _volt_std, _volt_blank)
compiles on all toolchains.

In `@src/VariableArray.cpp`:
- Around line 347-371: The local variables sName and cycCount are only declared
inside MS_VARIABLEARRAY_DEBUG/DEEP blocks but are used in unconditional
MS_DBG(...) calls; fix by declaring and initializing sName and cycCount
unconditionally (outside/above the `#if`) or by wrapping the MS_DBG(...) call in
the same `#if` so the variables and usage are consistently guarded; update the
declarations near where sensorList[i]->getSensorNameAndLocation() and
getNumberCompleteMeasurementsAttempts()/getNumberRetryAttemptsMade() are used so
references in MS_DBG(...) compile (refer to symbols sName, cycCount, MS_DBG, and
sensorList[i]->getSensorNameAndLocation()).

---

Nitpick comments:
In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: Replace the informal repeated intensifier "very, very,
very careful" in the README text with a single, crisper intensifier such as
"very careful" or "extremely careful" (the exact phrase appears in the sentence
starting "If using this method, be very, very, very careful..."); update that
sentence to read e.g. "If using this method, be very careful to make sure the
order of your variables exactly matches the order of your UUIDs." to keep the
docs concise and professional.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md`:
- Around line 108-110: Update the phrasing in the README paragraph that
instructs pasting the copied code: change "containing all of the UUIDs" to
"containing all the UUIDs" in the sentence that follows the instruction to use
the teal "Copy" button and paste into the variable array section, leaving the
rest of the wording unchanged.

In `@src/modems/LoggerModemMacros.h`:
- Around line 460-471: The MS_MODEM_CREATE_NULL_SECURE_CLIENTS macro declares a
TinyGSMType parameter that is never referenced, which may trigger
unused-parameter warnings; modify the macro body to explicitly mark TinyGSMType
as used (for example by adding a noop cast or comment referencing TinyGSMType)
so the compiler won't warn, keeping the existing createSecureClient
implementations in specificModem unchanged (symbols:
MS_MODEM_CREATE_NULL_SECURE_CLIENTS, TinyGSMType,
specificModem::createSecureClient).

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-108: The code currently ignores adcVoltage values outside
(-0.3, 3.6); add an explicit debug log so out-of-range readings are visible:
inside the same function in ApogeeSQ212.cpp, after the if (adcVoltage < 3.6 &&
adcVoltage > -0.3) block add an else (or an early check) that calls MS_DBG (or
the same logging macro used in this file) to print a clear message including the
adcVoltage value and the expected range (e.g. "ApogeeSQ212: adcVoltage out of
range", adcVoltage, "expected (-0.3,3.6)"), then let the function proceed to
bumpMeasurementAttemptCount(false) as before; reference symbols:
SQ212_CALIBRATION_FACTOR, verifyAndAddMeasurementResult, SQ212_PAR_VAR_NUM,
SQ212_VOLTAGE_VAR_NUM, and bumpMeasurementAttemptCount to locate the surrounding
code.

In `@src/sensors/FreescaleMPL115A2.cpp`:
- Around line 80-90: Extract the hard-coded validation bounds into named
constants and use them in the measurement check: add constexpr floats for
MPL115A2_TEMP_MIN, MPL115A2_TEMP_MAX, MPL115A2_PRESS_MIN, MPL115A2_PRESS_MAX
(either at the top of this source file or in the corresponding header), then
replace the numeric literals in the if-condition that calls
verifyAndAddMeasurementResult(MPL115A2_TEMP_VAR_NUM, temp) and
verifyAndAddMeasurementResult(MPL115A2_PRESSURE_VAR_NUM, press) so the condition
uses the new constants before returning bumpMeasurementAttemptCount(success).

In `@src/sensors/FreescaleMPL115A2.h`:
- Around line 38-39: Update the header documentation in FreescaleMPL115A2.h to
correct the mistaken sensor name: replace the reference to "AM2315" with
"MPL115A2" in the comment block that discusses I2C support (the file-level docs
for the FreescaleMPL115A2 sensor). Ensure the note now correctly reads that
Software I2C is not supported for the MPL115A2 (or remove the sentence if
inaccurate) so the documentation matches the FreescaleMPL115A2 implementation.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 58-87: The pressure validation currently uses a hard-coded ceiling
(14000.0); update the conditional in the measurement check to use the
sensor-configured upper bound _maxPressure instead (i.e. replace the "press <=
14000.0" check with "press <= _maxPressure") so
verifyAndAddMeasurementResult(MS5803_PRESSURE_VAR_NUM, press) uses the
configured range; keep the rest of the checks (isnan, temp range, press != 0.0)
intact and ensure _maxPressure is in the same units as the returned press value
before comparison.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 52-56: Reduce the redundant ADC priming by keeping only one
priming call to analogRead() after pinMode(_dataPin, INPUT) (remove the extra
priming call) and then perform the actual read into rawAnalog; also make the
type/intent explicit by either declaring rawAnalog as an int (e.g., int
rawAnalog) or keep float but cast the analogRead() result when assigning (e.g.,
rawAnalog = static_cast<float>(analogRead(_dataPin))) so the analogRead()
integer return is clear in ProcessorAnalog.cpp.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 112-123: Move the extraction and debug logging of sensor readings
so they only run after a successful getEvent call: after checking ret_val (the
result of getEvent), assign temp_val = temp_event.temperature and humid_val =
humidity_event.relative_humidity and then call MS_DBG for Temp and Humidity;
keep the failure branch (when !ret_val) to log "getEvent failed" and the isnan
check in the success branch to handle invalid measurements. Ensure you reference
temp_event, humidity_event, temp_val, humid_val, ret_val and MS_DBG when
relocating the code.
- Around line 165-167: In the sleep() routine the local variables temp_event and
humidity_event are declared without brace-initialization before calling
sht4x_internal.getEvent(&humidity_event, &temp_event); update their declarations
to use brace-initialization (matching the pattern used in
addSingleMeasurementResult) so they are zero-initialized (e.g., declare
sensors_event_t temp_event{} and sensors_event_t humidity_event{}), then call
getEvent as before; no other logic changes required.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 170-182: Add a runtime validation for _voltageDividerFactor before
it's applied: check that _voltageDividerFactor > 0 immediately before the
adcVoltage *= _voltageDividerFactor line (in the block that computes
calibResult); if the factor is <= 0, log an error/warning (using existing
logging like MS_DBG or MS_ERROR), skip applying the factor and skip adding
results (set success to false or return early) to avoid producing invalid
measurements—this keeps verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM,
...) and TURBIDITY_PLUS_VOLTAGE_VAR_NUM from being called with bad values.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
examples/simple_logging/ReadMe.md (1)

43-47: ⚠️ Potential issue | 🟡 Minor

Fix documentation inconsistency between instruction and code.

Line 43 instructs users to change "XXXX", but the code example at line 47 now shows "YourLoggerID". Update the instruction text to match the new placeholder.

📝 Proposed fix
-// Change the "XXXX" in this section of code to the loggerID assigned by Stroud:
+// Change the "YourLoggerID" in this section of code to the loggerID assigned by Stroud:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/simple_logging/ReadMe.md` around lines 43 - 47, Update the
instruction text so it matches the code example: replace the reference to "XXXX"
with "YourLoggerID" (the placeholder used in the const char *LoggerID =
"YourLoggerID"; example) or otherwise instruct the user to set LoggerID to the
logger ID assigned by Stroud; ensure the textual instruction and the LoggerID
symbol in the sample are consistent.
src/sensors/BoschBMP3xx.h (1)

8-10: ⚠️ Potential issue | 🟡 Minor

Documentation mentions non-existent BoschBMP3xx_Humidity class.

The @brief references BoschBMP3xx_Humidity, but this class is not defined in this file—only BoschBMP3xx_Temp, BoschBMP3xx_Pressure, and BoschBMP3xx_Altitude exist. The BMP3xx is a pressure/temperature sensor, not a humidity sensor.

📝 Suggested fix
- * `@brief` Contains the BoschBMP3xx sensor subclass and the variable subclasses
- * BoschBMP3xx_Temp, BoschBMP3xx_Humidity, BoschBMP3xx_Pressure, and
- * BoschBMP3xx_Altitude.
+ * `@brief` Contains the BoschBMP3xx sensor subclass and the variable subclasses
+ * BoschBMP3xx_Temp, BoschBMP3xx_Pressure, and BoschBMP3xx_Altitude.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/BoschBMP3xx.h` around lines 8 - 10, The file documentation
incorrectly references a non-existent BoschBMP3xx_Humidity class; update the
`@brief` in BoschBMP3xx.h to remove BoschBMP3xx_Humidity and list only the actual
subclasses BoschBMP3xx_Temp, BoschBMP3xx_Pressure, and BoschBMP3xx_Altitude (or
reword to "temperature and pressure/altitude variable subclasses") so the header
accurately reflects the implemented symbols.
src/VariableArray.cpp (1)

284-336: ⚠️ Potential issue | 🟠 Major

updateAllSensors can stall when wake=false.
completeUpdate clears status bits, then only starts measurements when WAKE_SUCCESSFUL is set. Because updateAllSensors now calls completeUpdate(false, false, false, false), WAKE_SUCCESSFUL never gets set and nSensorsCompleted can never advance, leaving the loop stuck.

If updateAllSensors should still run a full cycle, call completeUpdate() with defaults. Otherwise, skip clearing wake status or relax the WAKE_SUCCESSFUL gate when wake=false.

🛠️ Minimal fix (use defaults for a full cycle)
 bool VariableArray::updateAllSensors(void) {
-    return completeUpdate(false, false, false, false);
+    return completeUpdate();
 }

Also applies to: 401-408

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 284 - 336, The call updateAllSensors()
currently invokes completeUpdate(false, false, false, false) which forces the
WAKE_SUCCESSFUL gate and prevents nSensorsCompleted from advancing when wake
isn't performed; change updateAllSensors to call completeUpdate() with its
default parameters (i.e., remove the explicit false arguments) so a full cycle
runs, or alternatively modify completeUpdate to skip/relax the WAKE_SUCCESSFUL
check when the wake parameter is false (adjust logic around WAKE_SUCCESSFUL and
nSensorsCompleted in completeUpdate to allow progress when wake==false).
src/SensorBase.cpp (1)

460-503: ⚠️ Potential issue | 🟠 Major

Sensors never sleep because clearStatus() clears WAKE_SUCCESSFUL before wasActive reads it.

The bug is confirmed: clearStatus() (line 462) explicitly clears the WAKE_SUCCESSFUL bit, then wasActive = getStatusBit(WAKE_SUCCESSFUL) (line 469) reads it afterward, always returning false. This causes the if (wasActive) { sleep(); } condition (line 499) to never execute, leaving sensors awake and increasing power draw. Capture the pre-clear active state before calling clearStatus().

Suggested fix
+    const bool wasActiveBefore = getStatusBit(WAKE_SUCCESSFUL);
     // clear all of the status bits and timing values at the start of an update
     // cycle
     clearStatus();

-    bool wasActive = getStatusBit(WAKE_SUCCESSFUL);
-    if (!wasActive) {
+    if (!wasActiveBefore) {
         // NOT yet awake
         // wait for the sensor to have been powered for long enough to respond
         waitForWarmUp();
         ret_val &= wake();
     }
     // bail if the wake failed
     if (!ret_val) return ret_val;
     
     // ...
     
-    if (wasActive) { sleep(); }
+    if (wasActiveBefore) { sleep(); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.cpp` around lines 460 - 503, The code clears the
WAKE_SUCCESSFUL bit via clearStatus() before reading it, so wasActive always
becomes false; fix by reading and storing the current wake state with
getStatusBit(WAKE_SUCCESSFUL) into wasActive before calling clearStatus(), then
proceed with the existing logic (power handling, wake(), measurements, and
finally call sleep() only if the saved wasActive is true); ensure you update the
local variable used at the end (wasActive) rather than re-reading the status
after clearStatus().
🧹 Nitpick comments (9)
extras/AWS_IoT_SetCertificates/ReadMe.md (1)

110-112: Fix the private key instruction wording.

In the private key section, the line says to paste “the text of your certificate,” which is misleading; this should refer to the private key.

✏️ Proposed wording fix
-Find and replace the text `paste the private key here` with the text of your certificate.
+Find and replace the text `paste the private key here` with the text of your private key.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md` around lines 110 - 112, The wording
incorrectly tells users to paste "the text of your certificate" where the
private key belongs; update the three sentences that reference the placeholder
"paste the private key here" and the surrounding guidance so they explicitly say
"private key" (e.g., replace "paste the text of your certificate" with "paste
your private key" and ensure the lines referencing `-----BEGIN RSA PRIVATE
KEY-----` / `-----END RSA PRIVATE KEY-----` describe the private key, not the
certificate). Make the instruction consistent: tell users to replace the
placeholder with their private key contents and confirm the block begins and
ends with the RSA private key markers shown.
examples/simple_logging/ReadMe.md (1)

7-7: Consider clarifying the phrasing.

The phrase "you don't want or have access to a way of streaming live data" is somewhat awkward. Consider simplifying to improve readability, such as: "you don't have a way to stream live data" or "you cannot stream live data and won't upload data to Monitor My Watershed."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/simple_logging/ReadMe.md` at line 7, Revise the sentence "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
Monitor My Watershed." to a clearer phrasing; for example replace it with "Use
this example to deploy a logger when you cannot stream live data and will not
upload data to Monitor My Watershed." or "Use this example to deploy a logger
when you don't have a way to stream live data and won't upload data to Monitor
My Watershed." Update the ReadMe.md entry that contains that exact sentence
accordingly to improve readability.
examples/baro_rho_correction/ReadMe.md (1)

50-50: Consider clarifying the "Stroud" reference.

The phrase "assigned by Stroud" may not be immediately clear to users unfamiliar with the Stroud Center organization. Consider using more generic language like "assigned by the portal" or "assigned when you registered your site on Monitor My Watershed."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/baro_rho_correction/ReadMe.md` at line 50, Update the README text
that says 'Change the "YourLoggerID" in this section of code to the loggerID
assigned by Stroud' to use clearer, generic wording; for example, replace the
phrase 'assigned by Stroud' with 'assigned by the portal' or 'assigned when you
registered your site on Monitor My Watershed' so users know which ID to use for
the "YourLoggerID" value.
src/sensors/TIADS1x15.cpp (1)

94-102: Consider named constants for voltage bounds.

The 3.6 / -0.3 bounds are correct but read as magic numbers. Naming them improves expressiveness and makes future supply‑voltage changes easier.

💡 Possible refactor
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    constexpr float kAdsMinVoltage = -0.3f;
+    constexpr float kAdsMaxVoltage = 3.6f;
+    if (adcVoltage < kAdsMaxVoltage && adcVoltage > kAdsMinVoltage) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 94 - 102, The range check in the ADC
calibration branch uses magic numbers (3.6 and -0.3); replace them with
well‑named constants (e.g., ADS_VOLTAGE_MAX = 3.6f and ADS_VOLTAGE_MIN = -0.3f
or ADC_VALID_VOLTAGE_MAX/MIN) declared at the top of the file or as static
constexpr members so the check if (adcVoltage < ADS_VOLTAGE_MAX && adcVoltage >
ADS_VOLTAGE_MIN) is expressive; update the comment to reference the constant
names and leave the rest of the logic (calibResult = adcVoltage * _gain, MS_DBG,
verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult), success = true)
unchanged.
src/sensors/CampbellOBS3.cpp (1)

100-109: Consider naming the voltage bounds.

This would improve readability and make the TODO around range verification easier to action later.

♻️ Proposed refactor
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    if (adcVoltage < OBS3_MAX_VOLTAGE_V && adcVoltage > OBS3_MIN_VOLTAGE_V) {

Add near the top of the file (or in the header if preferred):

static constexpr float OBS3_MAX_VOLTAGE_V = 3.6f;
static constexpr float OBS3_MIN_VOLTAGE_V = -0.3f;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` around lines 100 - 109, The voltage bounds used
in the OBS3 sensor check are magic numbers; introduce named constants (e.g.,
OBS3_MAX_VOLTAGE_V and OBS3_MIN_VOLTAGE_V) and replace the inline literals in
the adcVoltage range check (the if (adcVoltage < 3.6 && adcVoltage > -0.3)
block) with these constants to improve readability and make the TODO actionable;
add the constants as static constexpr float near the top of the file or in the
header and keep the rest of the logic that computes calibResult (_x2_coeff_A,
_x1_coeff_B, _x0_coeff_C), calls
verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, ...) and
verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, ...) unchanged.
src/VariableArray.h (1)

261-269: Consider replacing the 4 boolean parameters with an options struct.
Four positional booleans reduce readability and maintainability. The method is called in only 4 locations (2 with explicit false, false, false, false and 2 using defaults), making this refactoring low-impact and straightforward to implement.

♻️ Possible refactor sketch
+    struct UpdateOptions {
+        bool powerUp   = true;
+        bool wake      = true;
+        bool sleep     = true;
+        bool powerDown = true;
+    };
-    bool completeUpdate(bool powerUp = true, bool wake = true,
-                        bool sleep = true, bool powerDown = true);
+    bool completeUpdate(const UpdateOptions& options = {});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.h` around lines 261 - 269, Replace the 4 positional
booleans on VariableArray::completeUpdate (powerUp, wake, sleep, powerDown) with
a single options struct (e.g., struct CompleteUpdateOptions { bool powerUp=true;
bool wake=true; bool sleep=true; bool powerDown=true; };) and change the
signature to bool completeUpdate(const CompleteUpdateOptions& opts = {}); update
all call sites to pass either default-constructed opts or an explicit
CompleteUpdateOptions with named fields set (replace occurrences that currently
pass false, false, false, false with CompleteUpdateOptions{.powerUp=false,
.wake=false, .sleep=false, .powerDown=false}); keep internal logic using
opts.field names instead of positional parameters and add the new struct
definition near the class declaration in VariableArray.h (and update any
includes) so compilation and behavior remain unchanged.
src/sensors/TurnerTurbidityPlus.h (1)

314-316: Consider tracking the TODO with an issue or reference.
If you want, I can draft an implementation approach for auto‑detecting PGA gain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.h` around lines 314 - 316, The inline TODO
"Determine gain automatically based on the board voltage?" in
TurnerTurbidityPlus.h should be tracked with a concrete issue or task reference;
create a ticket in your issue tracker describing auto-detecting PGA gain from
board voltage (include acceptance criteria and any test steps), then replace the
TODO comment with a brief note that includes the issue ID or URL and a short
summary (e.g., "see ISSUE-123: auto-detect PGA gain from Vcc") so future readers
can find the design and implementation plan; update the comment text in
TurnerTurbidityPlus.h to include that issue reference.
examples/menu_a_la_carte/ReadMe.md (1)

1267-1268: Consider tightening the repeated intensifier.

This reads a bit smoother without the triple “very”.

✏️ Possible tweak
-If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUIDs.
+If using this method, be extremely careful to make sure the order of your variables exactly matches the order of your UUIDs.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/ReadMe.md` around lines 1267 - 1268, The sentence
uses a triple intensifier ("be very, very, very careful"); revise it to a
single, stronger phrase for smoother tone—e.g., change that clause to "be very
careful" or "exercise extreme care" in the paragraph that starts "If using this
method, be very, very, very careful to make sure the order of your variables
exactly matches the order of your UUIDs." Update only that sentence in ReadMe.md
to remove the repeated "very" and keep the warning clear and concise.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

33-96: Deduplicate default endpoint initialization across constructors.

The host/path/port defaults are repeated in every ctor. A small helper keeps this DRY and lowers maintenance risk.

♻️ Suggested refactor
+namespace {
+inline void initMonitorMWDefaults(MonitorMyWatershedPublisher& p) {
+    p.setHost("monitormywatershed.org");
+    p.setPath("/api/data-stream/");
+    p.setPort(80);
+}
+}  // namespace
+
 MonitorMyWatershedPublisher::MonitorMyWatershedPublisher() : dataPublisher() {
-    setHost("monitormywatershed.org");
-    setPath("/api/data-stream/");
-    setPort(80);
+    initMonitorMWDefaults(*this);
 }
 ...
 MonitorMyWatershedPublisher::MonitorMyWatershedPublisher(Logger& baseLogger,
                                                          int     sendEveryX)
     : dataPublisher(baseLogger, sendEveryX) {
     _logBuffer.setNumVariables(_baseLogger->getArrayVarCount());
-    setHost("monitormywatershed.org");
-    setPath("/api/data-stream/");
-    setPort(80);
+    initMonitorMWDefaults(*this);
 }

As per coding guidelines **/*.cpp: Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 33 - 96,
Multiple MonitorMyWatershedPublisher constructors duplicate the same
host/path/port setup; create a single private helper (e.g.,
MonitorMyWatershedPublisher::initDefaultEndpoint or setDefaultEndpoint) that
calls setHost("monitormywatershed.org"), setPath("/api/data-stream/"), and
setPort(80), then replace the repeated three-line blocks in every
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher(...) overload with a
single call to that helper; ensure the helper is invoked after base
logger/client are initialized (so _baseLogger is valid) and keep existing calls
to _logBuffer.setNumVariables(...) and setToken(...) unchanged in order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1_WiFi/ReadMe.md`:
- Line 17: Update the sentence so it clearly instructs the user to replace
placeholders and insert the correct UUIDs: change the clause "and edit the UUID
section between `Beginning of Token UUID List` and `End of Token UUID List` the
correct UUIDs" to include a preposition such as "to" or "with" (e.g., "and edit
the UUID section between `Beginning of Token UUID List` and `End of Token UUID
List` to include the correct UUIDs from your specific site on
MonitorMyWatershed"), and keep the existing guidance to replace `YourWiFiSSID`
and `YourWiFiPassword`.

In `@examples/ReadMe.md`:
- Around line 108-112: Edit the sentence that shifts tense: replace the
past-tense phrase "where you selected only the portions of code pertinent to you
and delete everything else." with present tense so it reads like an instruction
(e.g., "where you select only the portions of code pertinent to you and delete
everything else."). Ensure the rest of the paragraph remains unchanged and
maintains instructional voice.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 177-178: The README has a typo listing the GPRS credentials as
`gprsUser` and `gprsUser`; update that sentence to reference the actual
variables used in the .ino: `gprsUser` (username) and `gprsPass` (password).
Locate the sentence that mentions GPRS credentials near the `gprsUser` variable
and replace the duplicate name with `gprsPass`, keeping the rest of the
instruction about recompiling and confirming `SerialAT`/`SerialMon` unchanged.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 52-54: Fix small documentation typos in the
MonitorMyWatershedPublisher header: remove the extra period in the constructor
docstring (the comment above the MonitorMyWatershedPublisher default
constructor) and correct the duplicated word "a a" to "a" in the comment for
publishData (or the method around lines 242-243 referenced by
publishData/PublishData), then run a quick scan for similar double-characters or
punctuation mistakes in other comments in the class to keep docs consistent.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 349-355: The timeout logic sets minTime/maxTime to 0 when
_alwaysAutoFocus is false, causing isStable to immediately time out; change the
assignments so that when autofocus is off minTime uses _stabilizationTime_ms and
maxTime uses HYDROCAM_STABILIZATION_TIME_MAX (i.e. set minTime =
_alwaysAutoFocus ? HYDROCAM_AUTOFOCUS_TIME_MS + _stabilizationTime_ms :
_stabilizationTime_ms and maxTime = _alwaysAutoFocus ?
HYDROCAM_AUTOFOCUS_TIME_MAX + HYDROCAM_STABILIZATION_TIME_MAX :
HYDROCAM_STABILIZATION_TIME_MAX) so the elapsed_since_wake_up check correctly
respects stabilization-only timing.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 58-91: The pressure validity check currently allows negative
pressures because it uses "press != 0.0"; update the condition in the
measurement validation (the block that calls MS5803_internal.getTemperature and
getPressure and then verifyAndAddMeasurementResult) to require press > 0.0
instead of press != 0.0 while keeping the upper bound (press <= 14000.0); locate
the logic by referencing getStatusBit(MEASUREMENT_SUCCESSFUL),
MS5803_internal.getTemperature(CELSIUS, ADC_512),
MS5803_internal.getPressure(ADC_4096),
verifyAndAddMeasurementResult(MS5803_PRESSURE_VAR_NUM), and ensure
bumpMeasurementAttemptCount(success) still returns the attempt result.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 1-3: Remove the stray asterisk in the file-level Doxygen comment
in ProcessorAnalog.cpp by editing the `@file` tag line (the second line of the
header comment) to remove the extra '*' so it reads correctly (e.g., "@file
ProcessorAnalog.cpp") and ensure the surrounding comment formatting remains
valid.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 107-124: The current code overwrites sentinel values
(temp_val/humid_val) with zero-initialized sensors_event_t fields even when
sht4x_internal.getEvent(...) fails, so change the flow in the getEvent handling:
only populate temp_val and humid_val from temp_event.temperature and
humidity_event.relative_humidity after ret_val indicates success; if getEvent
fails leave temp_val/humid_val unchanged (the -9999 sentinel) and log the
failure via MS_DBG(F("  getEvent failed; no values read!")) as before. Locate
the block using sht4x_internal.getEvent, temp_event, humidity_event, ret_val,
temp_val and humid_val and move/guard the assignments to occur inside the
successful-ret_val branch (and keep the isnan checks and logging accordingly).

In `@src/sensors/TIINA219.cpp`:
- Around line 88-123: The current addSingleMeasurementResult implementation only
calls ina219_phy.success() after all three reads, which only reflects the last
I2C operation; change it to check success after each individual read (after
ina219_phy.getCurrent_mA(), ina219_phy.getBusVoltage_V(), and
ina219_phy.getPower_mW()) and treat any read failure as overall failure: if any
ina219_phy.success() returns false or the value is isnan(), mark success=false
and avoid calling verifyAndAddMeasurementResult for that variable (or bail out
early), then pass the final success into bumpMeasurementAttemptCount(success).
Use the existing function names (addSingleMeasurementResult,
ina219_phy.getCurrent_mA/getBusVoltage_V/getPower_mW, ina219_phy.success,
verifyAndAddMeasurementResult, bumpMeasurementAttemptCount) to locate and
implement the per-read checks.

---

Outside diff comments:
In `@examples/simple_logging/ReadMe.md`:
- Around line 43-47: Update the instruction text so it matches the code example:
replace the reference to "XXXX" with "YourLoggerID" (the placeholder used in the
const char *LoggerID = "YourLoggerID"; example) or otherwise instruct the user
to set LoggerID to the logger ID assigned by Stroud; ensure the textual
instruction and the LoggerID symbol in the sample are consistent.

In `@src/SensorBase.cpp`:
- Around line 460-503: The code clears the WAKE_SUCCESSFUL bit via clearStatus()
before reading it, so wasActive always becomes false; fix by reading and storing
the current wake state with getStatusBit(WAKE_SUCCESSFUL) into wasActive before
calling clearStatus(), then proceed with the existing logic (power handling,
wake(), measurements, and finally call sleep() only if the saved wasActive is
true); ensure you update the local variable used at the end (wasActive) rather
than re-reading the status after clearStatus().

In `@src/sensors/BoschBMP3xx.h`:
- Around line 8-10: The file documentation incorrectly references a non-existent
BoschBMP3xx_Humidity class; update the `@brief` in BoschBMP3xx.h to remove
BoschBMP3xx_Humidity and list only the actual subclasses BoschBMP3xx_Temp,
BoschBMP3xx_Pressure, and BoschBMP3xx_Altitude (or reword to "temperature and
pressure/altitude variable subclasses") so the header accurately reflects the
implemented symbols.

In `@src/VariableArray.cpp`:
- Around line 284-336: The call updateAllSensors() currently invokes
completeUpdate(false, false, false, false) which forces the WAKE_SUCCESSFUL gate
and prevents nSensorsCompleted from advancing when wake isn't performed; change
updateAllSensors to call completeUpdate() with its default parameters (i.e.,
remove the explicit false arguments) so a full cycle runs, or alternatively
modify completeUpdate to skip/relax the WAKE_SUCCESSFUL check when the wake
parameter is false (adjust logic around WAKE_SUCCESSFUL and nSensorsCompleted in
completeUpdate to allow progress when wake==false).

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes file references Git filter drivers
smudgePasswords and disableDebug but no filter driver configuration exists; add
instructions or commit configuration for these filters by defining
filter.smudgePasswords.clean/filter.smudgePasswords.smudge and
filter.disableDebug.clean/filter.disableDebug.smudge (or provide documented
setup steps for contributors) so Git won't warn and will apply the filters on
checkout/commit; update repository documentation or a setup script to run the
corresponding git config commands and reference the filter names smudgePasswords
and disableDebug from the .gitattributes entry.

In `@ChangeLog.md`:
- Around line 74-79: The repeated changelog lines all start with "Removed ..."
which reduces readability; edit the ChangeLog.md entries (the lines describing
removal of _maxSamplesToAverage, copy doc calls, powerUp/powerDown overrides,
EnviroDIY references, and defines in example sketches) to vary sentence
openings—use a mix of synonyms (e.g., "Eliminated", "Dropped", "Deleted", "No
longer include", "Cleared") or convert some into a grouped sentence or bulleted
clause so the entries read more smoothly while preserving the same content and
meaning.
- Line 40: Change the phrasing "all the arguments" to "all arguments" in the
ChangeLog sentence describing updateAllSensors() so it reads: "To achieve the
same functionality as the old updateAllSensors() function (i.e., only updating
values), set all arguments to false." Update the parenthetical formatting for
clarity (use "i.e.,") and ensure the function name updateAllSensors() remains
code-formatted.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The example calls
modem.gsmModem.forceModemBaud(modemSerial, static_cast<uint32_t>(modemBaud))
which is non‑standard in upstream TinyGSM; verify whether your TinyGSM fork
actually provides forceModemBaud and if not replace both occurrences (around the
calls referencing modem.gsmModem, modemSerial, modemBaud) with the supported
workflow (e.g., call the modem serial's setBaud(...) or use TinyGsmAutoBaud(...)
as exposed by your TinyGSM dependency) and update any casts accordingly so the
example compiles against the declared TinyGSM version.

In `@examples/example_dependencies.json`:
- Line 29: The dependency entry in examples/example_dependencies.json specifies
an unavailable Adafruit NeoPixel version "~1.15.4"; update the version
constraint to a valid release (e.g., change the NeoPixel version to "~1.15.2")
or verify and set the latest published version in the same file so dependency
resolution succeeds (look for the NeoPixel "version" field in
examples/example_dependencies.json and replace "~1.15.4" accordingly).

In `@examples/examples.dox`:
- Around line 67-77: Ensure the `@ref` target actually resolves: locate the tokens
`@ref` examples_outdated, `@page` page_examples_outdated, and `@subpage`
example_learn_envirodiy in the docs and either (A) add the missing section
anchor {`#examples_outdated`} into the corresponding example ReadMe/markdown so
`@ref` examples_outdated points to that section, or (B) if the intent is to
reference the page symbol, replace `@ref` examples_outdated with `@ref`
page_examples_outdated to match the `@page` definition; make the minimal change
that makes the reference resolvable and consistent with the other page/subpage
entries.

In `@src/LoggerModem.h`:
- Around line 991-992: Fix the typo in the Doxygen comment for the modem reset
pin by inserting the missing "to" so the sentence reads "attached to the hard or
panic reset pin of the modem"; update the comment block that currently contains
"The digital pin number of the pin on the mcu attached the hard or panic reset
pin of the modem." (search for this exact comment in LoggerModem.h) to ensure
grammatical correctness.

In `@src/modems/EspressifESP32.h`:
- Line 162: The public field declaration "TinyGsmESP32 gsmModem;" changes the
class API surface; revert this by removing or making gsmModem non-public and
restoring the previous externally-visible type/contract. Specifically, change
the visibility of gsmModem to private (or back to the prior type if it was
different), keep the concrete TinyGsmESP32 instance only internally, and expose
a controlled accessor method (e.g., EspressifESP32::getModem() or
getModemInterface()) that returns the original public-facing type or an abstract
interface used before; ensure all usages of gsmModem outside the class call the
new accessor so the public API remains stable.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The include and type TinyGsmClientSIM7000SSL.h / TinyGsmSim7000SSL in
SIMComSIM7000.h may not exist in the TinyGSM you depend on; verify the
header/type exist in your vendored or upstream TinyGSM and either update the
include/type to the correct symbol provided by your TinyGSM (e.g.,
TinyGsmClientSIM7000 or TinyGsmSIM7000 variants) or guard usage with
preprocessor checks (`#ifdef/`#if defined) so compilation falls back to the
non-SSL TinyGSM client; also ensure your dependency declarations (platformio.ini
/ library.json / lib_deps) include a TinyGSM version that provides the required
SSL symbols.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 251-255: Currently _filename is updated regardless of transfer
outcome, causing getLastSavedImageName() to reference incomplete images; change
the logic in the transfer completion block so that the assignment to _filename
only happens when bytes_transferred == image_size (i.e., success is true). Use
the existing symbols (bytes_transferred, image_size, success) to determine
outcome and move or guard the _filename = filename assignment so it runs only on
success, keeping the MS_DBG logging as-is to report success/failure.

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-282: The new GeoluxHydroCam constructors inserted powerPin2
before imageResolution which breaks existing 4-arg positional calls; restore
backwards compatibility by either (A) adding overloads that match the old
signatures (keep the existing 3- and 4-arg constructors: GeoluxHydroCam(Stream*,
int8_t, Logger&, const char* imageResolution = "1600x1200", const char*
filePrefix = nullptr, bool alwaysAutoFocus = false) and the Stream& variant) so
callers passing a string as the 4th positional argument still bind to
imageResolution, or (B) move the powerPin2 parameter to the end of the parameter
list with a default value so the positional ordering for
imageResolution/filePrefix remains unchanged; apply the same change to both
declarations (Stream* and Stream& overloads) and update any delegating
constructors/implementation accordingly (references: GeoluxHydroCam
constructors, powerPin2, imageResolution).

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 125-196: The retry loop in
MaxBotixSonar::addSingleMeasurementResult can block far longer than the allowed
measurement budget because it retries up to rangeAttempts < 25 with a blocking
_stream->parseInt() (stream timeout elsewhere), so change the loop to stop based
on a time budget rather than a hard 25 count: record a start timestamp (e.g.,
using millis()) before the while, compute a deadline using
HRXL_MEASUREMENT_TIME_MS (or a configurable getAllowedMeasurementTimeMs()), and
loop while not success AND millis() < deadline (optionally also cap by a small
configurable max retries like getAllowedMeasurementRetries()), so the method
returns before the measurement time budget expires; update references to
rangeAttempts only for debugging and ensure bumpMeasurementAttemptCount(success)
is still honored.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 58-69: The code currently treats any rtc.getTemperature() result
as success; change MaximDS3231::read (or the block using rtc.getTemperature())
to validate tempVal before marking success: check if tempVal is finite/not NaN
(and != any library sentinel if applicable) and only call
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal) and
bumpMeasurementAttemptCount(true) when valid; if invalid, log a diagnostic with
getSensorNameAndLocation()/MS_DBG and call bumpMeasurementAttemptCount(false) so
retries occur and do not record the bad measurement.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 103-121: Capture and validate the byte count returned by
_i2c->requestFrom(static_cast<uint8_t>(_i2cAddressHex), static_cast<uint8_t>(4))
instead of using it as a boolean; store that return value (e.g., bytesRequested
or bytesRead) and require it equals the expected 4 (or at least the minimum
required) before proceeding, otherwise log the short/partial read (using
getSensorNameAndLocation()/MS_DBG) and return
bumpMeasurementAttemptCount(false); also use that returned count as the upper
bound for filling SerialBuffer and for the subsequent version-detection logic
(instead of relying on _i2c->available() alone) to avoid misinterpreting
zero-filled trailing bytes.

In `@src/sensors/TIINA219.cpp`:
- Around line 40-63: Store and combine the base class setup result with the
INA219 init so failures from Sensor::setup() propagate: call Sensor::setup() and
save its bool return (e.g., baseOk), then call ina219_phy.begin(_i2c) and set
success = baseOk && ina219_phy.begin(_i2c); keep the existing power handling
(checkPowerOn, powerUp, waitForWarmUp, powerDown) but use the combined success
to decide status bits (setStatusBit(ERROR_OCCURRED) and
clearStatusBit(SETUP_SUCCESSFUL)) so a failed Sensor::setup() prevents
SETUP_SUCCESSFUL from being left set; update references in TIINA219::setup to
use baseOk and success accordingly.

---

Nitpick comments:
In `@examples/baro_rho_correction/ReadMe.md`:
- Line 50: Update the README text that says 'Change the "YourLoggerID" in this
section of code to the loggerID assigned by Stroud' to use clearer, generic
wording; for example, replace the phrase 'assigned by Stroud' with 'assigned by
the portal' or 'assigned when you registered your site on Monitor My Watershed'
so users know which ID to use for the "YourLoggerID" value.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: The sentence uses a triple intensifier ("be very, very,
very careful"); revise it to a single, stronger phrase for smoother tone—e.g.,
change that clause to "be very careful" or "exercise extreme care" in the
paragraph that starts "If using this method, be very, very, very careful to make
sure the order of your variables exactly matches the order of your UUIDs."
Update only that sentence in ReadMe.md to remove the repeated "very" and keep
the warning clear and concise.

In `@examples/simple_logging/ReadMe.md`:
- Line 7: Revise the sentence "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 Monitor My Watershed." to a clearer
phrasing; for example replace it with "Use this example to deploy a logger when
you cannot stream live data and will not upload data to Monitor My Watershed."
or "Use this example to deploy a logger when you don't have a way to stream live
data and won't upload data to Monitor My Watershed." Update the ReadMe.md entry
that contains that exact sentence accordingly to improve readability.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 110-112: The wording incorrectly tells users to paste "the text of
your certificate" where the private key belongs; update the three sentences that
reference the placeholder "paste the private key here" and the surrounding
guidance so they explicitly say "private key" (e.g., replace "paste the text of
your certificate" with "paste your private key" and ensure the lines referencing
`-----BEGIN RSA PRIVATE KEY-----` / `-----END RSA PRIVATE KEY-----` describe the
private key, not the certificate). Make the instruction consistent: tell users
to replace the placeholder with their private key contents and confirm the block
begins and ends with the RSA private key markers shown.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 33-96: Multiple MonitorMyWatershedPublisher constructors duplicate
the same host/path/port setup; create a single private helper (e.g.,
MonitorMyWatershedPublisher::initDefaultEndpoint or setDefaultEndpoint) that
calls setHost("monitormywatershed.org"), setPath("/api/data-stream/"), and
setPort(80), then replace the repeated three-line blocks in every
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher(...) overload with a
single call to that helper; ensure the helper is invoked after base
logger/client are initialized (so _baseLogger is valid) and keep existing calls
to _logBuffer.setNumVariables(...) and setToken(...) unchanged in order.

In `@src/sensors/CampbellOBS3.cpp`:
- Around line 100-109: The voltage bounds used in the OBS3 sensor check are
magic numbers; introduce named constants (e.g., OBS3_MAX_VOLTAGE_V and
OBS3_MIN_VOLTAGE_V) and replace the inline literals in the adcVoltage range
check (the if (adcVoltage < 3.6 && adcVoltage > -0.3) block) with these
constants to improve readability and make the TODO actionable; add the constants
as static constexpr float near the top of the file or in the header and keep the
rest of the logic that computes calibResult (_x2_coeff_A, _x1_coeff_B,
_x0_coeff_C), calls verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, ...) and
verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, ...) unchanged.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 94-102: The range check in the ADC calibration branch uses magic
numbers (3.6 and -0.3); replace them with well‑named constants (e.g.,
ADS_VOLTAGE_MAX = 3.6f and ADS_VOLTAGE_MIN = -0.3f or ADC_VALID_VOLTAGE_MAX/MIN)
declared at the top of the file or as static constexpr members so the check if
(adcVoltage < ADS_VOLTAGE_MAX && adcVoltage > ADS_VOLTAGE_MIN) is expressive;
update the comment to reference the constant names and leave the rest of the
logic (calibResult = adcVoltage * _gain, MS_DBG,
verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult), success = true)
unchanged.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 314-316: The inline TODO "Determine gain automatically based on
the board voltage?" in TurnerTurbidityPlus.h should be tracked with a concrete
issue or task reference; create a ticket in your issue tracker describing
auto-detecting PGA gain from board voltage (include acceptance criteria and any
test steps), then replace the TODO comment with a brief note that includes the
issue ID or URL and a short summary (e.g., "see ISSUE-123: auto-detect PGA gain
from Vcc") so future readers can find the design and implementation plan; update
the comment text in TurnerTurbidityPlus.h to include that issue reference.

In `@src/VariableArray.h`:
- Around line 261-269: Replace the 4 positional booleans on
VariableArray::completeUpdate (powerUp, wake, sleep, powerDown) with a single
options struct (e.g., struct CompleteUpdateOptions { bool powerUp=true; bool
wake=true; bool sleep=true; bool powerDown=true; };) and change the signature to
bool completeUpdate(const CompleteUpdateOptions& opts = {}); update all call
sites to pass either default-constructed opts or an explicit
CompleteUpdateOptions with named fields set (replace occurrences that currently
pass false, false, false, false with CompleteUpdateOptions{.powerUp=false,
.wake=false, .sleep=false, .powerDown=false}); keep internal logic using
opts.field names instead of positional parameters and add the new struct
definition near the class declaration in VariableArray.h (and update any
includes) so compilation and behavior remain unchanged.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md (1)

48-52: ⚠️ Potential issue | 🟡 Minor

Fix placeholder mismatch in instructions.

Line 48 tells users to change “XXXX”, but the snippet now uses “YourLoggerID”. Update the instruction to match the new placeholder so users don’t look for a value that no longer exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md` around
lines 48 - 52, Update the README instruction to match the placeholder used in
the code snippet: replace the reference to "XXXX" with "YourLoggerID" so users
know to set the LoggerID constant (const char *LoggerID = "YourLoggerID";) to
the logger ID assigned by Stroud; ensure the surrounding prose consistently uses
"YourLoggerID" when referring to that placeholder.
examples/simple_logging/ReadMe.md (1)

43-47: ⚠️ Potential issue | 🟡 Minor

Fix inconsistency between instruction text and code example.

Line 43 instructs users to change "XXXX" but line 47 now shows "YourLoggerID" as the placeholder. This mismatch will confuse users following the documentation.

📝 Proposed fix to align instruction with code example
-// Change the "XXXX" in this section of code to the loggerID assigned by Stroud:
+// Change the "YourLoggerID" in this section of code to the loggerID assigned by Stroud:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/simple_logging/ReadMe.md` around lines 43 - 47, Update the README
instruction so it matches the code example: replace the "XXXX" placeholder in
the instruction text with "YourLoggerID" (the same placeholder used in the const
char *LoggerID = "YourLoggerID"; example) so users are told to set LoggerID to
the value assigned by Stroud; ensure the instruction explicitly references the
LoggerID constant name to avoid ambiguity.
src/sensors/TIINA219.cpp (1)

40-64: ⚠️ Potential issue | 🟡 Minor

Sensor::setup() return value is not captured.

While the error handling for ina219_phy.begin() failure has been improved, the return value from Sensor::setup() at line 42 is still not captured and combined with the local success variable. If Sensor::setup() fails (e.g., power pin issues), the function could still return true if ina219_phy.begin() succeeds.

🔧 Suggested fix
 bool TIINA219::setup(void) {
     bool wasOn;
-    Sensor::setup();  // this will set pin modes and the setup status bit
+    bool success = Sensor::setup();  // this will set pin modes and the setup status bit

     // This sensor needs power for setup!
     delay(10);
     wasOn = checkPowerOn();
     if (!wasOn) {
         powerUp();
         waitForWarmUp();
     }

-    bool success = ina219_phy.begin(_i2c);
+    success = success && ina219_phy.begin(_i2c);

     // Turn the power back off it it had been turned on
     if (!wasOn) { powerDown(); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIINA219.cpp` around lines 40 - 64, TIINA219::setup currently
ignores the bool returned by Sensor::setup(); capture that result (e.g., bool
baseOk = Sensor::setup()) and combine it with the INA219 init result (bool phyOk
= ina219_phy.begin(_i2c); bool success = baseOk && phyOk) so the method only
returns true if both setup steps succeed; ensure you still call
checkPowerOn()/powerUp()/powerDown()/waitForWarmUp() as before and
setStatusBit(ERROR_OCCURRED) / clearStatusBit(SETUP_SUCCESSFUL) based on the
combined success value so failures in Sensor::setup are propagated.
src/sensors/GroPointParent.h (2)

38-49: ⚠️ Potential issue | 🟡 Minor

Documentation references dual power pin functionality that may no longer exist.

The documentation at lines 38-49 extensively describes two-power-pin behavior (e.g., "one or two power pins", "Use -1 for the second power pin", shared vs. unshared pin ordering). If _powerPin2 has been removed and the second pin is no longer managed by this class, this documentation is now misleading.

Update the documentation to reflect the current power management design, or clarify if the second pin is now handled by the parent Sensor class.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` around lines 38 - 49, The class documentation
still describes dual-power-pin behavior (references to "one or two power pins",
using -1 for a second pin, shared/unshared ordering) but the implementation no
longer manages a second pin (e.g., _powerPin2 was removed or delegated); update
the comment block in GroPointParent.h to match the current design by removing or
rewriting the dual-pin instructions and instead document the actual power
management (mention that only a single power pin is supported or that power is
handled by the parent Sensor class), and reference the class/field names
involved (GroPointParent, _powerPin2, Sensor) so readers know whether to use a
single pin or rely on Sensor for power control.

106-110: ⚠️ Potential issue | 🟡 Minor

Constructor parameter documentation for powerPin2 should align with implementation.

The @param powerPin2 documentation describes RS485 adapter power control, but this should be updated or removed if the parameter is no longer stored or used by this class.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` around lines 106 - 110, The constructor
docblock for GroPointParent mentions a powerPin2 parameter for RS485 adapter
power control but the implementation no longer stores/uses it; update the
documentation for the GroPointParent constructor to match reality: either remove
the `@param` powerPin2 entry if the constructor signature no longer accepts or
uses that argument, or change the description to reflect how powerPin2 is
actually used/stored in the class (reference the GroPointParent constructor and
any member variables that would hold power pins) so the doc comment and
implementation stay in sync.
extras/AWS_IoT_SetCertificates/ReadMe.md (1)

107-112: ⚠️ Potential issue | 🟡 Minor

Private‑key section says “certificate” — should be “private key.”

Line 111 is misleading in a critical step and can cause users to paste the wrong material.

✏️ Proposed fix
-Find and replace the text `paste the private key here` with the text of your certificate.
+Find and replace the text `paste the private key here` with the text of your private key.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md` around lines 107 - 112, The
instruction incorrectly tells users to paste their "certificate" into the
placeholder 'paste the private key here'—update the copy so it explicitly
instructs pasting the private key (not the certificate) from the file that ends
with "private.pem.key"; change the sentence that currently reads to paste the
"certificate" to instead say "private key", and reinforce that the pasted text
must begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA
PRIVATE KEY-----' (i.e., paste the contents of the private.pem.key file into the
placeholder).
src/SensorBase.cpp (1)

398-414: ⚠️ Potential issue | 🔴 Critical

Duplicate conditions make the second branch unreachable.

Lines 398-400 and 405-407 have identical conditions:

  • First: (sensorValues[resultNumber] == -9999 || isnan(...)) && (resultValue != -9999 && !isnan(...))
  • Second: (sensorValues[resultNumber] == -9999 || isnan(...)) && (resultValue != -9999 && !isnan(...))

The second branch (lines 405-414) that adds to existing good results can never execute because the first branch catches the same case. Based on the comment "If the new result is good and there were already good results", the second condition should check that sensorValues[resultNumber] is not -9999/NaN.

🐛 Proposed fix
-    } else if ((sensorValues[resultNumber] == -9999 ||
-                isnan(sensorValues[resultNumber])) &&
+    } else if ((sensorValues[resultNumber] != -9999 &&
+                !isnan(sensorValues[resultNumber])) &&
                (resultValue != -9999 && !isnan(resultValue))) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.cpp` around lines 398 - 414, The second branch uses the exact
same condition as the first, making it unreachable; update the else-if for the
"add to existing good results" branch in SensorBase.cpp so it checks that
sensorValues[resultNumber] is already a valid/real value (i.e., not the -9999
sentinel and not NaN) while resultValue is good, instead of checking that
sensorValues is the sentinel/NaN; locate the two branches around
sensorValues[resultNumber], numberGoodMeasurementsMade[resultNumber], and
getSensorNameAndLocation() and change the second branch's condition to require
the existing sensorValues entry be valid before adding and incrementing the
good-measurement count.
🧹 Nitpick comments (9)
examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md (1)

9-9: Tighten sentence for clarity.

The line is a bit long and repetitive (“you don’t want or have access…”). Consider a shorter phrasing to improve readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md` at line 9,
The sentence "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 Monitor My Watershed." is long and repetitive;
replace it with a tighter phrasing (for example: "Use this example to deploy a
logger when live streaming and Monitor My Watershed uploads are not available.")
in ReadMe.md so the intent is clear and the line reads more concisely.
src/sensors/MaximDS18.cpp (1)

184-208: Clean refactor with improved measurement validation.

The changes align well with the PR-wide measurement flow refinements:

  • Early return when measurement wasn't started prevents unnecessary sensor communication
  • Invalid reading handling (85°C power-on reset, -127°C disconnection) is correct for DS18B20 behavior
  • Return via bumpMeasurementAttemptCount(success) is consistent with other sensors

One optional defensive improvement: if getTempC() ever returns NaN (unlikely but possible on communication errors), it would pass the validity check and set success=true even though verifyAndAddMeasurementResult would reject it.

🛡️ Optional: Add NaN check for robustness
-    if (result != 85 && result != -127) {
+    if (result != 85 && result != -127 && !isnan(result)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaximDS18.cpp` around lines 184 - 208, Add a NaN defensive check
after calling _internalDallasTemp.getTempC(_OneWireAddress) to ensure a NaN
result doesn't count as a valid reading; specifically, after result =
_internalDallasTemp.getTempC(_OneWireAddress) validate with std::isnan(result)
(or equivalent) and treat it like the existing invalid cases (do not call
verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result) and leave
success=false), then return via bumpMeasurementAttemptCount(success) as before;
you can still log the invalid/NaN case via MS_DBG and keep the early return when
getStatusBit(MEASUREMENT_SUCCESSFUL) is false.
src/sensors/AlphasenseCO2.cpp (1)

119-133: Address the TODO: verify the voltage range for the CO2 sensor.

The @todo comment indicates the voltage range check (-0.3V to 3.6V) hasn't been verified against the actual CO2 sensor specifications. The current range appears to be based on the ADS1115's operating limits when powered at 3.3V, but the sensor's actual output range should be confirmed.

Would you like me to help research the Alphasense CO2 sensor specifications to determine the appropriate voltage range, or open an issue to track this verification task?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 119 - 133, The voltage bounds
check in AlphasenseCO2.cpp (the if using adcVoltage < 3.6 && adcVoltage > -0.3)
is based on ADS limits, not the Alphasense CO2 sensor spec; verify the sensor's
actual output voltage range from the Alphasense datasheet and replace the
hardcoded -0.3/3.6 values with properly named constants (e.g.,
CO2_SENSOR_MIN_VOLTAGE, CO2_SENSOR_MAX_VOLTAGE) used in the adcVoltage check
inside the block that computes co2Current and calibResult and calls
verifyAndAddMeasurementResult(ALPHASENSE_CO2_VAR_NUM, ...) and
verifyAndAddMeasurementResult(ALPHASENSE_CO2_VOLTAGE_VAR_NUM, ...); update the
comment above the check to cite the datasheet source and, if the sensor output
depends on external circuitry (e.g., 250Ω resistor), document that in the
comment and add a fallback/guard if the ADC reading is out-of-spec (e.g., skip
measurement and set success=false or log an error).
src/sensors/CampbellOBS3.cpp (1)

100-110: Consider adding debug output when voltage is out of range.

When adcVoltage falls outside the valid range, the function silently skips adding results. Adding a debug message for this case would aid troubleshooting sensor or wiring issues in the field.

💡 Optional: Add debug logging for out-of-range voltage
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage out of valid range:"), adcVoltage);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` around lines 100 - 110, Add a debug log when
adcVoltage is outside the accepted range so out-of-range conditions are visible;
inside the same function/block that currently checks "if (adcVoltage < 3.6 &&
adcVoltage > -0.3)" add an else branch that calls the existing debug logging
macro (e.g., MS_DBG or MS_LOG) and prints the adcVoltage value and the expected
range, and ensure the function still leaves success false and does not call
verifyAndAddMeasurementResult for OBS3_TURB_VAR_NUM or OBS3_VOLTAGE_VAR_NUM;
reference the adcVoltage variable, the success flag, and the MS_DBG logging
macro to locate where to insert the else branch.
src/VariableArray.cpp (2)

376-377: Direct manipulation of protected members relies on friend access.

The code directly sets _measurementAttemptsCompleted and _measurementsToAverage to skip sensors that failed to wake. This works because VariableArray is declared as a friend of Sensor. This approach is reasonable for handling wake failures efficiently, though adding a public helper method like markMeasurementsComplete() could make the intent clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 376 - 377, The code directly manipulates
Sensor's protected fields (_measurementAttemptsCompleted and
_measurementsToAverage) from VariableArray relying on friend access; replace
this with a clear public API: add a Sensor::markMeasurementsComplete() (or
similar) method that sets _measurementAttemptsCompleted =
_measurementsToAverage, and update VariableArray to call
sensorList[i]->markMeasurementsComplete() instead of touching the protected
members directly; alternatively, if you keep friend access, add an explicit
Sensor::skipDueToWakeFailure() helper to encapsulate the intent and call that
from VariableArray.

476-556: Complex power-down logic with shared pin handling.

The nested loops checking for shared power pins (both primary and secondary) are thorough but complex. The logic correctly prevents powering down a pin if any other sensor sharing that pin still needs measurements. Consider extracting this into a helper method like canPowerDownSensor(uint8_t sensorIndex) for improved readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 476 - 556, Extract the complex shared-pin
power-down check into a helper method (e.g., bool canPowerDownSensor(uint8_t
sensorIndex)) that iterates _sensorCount and uses
sensorList[sensorIndex]->getPowerPin(), getSecondaryPowerPin(), and each
sensorList[k]->getNumberCompleteMeasurementsAttempts() vs
getNumberMeasurementsToAverage() to decide whether any other sensor sharing
either primary or secondary pin still needs measurements; replace the inline
for-loop and canPowerDown boolean in the existing block with a single call to
canPowerDownSensor(i) and call sensorList[i]->powerDown() when it returns true;
also move the duplicate debug iteration (inside the MS_VARIABLEARRAY_DEBUG
branch) to reuse the helper or a small helper-returned list/flag so the debug
messages remain accurate without duplicating the matching logic.
src/SensorBase.cpp (1)

692-709: Off-by-one in retry counting logic.

bumpMeasurementAttemptCount increments _retryAttemptsMade before the check at line 702. With _allowedMeasurementRetries = 1 (default), after the first failed attempt _retryAttemptsMade becomes 1, and since 1 > 1 is false, a retry is allowed. After the second attempt, _retryAttemptsMade becomes 2, and 2 > 1 is true, so it stops. This means 1 retry is allowed, which matches the intent.

However, consider that _retryAttemptsMade is incremented unconditionally (line 700) even on success. This means on success, _retryAttemptsMade momentarily becomes 1 before being reset to 0. While functionally correct (since wasSuccessful short-circuits the OR), this is slightly confusing. A minor clarity improvement would be to only increment on failure.

♻️ Optional: Increment retries only on failure for clarity
-    // Bump the number of attempted retries
-    _retryAttemptsMade++;
-
-    if (wasSuccessful || _retryAttemptsMade > _allowedMeasurementRetries) {
+    if (wasSuccessful) {
         // Bump the number of completed measurement attempts - we've succeeded
-        // or failed but exceeded retries
         _measurementAttemptsCompleted++;
-        // Reset the number of retries made for the next measurement attempt
         _retryAttemptsMade = 0;
+    } else {
+        // Bump the number of attempted retries
+        _retryAttemptsMade++;
+        if (_retryAttemptsMade > _allowedMeasurementRetries) {
+            // Failed but exceeded retries
+            _measurementAttemptsCompleted++;
+            _retryAttemptsMade = 0;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.cpp` around lines 692 - 709, The function
bumpMeasurementAttemptCount currently increments _retryAttemptsMade
unconditionally which is confusing; change it so _retryAttemptsMade is only
incremented when wasSuccessful is false, leaving success paths to skip the
increment and then reset _retryAttemptsMade to 0 when wasSuccessful or when
retries exceeded; keep the existing behavior of recording
_millisMeasurementCompleted, clearing _millisMeasurementRequested and status
bits via clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL), and
still increment _measurementAttemptsCompleted and reset _retryAttemptsMade when
(wasSuccessful || _retryAttemptsMade > _allowedMeasurementRetries) so logic and
symbols (_retryAttemptsMade, _allowedMeasurementRetries,
_measurementAttemptsCompleted) remain consistent.
examples/menu_a_la_carte/ReadMe.md (1)

1267-1268: Tone down the triple “very” for readability.
The repetition reads noisy; a single stronger modifier keeps the warning clear.

💡 Suggested wording
-If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUIDs.
+If using this method, be extremely careful to make sure the order of your variables exactly matches the order of your UUIDs.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/ReadMe.md` around lines 1267 - 1268, The phrasing
"be very, very, very careful" in the README is overly repetitive; update the
sentence in the examples/menu_a_la_carte/ReadMe.md where it currently reads "If
using this method, be very, very, very careful to make sure the order of your
variables exactly matches the order of your UUIDs." to a clearer single stronger
modifier such as "If using this method, be extremely careful to make sure the
order of your variables exactly matches the order of your UUIDs." or "exercise
great care" to improve readability.
src/sensors/KnownProcessors.h (1)

259-288: Consider adding default fallbacks for BUILT_IN_ALS_* macros for consistency. All current uses are properly guarded with comprehensive #if defined() checks, preventing compilation issues on unsupported boards. However, adding sentinel defaults (e.g., -1 for pins) alongside the existing fallbacks for LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER would make the guard conditions consistent across all processor-specific macros and provide defensive protection against future unguarded uses.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KnownProcessors.h` around lines 259 - 288, Add defensive default
fallbacks for the BUILT_IN_ALS_* macros (so they mirror the existing pattern for
LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, BATTERY_MULTIPLIER): wrap each
macro in an `#ifndef` / `#define` with a sensible sentinel (e.g., define pin macros
like BUILT_IN_ALS_PIN to -1, numeric/config macros to 0, and any name macros to
"Unknown") and include a matching `#pragma` message warning the user that the
macro was not defined and can be added in KnownProcessors.h; ensure you use the
same style and placement as the existing
LOGGER_BOARD/OPERATING_VOLTAGE/BATTERY_* blocks and reference the macro names
(e.g., BUILT_IN_ALS_PIN) so future unguarded uses are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/Further-Reading/Sleep-Configurations.md`:
- Around line 206-209: The documentation line incorrectly states
Logger::disablePeripheralShutdown(false) "re-enable[s] pin tri-state"; update
that sentence to say it re-enables peripheral shutdown (or reverts the previous
disable) instead of referring to pin tri-state, and keep the clarifying note
that no peripheral state changes occur until systemSleep() is invoked; locate
the references to Logger::disablePeripheralShutdown and systemSleep in the
Sleep-Configurations page and replace the misworded phrase with wording like
"re-enable peripheral shutdown" or "revert peripheral shutdown disablement."

In `@examples/AWS_IoT_Core/ReadMe.md`:
- Line 59: Update the sentence that currently reads "Find and replace the text
`YourWiFiSSID` with your wifi name (SSID) and `YourWiFiPassword` with your wifi
password." to use "WiFi" capitalization consistently; replace both occurrences
of the lowercase "wifi" with "WiFi" while keeping the `YourWiFiSSID` and
`YourWiFiPassword` tokens unchanged so the final sentence reads that users
should replace those tokens with their "WiFi" name (SSID) and "WiFi" password.

In `@examples/baro_rho_correction/ReadMe.md`:
- Line 50: Update the README text that tells users to "Change the 'YourLoggerID'
... to the loggerID assigned by Stroud" so the organization reference is
consistent with the rest of the doc; either replace "Stroud" with "Monitor My
Watershed" or remove the organization name entirely, keeping the instruction
that the placeholder "YourLoggerID" should be replaced with the actual logger
ID.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 121-129: In the instructions paragraph that reads "If you are
using a Hologram SIM, use the text `hologram`.." remove the extra period so it
ends with a single period ("`hologram`.); update the sentence containing the
literal token `hologram` to have only one trailing period.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Line 237: The code computes byte_error using abs(bytes_transferred -
image_size) which may call the 16-bit int abs on some platforms; change to use
labs() for a 32-bit-safe absolute value: compute the difference between
bytes_transferred and image_size as a long (or cast both operands to long) and
call labs() so byte_error = labs((long)bytes_transferred - (long)image_size);
update the expression where byte_error is assigned in GeoluxHydroCam.cpp to use
labs() and appropriate casts to ensure correct behavior on Arduino targets.

In `@src/sensors/ProcessorAnalog.h`:
- Around line 174-176: Update the doc comment for the parameter `dataPin` in
ProcessorAnalog.h (used by the ProcessorAnalog class/constructor) to remove EC
probe–specific wording and make it sensor-agnostic; e.g., describe `dataPin` as
"the processor ADC pin used to read an analog voltage from the connected sensor
(note that not all processor pins support analog input; analog-capable pins are
usually prefixed with 'A')" so the comment applies to any analog sensor.

In `@src/sensors/TurnerTurbidityPlus.h`:
- Around line 312-317: The documentation for the private member _PGA_gain
wrongly states "default is GAIN_DEFAULT (0)"; update this comment to match the
constructor/default parameter which use GAIN_ONE — ensure the comment block near
_PGA_gain and any `@brief/`@todo text references GAIN_ONE (or remove the explicit
numeric value) so the documentation is consistent with the TurnerTurbidityPlus
constructor and the default parameter.

---

Outside diff comments:
In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md`:
- Around line 48-52: Update the README instruction to match the placeholder used
in the code snippet: replace the reference to "XXXX" with "YourLoggerID" so
users know to set the LoggerID constant (const char *LoggerID = "YourLoggerID";)
to the logger ID assigned by Stroud; ensure the surrounding prose consistently
uses "YourLoggerID" when referring to that placeholder.

In `@examples/simple_logging/ReadMe.md`:
- Around line 43-47: Update the README instruction so it matches the code
example: replace the "XXXX" placeholder in the instruction text with
"YourLoggerID" (the same placeholder used in the const char *LoggerID =
"YourLoggerID"; example) so users are told to set LoggerID to the value assigned
by Stroud; ensure the instruction explicitly references the LoggerID constant
name to avoid ambiguity.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 107-112: The instruction incorrectly tells users to paste their
"certificate" into the placeholder 'paste the private key here'—update the copy
so it explicitly instructs pasting the private key (not the certificate) from
the file that ends with "private.pem.key"; change the sentence that currently
reads to paste the "certificate" to instead say "private key", and reinforce
that the pasted text must begin with '-----BEGIN RSA PRIVATE KEY-----' and end
with '-----END RSA PRIVATE KEY-----' (i.e., paste the contents of the
private.pem.key file into the placeholder).

In `@src/SensorBase.cpp`:
- Around line 398-414: The second branch uses the exact same condition as the
first, making it unreachable; update the else-if for the "add to existing good
results" branch in SensorBase.cpp so it checks that sensorValues[resultNumber]
is already a valid/real value (i.e., not the -9999 sentinel and not NaN) while
resultValue is good, instead of checking that sensorValues is the sentinel/NaN;
locate the two branches around sensorValues[resultNumber],
numberGoodMeasurementsMade[resultNumber], and getSensorNameAndLocation() and
change the second branch's condition to require the existing sensorValues entry
be valid before adding and incrementing the good-measurement count.

In `@src/sensors/GroPointParent.h`:
- Around line 38-49: The class documentation still describes dual-power-pin
behavior (references to "one or two power pins", using -1 for a second pin,
shared/unshared ordering) but the implementation no longer manages a second pin
(e.g., _powerPin2 was removed or delegated); update the comment block in
GroPointParent.h to match the current design by removing or rewriting the
dual-pin instructions and instead document the actual power management (mention
that only a single power pin is supported or that power is handled by the parent
Sensor class), and reference the class/field names involved (GroPointParent,
_powerPin2, Sensor) so readers know whether to use a single pin or rely on
Sensor for power control.
- Around line 106-110: The constructor docblock for GroPointParent mentions a
powerPin2 parameter for RS485 adapter power control but the implementation no
longer stores/uses it; update the documentation for the GroPointParent
constructor to match reality: either remove the `@param` powerPin2 entry if the
constructor signature no longer accepts or uses that argument, or change the
description to reflect how powerPin2 is actually used/stored in the class
(reference the GroPointParent constructor and any member variables that would
hold power pins) so the doc comment and implementation stay in sync.

In `@src/sensors/TIINA219.cpp`:
- Around line 40-64: TIINA219::setup currently ignores the bool returned by
Sensor::setup(); capture that result (e.g., bool baseOk = Sensor::setup()) and
combine it with the INA219 init result (bool phyOk = ina219_phy.begin(_i2c);
bool success = baseOk && phyOk) so the method only returns true if both setup
steps succeed; ensure you still call
checkPowerOn()/powerUp()/powerDown()/waitForWarmUp() as before and
setStatusBit(ERROR_OCCURRED) / clearStatusBit(SETUP_SUCCESSFUL) based on the
combined success value so failures in Sensor::setup are propagated.

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes addition registers two custom git
filters (smudgePasswords and disableDebug) but there are no corresponding
filter.<name>.clean/smudge definitions or contributor docs; add filter driver
definitions and usage docs: update repository setup scripts or CONTRIBUTING.md
to define git config entries for
filter.smudgePasswords.smudge/filter.smudgePasswords.clean and
filter.disableDebug.smudge/filter.disableDebug.clean (or provide a .gitconfig
snippet and a small setup script that runs git config --local ...), and document
when and why to use the smudgePasswords and disableDebug filters so contributors
know to enable them.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to modem.gsmModem.forceModemBaud(modemSerial,
static_cast<uint32_t>(modemBaud)) may not exist in all TinyGSM forks; verify
that forceModemBaud is present in your TinyGSM dependency and if not, replace
the call with a supported helper (for example use TinyGsmAutoBaud(modemSerial,
modemBaud) or modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) depending
on the API) or guard it behind a compile-time check/macros so the code uses the
available baud helper; check symbols forceModemBaud, TinyGsmAutoBaud, setBaud,
and the modem.gsmModem usage when making the change.

In `@examples/example_dependencies.json`:
- Line 29: The dependency pin in examples/example_dependencies.json locks
"Adafruit NeoPixel" to "~1.15.4", which may not exist in the Arduino Library
Manager index; verify that the release "1.15.4" is present and if it is missing
replace "~1.15.4" with a valid published version (e.g., the exact version string
found in the index) or a supported semver range, updating the "Adafruit
NeoPixel" entry accordingly so dependency resolution succeeds.

In `@examples/examples.dox`:
- Around line 67-77: Update the broken cross-reference by replacing the old
anchor name examples_outdated with the new anchor page_examples_outdated
wherever it’s referenced (specifically change any `@ref` examples_outdated to `@ref`
page_examples_outdated); check the surrounding subpage/@page entries (e.g., the
`@subpage` and `@page` declarations) to ensure they use the same
page_examples_outdated anchor consistently so the documentation link is no
longer broken.

In `@examples/menu_a_la_carte/menu_a_la_carte.ino`:
- Around line 3796-3799: The call to modem.gsmModem.forceModemBaud(...) may not
exist in some TinyGSM forks; verify that the TinyGSM dependency provides
forceModemBaud and if not replace it with a supported helper (e.g.,
TinyGsmAutoBaud::autoBaud(...) or a modem.gsmModem.setBaud(...) variant) or fall
back to directly setting the serial baud (modemSerial.begin(modemBaud)); update
references at the two call sites (modem.gsmModem.forceModemBaud, modemSerial,
modemBaud) and/or guard the call with a compile-time check (`#ifdef`
HAS_FORCE_MODEM_BAUD or similar) after confirming which symbol exists in your
TinyGSM headers.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The include for TinyGsmClientSIM7000SSL.h and the use of
TinyGsmSim7000SSL (tied to TINY_GSM_MODEM_SIM7000SSL) must be verified against
the EnviroDIY fork: check that the header TinyGsmClientSIM7000SSL.h and the
modem class TinyGsmSim7000SSL actually exist in the EnviroDIY TinyGSM sources;
if they do not, replace the include with the correct EnviroDIY header name
(e.g., the pattern used like TinyGsmClientESP32.h) or wrap the include/usage
behind a conditional `#ifdef` that matches TINY_GSM_MODEM_SIM7000SSL, and update
any references to the modem class to the correct EnviroDIY symbol so the build
uses the forked header/class consistently.

In `@src/modems/SodaqUBeeU201.h`:
- Around line 78-79: Public API changed: the exposed type was switched to
TinyGsmUBLOX (in SodaqUBeeU201.h) instead of the previous TinyGsm causing
consumers using gsmModem to break; update the project release notes and add a
migration note explaining that callers must replace declarations/types of
gsmModem/TinyGsm with TinyGsmUBLOX (or provide a typedef alias), update any
downstream examples or sample code that declares gsmModem to use TinyGsmUBLOX,
and add a short code snippet or suggested typedef (e.g., using gsmModem =
TinyGsmUBLOX) to the changelog/migration section to ease transition.

In `@src/ModSensorConfig.h`:
- Line 12: Update the grammatical error in the header comment note in
ModSensorConfig.h: change the phrase in the `@note` comment that currently reads
"must be include in every header file" to "must be included in every header
file" so the comment is clear; locate the comment block containing "@note This
file must be include..." and correct "include" to "included".

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 350-355: minTime currently becomes 0 when _alwaysAutoFocus is
false, which bypasses the stabilization wait; change the ternary for minTime so
the false branch uses the stabilization delay instead of 0. Specifically, update
the minTime calculation (the variable named minTime near maxTime in
GeoluxHydroCam.cpp) to return HYDROCAM_AUTOFOCUS_TIME_MS + _stabilizationTime_ms
when _alwaysAutoFocus is true and _stabilizationTime_ms when false, keeping
maxTime as-is; this ensures calls like isCameraReady() respect the stabilization
delay.

In `@src/sensors/GroPointParent.cpp`:
- Around line 210-239: Moisture and temperature results are now stored
independently via verifyAndAddMeasurementResult(...) when success or successT
are true; keep that behavior and preserve the existing overall success semantics
by returning bumpMeasurementAttemptCount((success && successT)) from the
function, referencing verifyAndAddMeasurementResult and
bumpMeasurementAttemptCount to locate the logic and ensure no further
conditional gating removes partial data.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop using rangeAttempts < 25 can block for
seconds because each iteration waits for _stream->parseInt() stream timeout and
compounds with bumpMeasurementAttemptCount; change the loop to bound by elapsed
time instead of a fixed count: record a start timestamp before the loop and keep
retrying while millis() - start < <reasonable_time_budget_ms> (e.g., based on
the stream timeout or a configured per-measurement budget), still triggering the
sensor via _triggerPin and parsing with _stream->parseInt() and preserving the
existing validation (result <= 0 || result >= _maxRange) and conversion
(_convertCm). Ensure you also break and set result = -9999 if the time budget
expires so callers relying on success flag behave the same.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 57-69: The function MaximDS3231::addSingleMeasurementResult should
validate the temperature returned by rtc.getTemperature() before marking the
measurement attempt successful; check tempVal for NaN (e.g., using std::isnan)
and only call verifyAndAddMeasurementResult(...) and
bumpMeasurementAttemptCount(true) when the value is valid, otherwise log a
warning/error referencing getSensorNameAndLocation()/Temp and return
bumpMeasurementAttemptCount(false) so retries can occur; update the flow around
rtc.getTemperature(), verifyAndAddMeasurementResult, and
bumpMeasurementAttemptCount accordingly.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 93-167: The I2C read/validation flow in
RainCounterI2C::addSingleMeasurementResult uses _i2c and basic byte checks but
can mis-handle partial reads and negative tip/rain detection; fix by ensuring
the code treats the actual number of bytes read (byte_in) when deciding v0.1 vs
v0.2 payloads, validate that byte_in is either 1 or 4 before assembling
SerialBuffer, and avoid interpreting uninitialized bytes (use byte_in to limit
shifts/concatenation), then only call
verifyAndAddMeasurementResult(BUCKET_RAIN_VAR_NUM, rain) and
verifyAndAddMeasurementResult(BUCKET_TIPS_VAR_NUM, tips) when both values were
derived from a valid byte count; keep using _i2c, _i2cAddressHex, SerialBuffer,
bumpMeasurementAttemptCount(success) and verifyAndAddMeasurementResult to locate
the changes.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 112-120: The code currently assigns temp_val and humid_val from
temp_event and humidity_event unconditionally, overwriting sentinel (-9999) when
ret_val is false; change the logic in the block around temp_val, humid_val,
temp_event, humidity_event and ret_val (the getEvent result) so that you only
assign temp_val = temp_event.temperature and humid_val =
humidity_event.relative_humidity when ret_val is true (i.e., the getEvent
succeeded); leave the sentinel values untouched when ret_val is false and keep
the existing logging branches (MS_DBG(...)) as-is.

In `@src/VariableArray.cpp`:
- Around line 347-353: The variables sName and cycCount are only declared inside
the MS_VARIABLEARRAY_DEBUG guard but are referenced by MS_DBG() calls regardless
when MS_DEBUGGING_STD is enabled; move the declarations of sName and cycCount
out of the `#if/`#endif so they are always defined (e.g., declare String sName and
String cycCount before the conditional), and keep the assignment that calls
sensorList[i]->getSensorNameAndLocation(),
getNumberCompleteMeasurementsAttempts(), and getNumberRetryAttemptsMade() inside
the conditional if you want to avoid extra work when those debug flags are off;
ensure references in MS_DBG() use these always-defined symbols.

---

Nitpick comments:
In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: The phrasing "be very, very, very careful" in the
README is overly repetitive; update the sentence in the
examples/menu_a_la_carte/ReadMe.md where it currently reads "If using this
method, be very, very, very careful to make sure the order of your variables
exactly matches the order of your UUIDs." to a clearer single stronger modifier
such as "If using this method, be extremely careful to make sure the order of
your variables exactly matches the order of your UUIDs." or "exercise great
care" to improve readability.

In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md`:
- Line 9: The sentence "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 Monitor My Watershed." is long and
repetitive; replace it with a tighter phrasing (for example: "Use this example
to deploy a logger when live streaming and Monitor My Watershed uploads are not
available.") in ReadMe.md so the intent is clear and the line reads more
concisely.

In `@src/SensorBase.cpp`:
- Around line 692-709: The function bumpMeasurementAttemptCount currently
increments _retryAttemptsMade unconditionally which is confusing; change it so
_retryAttemptsMade is only incremented when wasSuccessful is false, leaving
success paths to skip the increment and then reset _retryAttemptsMade to 0 when
wasSuccessful or when retries exceeded; keep the existing behavior of recording
_millisMeasurementCompleted, clearing _millisMeasurementRequested and status
bits via clearStatusBits(MEASUREMENT_ATTEMPTED, MEASUREMENT_SUCCESSFUL), and
still increment _measurementAttemptsCompleted and reset _retryAttemptsMade when
(wasSuccessful || _retryAttemptsMade > _allowedMeasurementRetries) so logic and
symbols (_retryAttemptsMade, _allowedMeasurementRetries,
_measurementAttemptsCompleted) remain consistent.

In `@src/sensors/AlphasenseCO2.cpp`:
- Around line 119-133: The voltage bounds check in AlphasenseCO2.cpp (the if
using adcVoltage < 3.6 && adcVoltage > -0.3) is based on ADS limits, not the
Alphasense CO2 sensor spec; verify the sensor's actual output voltage range from
the Alphasense datasheet and replace the hardcoded -0.3/3.6 values with properly
named constants (e.g., CO2_SENSOR_MIN_VOLTAGE, CO2_SENSOR_MAX_VOLTAGE) used in
the adcVoltage check inside the block that computes co2Current and calibResult
and calls verifyAndAddMeasurementResult(ALPHASENSE_CO2_VAR_NUM, ...) and
verifyAndAddMeasurementResult(ALPHASENSE_CO2_VOLTAGE_VAR_NUM, ...); update the
comment above the check to cite the datasheet source and, if the sensor output
depends on external circuitry (e.g., 250Ω resistor), document that in the
comment and add a fallback/guard if the ADC reading is out-of-spec (e.g., skip
measurement and set success=false or log an error).

In `@src/sensors/CampbellOBS3.cpp`:
- Around line 100-110: Add a debug log when adcVoltage is outside the accepted
range so out-of-range conditions are visible; inside the same function/block
that currently checks "if (adcVoltage < 3.6 && adcVoltage > -0.3)" add an else
branch that calls the existing debug logging macro (e.g., MS_DBG or MS_LOG) and
prints the adcVoltage value and the expected range, and ensure the function
still leaves success false and does not call verifyAndAddMeasurementResult for
OBS3_TURB_VAR_NUM or OBS3_VOLTAGE_VAR_NUM; reference the adcVoltage variable,
the success flag, and the MS_DBG logging macro to locate where to insert the
else branch.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-288: Add defensive default fallbacks for the BUILT_IN_ALS_*
macros (so they mirror the existing pattern for LOGGER_BOARD, OPERATING_VOLTAGE,
BATTERY_PIN, BATTERY_MULTIPLIER): wrap each macro in an `#ifndef` / `#define` with a
sensible sentinel (e.g., define pin macros like BUILT_IN_ALS_PIN to -1,
numeric/config macros to 0, and any name macros to "Unknown") and include a
matching `#pragma` message warning the user that the macro was not defined and can
be added in KnownProcessors.h; ensure you use the same style and placement as
the existing LOGGER_BOARD/OPERATING_VOLTAGE/BATTERY_* blocks and reference the
macro names (e.g., BUILT_IN_ALS_PIN) so future unguarded uses are safe.

In `@src/sensors/MaximDS18.cpp`:
- Around line 184-208: Add a NaN defensive check after calling
_internalDallasTemp.getTempC(_OneWireAddress) to ensure a NaN result doesn't
count as a valid reading; specifically, after result =
_internalDallasTemp.getTempC(_OneWireAddress) validate with std::isnan(result)
(or equivalent) and treat it like the existing invalid cases (do not call
verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result) and leave
success=false), then return via bumpMeasurementAttemptCount(success) as before;
you can still log the invalid/NaN case via MS_DBG and keep the early return when
getStatusBit(MEASUREMENT_SUCCESSFUL) is false.

In `@src/VariableArray.cpp`:
- Around line 376-377: The code directly manipulates Sensor's protected fields
(_measurementAttemptsCompleted and _measurementsToAverage) from VariableArray
relying on friend access; replace this with a clear public API: add a
Sensor::markMeasurementsComplete() (or similar) method that sets
_measurementAttemptsCompleted = _measurementsToAverage, and update VariableArray
to call sensorList[i]->markMeasurementsComplete() instead of touching the
protected members directly; alternatively, if you keep friend access, add an
explicit Sensor::skipDueToWakeFailure() helper to encapsulate the intent and
call that from VariableArray.
- Around line 476-556: Extract the complex shared-pin power-down check into a
helper method (e.g., bool canPowerDownSensor(uint8_t sensorIndex)) that iterates
_sensorCount and uses sensorList[sensorIndex]->getPowerPin(),
getSecondaryPowerPin(), and each
sensorList[k]->getNumberCompleteMeasurementsAttempts() vs
getNumberMeasurementsToAverage() to decide whether any other sensor sharing
either primary or secondary pin still needs measurements; replace the inline
for-loop and canPowerDown boolean in the existing block with a single call to
canPowerDownSensor(i) and call sensorList[i]->powerDown() when it returns true;
also move the duplicate debug iteration (inside the MS_VARIABLEARRAY_DEBUG
branch) to reuse the helper or a small helper-returned list/flag so the debug
messages remain accurate without duplicating the matching logic.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/publishers/MonitorMyWatershedPublisher.cpp (1)

137-178: ⚠️ Potential issue | 🟡 Minor

Guard against uint16_t underflow in JSON size calculation when buffer is empty.

The expression records * (25 + 2) + records - 1 underflows when records == 0, producing 65535 (signed -1 cast to uint16_t). This occurs when calculateJsonSize() is called from flushDataBuffer() with an empty log buffer—a scenario reachable via forceFlush parameter even when no records exist. The resulting incorrect Content-Length header causes malformed HTTP requests. Add an early return in calculateJsonSize() if records are zero:

Suggested guard
 uint16_t MonitorMyWatershedPublisher::calculateJsonSize() {
     uint8_t variables = _logBuffer.getNumVariables();
     int     records   = _logBuffer.getNumRecords();
+    if (records == 0) { return 0; }
     MS_DBG(F("Number of records in log buffer:"), records);

Alternatively, skip the flush entirely if no records exist in flushDataBuffer().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 137 - 178,
calculateJsonSize() can underflow when _logBuffer.getNumRecords() returns 0
because expressions like records * (25 + 2) + records - 1 produce negative
values cast into uint16_t; guard against this by adding an early check in
MonitorMyWatershedPublisher::calculateJsonSize(): if records == 0 return 0 (or a
minimal header size) immediately, or alternatively update flushDataBuffer() to
skip calling calculateJsonSize() when _logBuffer.getNumRecords() == 0 (check
_logBuffer.getNumRecords() before forceFlush handling) so no zero-record size
computation occurs.
src/sensors/ANBpH.h (1)

369-445: ⚠️ Potential issue | 🟠 Major

Verify downstream mappings after code index renumbering.
Shifting health/diagnostic/status to indices 5/6/7 changes the output ordering; any code that assumes prior positions (CSV headers, data portal mappings, or raw sensorValues indexing) can silently mis‑label data. Please confirm all consumers use the updated constants.

#!/bin/bash
# Find ANB pH variable index usage and raw sensorValues indexing.
rg -n "ANB_PH_(HEALTH|DIAGNOSTIC|STATUS)_CODE_VAR_NUM" -g'*.{h,cpp,ino}' -C2
rg -n "sensorValues\s*\[\s*4\s*\]" -g'*.{h,cpp,ino}' -C2
rg -n "ANBpH_(HealthCode|DiagnosticCode|StatusCode)" -g'*.{h,cpp,ino}' -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 369 - 445, The health/diagnostic/status
variable indices were changed to ANB_PH_HEALTH_CODE_VAR_NUM,
ANB_PH_DIAGNOSTIC_CODE_VAR_NUM, and ANB_PH_STATUS_CODE_VAR_NUM (now 5/6/7),
which can break any code that assumes the old positions or hardcodes
sensorValues[4] etc.; find every consumer (CSV header builders, data portal
mapping code, tests, and any direct sensorValues[N] accesses) and update them to
use these constants (or the appropriate ANBpH_* symbolic names like
ANBpH_HealthCode/DiagnosticCode/StatusCode) instead of numeric literals, adjust
header ordering where output order matters, and add/adjust tests to verify
output labels/columns match the new ordering.
🧹 Nitpick comments (8)
src/sensors/AOSongDHT.cpp (1)

15-23: Consider a named constant for retry count.

The literal 5 obscures intent; a named constant or config makes this easier to tune and understand later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AOSongDHT.cpp` around lines 15 - 23, Replace the magic literal 5
passed to setAllowedMeasurementRetries in the AOSongDHT constructor with a named
constant (e.g., DHT_DEFAULT_RETRY_COUNT or kDefaultMeasurementRetries) declared
near the class or file scope so the retry policy is clear and easily tunable;
update the AOSongDHT constructor call to use that constant and add the constant
definition in the header or top of the source file adjacent to other DHT_*
constants.
src/sensors/MaxBotixSonar.cpp (1)

155-155: Prefer idiomatic boolean expressions.

For cleaner, more expressive C++, use !success instead of success == false and _convertCm instead of _convertCm == true.

♻️ Suggested style improvements
-    while (success == false && rangeAttempts < 25) {
+    while (!success && rangeAttempts < 25) {
-            if (_convertCm == true) { result *= 10; }
+            if (_convertCm) { result *= 10; }

Also applies to: 189-189

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.cpp` at line 155, Replace explicit boolean
comparisons with idiomatic expressions: change occurrences of "success == false"
to "!success" in the loop condition inside MaxBotixSonar::... (the while using
variable success and rangeAttempts) and similarly replace "_convertCm == true"
with "_convertCm" (and any other occurrences like the one around line 189) to
use direct boolean variables; update only the conditional expressions — do not
alter the surrounding logic or variable names.
src/sensors/AlphasenseCO2.cpp (2)

122-127: Consider extracting magic numbers to named constants.

The conversion formula is correct for a 4-20mA current loop with a 250Ω sensing resistor, but the hardcoded values could benefit from named constants for clarity and maintainability.

♻️ Suggested improvement
+// Constants for current-to-CO2 conversion
+// Based on Alphasense CO2 sensor with 4-20mA output and 250 Ohm sensing resistor
+static constexpr float SENSING_RESISTOR_OHMS = 250.0;
+static constexpr float CO2_PPM_PER_MA = 312.5;
+static constexpr float CO2_PPM_OFFSET = 1250.0;

 if (adcVoltage < 3.6 && adcVoltage > -0.3) {
     // Convert voltage to current (mA) - assuming a 250 Ohm resistor is in
     // series
-    co2Current = (adcVoltage / 250) * 1000;
+    co2Current = (adcVoltage / SENSING_RESISTOR_OHMS) * 1000;
     // Convert current to ppm (using a formula recommended by the sensor
     // manufacturer)
-    calibResult = 312.5 * co2Current - 1250;
+    calibResult = CO2_PPM_PER_MA * co2Current - CO2_PPM_OFFSET;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 122 - 127, Extract the magic
numbers used in the conversion into named constants and use them in the
calculations: replace the literal 250 with a constant like SENSE_RESISTOR_OHMS,
1000 with MILLI_FACTOR or MA_CONVERSION_FACTOR, and 312.5 and 1250 with
PPM_SCALE and PPM_OFFSET (or similarly descriptive names), then update the
co2Current and calibResult expressions to use those constants (e.g., co2Current
= (adcVoltage / SENSE_RESISTOR_OHMS) * MA_CONVERSION_FACTOR; calibResult =
PPM_SCALE * co2Current - PPM_OFFSET) so the intent and maintainability are
clear.

119-120: Address TODO: Verify voltage range for CO2 sensor.

The TODO indicates the voltage range check needs verification for the specific CO2 sensor. The current range (-0.3 to 3.6V) matches the ADS powered at 3.3V, but may need adjustment for the actual sensor output characteristics.

Would you like me to help research the Alphasense CO2 sensor specifications to determine the correct voltage range, or open an issue to track this verification task?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 119 - 120, Replace the TODO in
src/sensors/AlphasenseCO2.cpp about verifying the voltage range with an
actionable change: research the specific Alphasense CO2 sensor datasheet
(reference the exact model used by this code) and update the voltage range
constants or clamps in this file to match the sensor's output spec instead of
the ADS ADC powered-range (-0.3 to 3.6V); if you can't confirm now, create a
tracked issue and annotate the file with the sensor model, a link to the
datasheet, and mark the current range as "ADC-derived" so future reviewers know
to change the constants (update any variables/constants referenced in this
module that implement the range check).
src/sensors/ANBpH.cpp (1)

612-612: Consider using explicit initialization for unsigned type.

Assigning -1 to uint8_t wraps to 255, which works but is slightly confusing since it's an output parameter that gets overwritten by getNowParts. Consider initializing to 0 for clarity.

-    uint8_t tz_offset = -1;
+    uint8_t tz_offset = 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.cpp` at line 612, The local unsigned variable tz_offset is
initialized with -1 which wraps to 255 and is confusing; change the
initialization of tz_offset in ANBpH.cpp (the uint8_t tz_offset variable used
before calling getNowParts) to an explicit neutral value such as 0 (or another
clearly documented sentinel) so the type and intent are clear and getNowParts
will overwrite it as intended.
src/sensors/ProcessorAnalog.cpp (1)

52-66: Consider reducing priming reads.

The code performs two priming reads before the actual measurement (lines 53 and 55). Typically, one priming read is sufficient to discard any residual charge from the previous ADC channel. Unless there's a specific reason for two priming reads (e.g., high-impedance source), this could be simplified.

That said, the current implementation is functionally correct and the extra read has minimal overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ProcessorAnalog.cpp` around lines 52 - 66, The code does two
priming analogRead() calls on _dataPin before the actual measurement; remove one
of those redundant priming reads so there is only a single discard read prior to
capturing rawAnalog. Specifically, in ProcessorAnalog.cpp replace the two
consecutive analogRead(_dataPin) priming calls with a single
analogRead(_dataPin) priming call that precedes the float rawAnalog =
analogRead(_dataPin) measurement, leaving the conversion to sensorValue_analog
and the subsequent verifyAndAddMeasurementResult(PROCESSOR_ANALOG_VAR_NUM,
sensorValue_analog) and bumpMeasurementAttemptCount(true) logic unchanged.
src/sensors/KellerParent.cpp (1)

124-126: Consider simplifying redundant validation.

Lines 109-110 already validate waterPressureBar and waterTemperatureC. The re-validation at lines 124-126 only needs to check waterDepthM since the other values haven't changed.

♻️ Simplified validation
-    success &= (!isnan(waterPressureBar) && waterPressureBar != -9999 &&
-                !isnan(waterTemperatureC) && waterTemperatureC != -9999 &&
-                !isnan(waterDepthM) && waterDepthM != -9999);
+    success &= (!isnan(waterDepthM) && waterDepthM != -9999);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KellerParent.cpp` around lines 124 - 126, The final aggregated
success check redundantly re-validates waterPressureBar and waterTemperatureC;
update the expression that assigns to success (the line using success &= ...) to
only validate waterDepthM (e.g., ensure !isnan(waterDepthM) && waterDepthM !=
-9999) since waterPressureBar and waterTemperatureC were already validated
earlier, keeping the same pattern/semantics used elsewhere in KellerParent.cpp.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

33-96: Reduce repeated endpoint defaulting across constructors.
The same host/path/port defaults are repeated in every constructor; a single helper reduces drift and makes changes safer.

♻️ Possible refactor
+void MonitorMyWatershedPublisher::initEndpointDefaults() {
+    setHost("monitormywatershed.org");
+    setPath("/api/data-stream/");
+    setPort(80);
+}
+
 MonitorMyWatershedPublisher::MonitorMyWatershedPublisher() : dataPublisher() {
-    setHost("monitormywatershed.org");
-    setPath("/api/data-stream/");
-    setPort(80);
+    initEndpointDefaults();
 }
@@
     _logBuffer.setNumVariables(_baseLogger->getArrayVarCount());
-    setHost("monitormywatershed.org");
-    setPath("/api/data-stream/");
-    setPort(80);
+    initEndpointDefaults();
 }

As per coding guidelines **/*.cpp: Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 33 - 96, The
constructors of MonitorMyWatershedPublisher repeat the same host/path/port
setup; add a single private helper method (e.g., initEndpointDefaults() or
setEndpointDefaults()) that calls setHost("monitormywatershed.org"),
setPath("/api/data-stream/"), and setPort(80), then replace the repeated lines
in every MonitorMyWatershedPublisher constructor with a single call to that
helper (or alternatively implement one delegating constructor that calls the
helper), and update the class declaration to include the new private helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 159-176: setHost and setPath currently store raw const char*
pointers which may dangle if callers pass temporaries; update the
MonitorMyWatershedPublisher API to use owned strings: change internal storage to
std::string and make setHost/setPath accept either const std::string& or copy
the incoming const char* into the member, and update getPath to return a const
std::string& (or std::string) accordingly; alternatively, if you prefer the
minimal change, add clear documentation comments on setHost/setPath and the
class that the caller must guarantee the lifetime of the passed C-strings until
after all publish calls.

In `@src/SensorBase.h`:
- Around line 741-744: The comment for uint8_t _measurementAttemptsCompleted =
0; incorrectly states “since last power on” while the counter is actually reset
each update by clearValues(); either update the doc comment to reflect that it
counts attempts completed during the current update cycle (or since
clearValues() was last called), or move the reset out of clearValues() to
preserve “since last power on” semantics; reference
_measurementAttemptsCompleted and clearValues() when making the change so the
intended lifetime is clear and consistent.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 78-85: The pressure upper bound is hard-coded to 14000 mbar;
replace that literal with a dynamic check using the instance field _maxPressure
(which is in bar) converted to mbar (multiply by 1000) so all sensor models (up
to 30 bar) are accepted. In the validation block that uses getPressure()/press
and verifyAndAddMeasurementResult(), change the condition press <= 14000.0 to
press <= (_maxPressure * 1000.0) and keep the rest of the checks (temp range,
press > 0.0) intact; ensure you reference _maxPressure and getPressure() when
locating the code to update.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 105-107: Fix the typo in the inline comment that reads "effecting
others" to use the correct word "affecting others"; locate the comment near the
ADC setup for the auxiliary ADC object (the block mentioning "Create an
auxiliary ADC object" andadc setup so each sensor may set the gain
appropriately) in TurnerTurbidityPlus.cpp and update the wording to "affecting"
to match coding guidelines and improve clarity.

---

Outside diff comments:
In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 137-178: calculateJsonSize() can underflow when
_logBuffer.getNumRecords() returns 0 because expressions like records * (25 + 2)
+ records - 1 produce negative values cast into uint16_t; guard against this by
adding an early check in MonitorMyWatershedPublisher::calculateJsonSize(): if
records == 0 return 0 (or a minimal header size) immediately, or alternatively
update flushDataBuffer() to skip calling calculateJsonSize() when
_logBuffer.getNumRecords() == 0 (check _logBuffer.getNumRecords() before
forceFlush handling) so no zero-record size computation occurs.

In `@src/sensors/ANBpH.h`:
- Around line 369-445: The health/diagnostic/status variable indices were
changed to ANB_PH_HEALTH_CODE_VAR_NUM, ANB_PH_DIAGNOSTIC_CODE_VAR_NUM, and
ANB_PH_STATUS_CODE_VAR_NUM (now 5/6/7), which can break any code that assumes
the old positions or hardcodes sensorValues[4] etc.; find every consumer (CSV
header builders, data portal mapping code, tests, and any direct sensorValues[N]
accesses) and update them to use these constants (or the appropriate ANBpH_*
symbolic names like ANBpH_HealthCode/DiagnosticCode/StatusCode) instead of
numeric literals, adjust header ordering where output order matters, and
add/adjust tests to verify output labels/columns match the new ordering.

---

Duplicate comments:
In @.gitattributes:
- Around line 35-38: The .gitattributes entries reference custom filter drivers
(smudgePasswords and disableDebug) but there are no matching Git filter driver
definitions; add corresponding filter.<name>.clean and filter.<name>.smudge
config entries (or a documented setup script/README note) so Git won't warn and
will apply filtering, e.g., define filter.smudgePasswords.* and
filter.disableDebug.* in project-level git config or provide contributor docs
explaining how to set them up; ensure the README or CONTRIBUTING mentions these
symbols (smudgePasswords, disableDebug, and the .gitattributes entries) and how
to verify with git config commands.

In `@ChangeLog.md`:
- Around line 74-80: Combine the repetitive "Removed ..." bullets into a concise
grouped paragraph: mention removal of the _maxSamplesToAverage parameter and its
countMaxToAverage() function, the unnecessary copy doc calls for inherited
functions/properties, the now-unneeded overrides of powerUp and powerDown (since
sensors have two power pins), removal of references to the EnviroDIY data
portal, and removal of all defines from example sketches; keep the warning about
defining values for TinyGSM/MQTT and ModSensorConfig.h (and the One Definition
Rule) as a separate sentence for emphasis and clarity, referencing the symbols
_maxSamplesToAverage, countMaxToAverage, powerUp, powerDown, EnviroDIY, TinyGSM,
MQTT, and ModSensorConfig.h so reviewers can locate the changes.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 480-483: The call to modem.gsmModem.forceModemBaud(...) is likely
not part of upstream TinyGSM and will fail to compile; replace this with the
supported TinyGSM workflow: attempt TinyGsmAutoBaud (or call
gsmModem.getBaud/autobaud helper) and then gsmModem.setBaud(modemSerial,
modemBaud) (or use the EnviroDIY fork equivalent if that fork actually defines
forceModemBaud); update both occurrences (the if-block around
modem.gsmModem.testAT() and the repeated block at lines ~495-498) to use
TinyGsmAutoBaud/getBaud + setBaud, or add a compile-time check/guard that
verifies the fork provides forceModemBaud before calling it.

In `@examples/example_dependencies.json`:
- Around line 26-30: The dependency entry for "Adafruit NeoPixel" currently pins
"version": "~1.15.4", which may not exist in the Arduino Library Manager index;
verify the available versions and update the "version" value to a valid
published release (or remove the tilde to pin an exact version). Locate the JSON
object with "name": "Adafruit NeoPixel" and replace "~1.15.4" with a confirmed
version string (e.g., "1.x.y") that appears in the Arduino library_index.json,
or change to a valid semver spec supported by your resolver.

In `@examples/examples.dox`:
- Around line 67-78: Update the broken Doxygen reference: replace the `@ref`
target "examples_outdated" with the renamed page identifier
"page_examples_outdated" wherever it appears (e.g., the line that reads
[Examples for Outdated Hardware](`@ref` examples_outdated)); also check related
subpage tags like "@subpage page_examples_outdated" and "@subpage
example_learn_envirodiy" to ensure any cross-references use the correct page
name "page_examples_outdated".

In `@examples/menu_a_la_carte/menu_a_la_carte.ino`:
- Around line 3796-3798: The call to modem.gsmModem.forceModemBaud(modemSerial,
static_cast<uint32_t>(modemBaud)) likely uses a nonstandard API (forceModemBaud)
and will not compile against upstream TinyGSM; replace this with the supported
TinyGSM autobaud/setBaud workflow or confirm the repo fork provides
forceModemBaud. Specifically, either use the TinyGSM autobaud helper
(TinyGsmAutoBaud) to detect baud on modemSerial and then call
setBaud(modemSerial, modemBaud) on the modem object, or, if this fork truly
exposes forceModemBaud, add a compile-time check or comment confirming
availability; update references around modem.gsmModem, modemSerial, and
modemBaud accordingly and remove/replace duplicate occurrences at the nearby
block (lines ~3819-3821).

In `@src/modems/EspressifESP32.h`:
- Around line 159-163: The public concrete field gsmModem was changed to
TinyGsmESP32 which can break external code expecting the prior TinyGsm type;
make a non-breaking change by hiding the concrete member inside class
EspressifESP32 (change gsmModem to private/protected) and expose a type-agnostic
accessor (e.g., a method returning TinyGsm& or a pointer/abstract interface) so
callers use the stable API instead of the concrete TinyGsmESP32 type;
alternatively revert the declaration to the original TinyGsm type if you must
keep it public, and run the suggested grep to verify external usages of
gsmModem/EspressifESP32 are compatible.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: Confirm that the EnviroDIY fork contains the SIM7000 SSL symbols by
checking for the header TinyGsmClientSIM7000SSL.h and the type
TinyGsmSim7000SSL; if they are missing, add a fallback include/typedef path or
conditional compilation around the include in SIMComSIM7000.h (e.g., test for
HAVE_TINYGSM_SIM7000SSL or presence of TinyGsmClientSIM7000SSL.h) and fall back
to the upstream TinyGsmClient or TinyGsm classes (or provide a shim typedef for
TinyGsmSim7000SSL) so compilation succeeds when the fork-specific header/type
are absent.

In `@src/ModSensorConfig.h`:
- Line 12: Update the note comment in ModSensorConfig.h to correct the grammar:
change the phrase "must be include" to "must be included" in the `@note` line so
the comment reads "...This file must be included in every header file in the
library - before..."; locate the `@note` comment in ModSensorConfig.h to make this
one-word fix.

In `@src/sensors/ANBpH.cpp`:
- Around line 152-184: The interval validation/clamping logic around
_loggingIntervalMinutes correctly enforces [10,240] for power-cycled sensors
while allowing 0 for always-powered devices; no code changes required—leave the
checks that set programmedInterval and the calls to
_anb_sensor.setIntervalTime(programmedInterval) / setIntervalTime(0) and the
retVal &= intervalSet line as-is.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 251-255: The code currently assigns _filename even when
bytes_transferred != image_size so getLastSavedImageName() can return partial
files; change the logic so the attempted name is tracked separately (e.g.,
_lastAttemptedFilename = filename) and only set _filename = filename when
success is true (where success == bytes_transferred == image_size), or
alternatively move the _filename assignment inside the success branch that
computes success; update any uses that expect only successful filenames to read
_filename and use _lastAttemptedFilename when needed.

In `@src/sensors/GroPointParent.cpp`:
- Around line 165-240: The method GroPointParent::addSingleMeasurementResult
currently treats the overall measurement as failed unless both moisture and
temperature succeed, which lets successful measurements accumulate while retries
occur; change the final success gating so partial successes count by returning
bumpMeasurementAttemptCount(success || successT) instead of
bumpMeasurementAttemptCount((success && successT)), leaving the existing
verifyAndAddMeasurementResult calls as-is; this uses the existing symbols
addSingleMeasurementResult, success, successT, verifyAndAddMeasurementResult,
and bumpMeasurementAttemptCount to locate and implement the fix.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop using rangeAttempts can block for many
seconds because each parseInt() waits the stream timeout; change the loop to
also respect a measurement time budget by recording a start timestamp (millis())
before the loop and update the condition to break when elapsed >=
HRXL_MEASUREMENT_TIME_MS (i.e. while(success==false && rangeAttempts<25 &&
(millis()-start) < HRXL_MEASUREMENT_TIME_MS)); leave the trigger handling
(_triggerPin), result parsing (_stream->parseInt(), _stream->read()), and
conversion (_convertCm) logic unchanged but ensure the loop exits cleanly on
timeout and sets a failure result (-9999) if the time budget is exceeded.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 58-69: The code unconditionally calls
bumpMeasurementAttemptCount(true) even if rtc.getTemperature() failed or
returned NaN; update the logic in the DS3231 temperature read path so you check
the returned value from rtc.getTemperature() (or any library status flag if
available) before marking the attempt successful: after calling
rtc.getTemperature() in the function that calls
verifyAndAddMeasurementResult(DS3231_TEMP_VAR_NUM, tempVal), detect failure
(e.g., using std::isnan(tempVal) or the library's status API) and only call
bumpMeasurementAttemptCount(true) when the temperature is valid, otherwise call
bumpMeasurementAttemptCount(false) (or return a failure) so retries are not
suppressed.

In `@src/VariableArray.cpp`:
- Around line 370-395: The debug variables sName and cycCount are declared only
inside the MS_VARIABLEARRAY_DEBUG/#if block but are used unconditionally in the
MS_DBG call; fix by making their declarations unconditional (move the calls to
sensorList[i]->getSensorNameAndLocation() and
getNumberCompleteMeasurementsAttempts()/getNumberRetryAttemptsMade() outside the
`#if`) or by surrounding the MS_DBG invocation with the same `#if/`#endif so sName
and cycCount are only referenced when defined; update the code around
sensorList, sName, cycCount and the MS_DBG call to ensure no use of those
symbols occurs unless the debug macros are enabled.

---

Nitpick comments:
In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 33-96: The constructors of MonitorMyWatershedPublisher repeat the
same host/path/port setup; add a single private helper method (e.g.,
initEndpointDefaults() or setEndpointDefaults()) that calls
setHost("monitormywatershed.org"), setPath("/api/data-stream/"), and
setPort(80), then replace the repeated lines in every
MonitorMyWatershedPublisher constructor with a single call to that helper (or
alternatively implement one delegating constructor that calls the helper), and
update the class declaration to include the new private helper.

In `@src/sensors/AlphasenseCO2.cpp`:
- Around line 122-127: Extract the magic numbers used in the conversion into
named constants and use them in the calculations: replace the literal 250 with a
constant like SENSE_RESISTOR_OHMS, 1000 with MILLI_FACTOR or
MA_CONVERSION_FACTOR, and 312.5 and 1250 with PPM_SCALE and PPM_OFFSET (or
similarly descriptive names), then update the co2Current and calibResult
expressions to use those constants (e.g., co2Current = (adcVoltage /
SENSE_RESISTOR_OHMS) * MA_CONVERSION_FACTOR; calibResult = PPM_SCALE *
co2Current - PPM_OFFSET) so the intent and maintainability are clear.
- Around line 119-120: Replace the TODO in src/sensors/AlphasenseCO2.cpp about
verifying the voltage range with an actionable change: research the specific
Alphasense CO2 sensor datasheet (reference the exact model used by this code)
and update the voltage range constants or clamps in this file to match the
sensor's output spec instead of the ADS ADC powered-range (-0.3 to 3.6V); if you
can't confirm now, create a tracked issue and annotate the file with the sensor
model, a link to the datasheet, and mark the current range as "ADC-derived" so
future reviewers know to change the constants (update any variables/constants
referenced in this module that implement the range check).

In `@src/sensors/ANBpH.cpp`:
- Line 612: The local unsigned variable tz_offset is initialized with -1 which
wraps to 255 and is confusing; change the initialization of tz_offset in
ANBpH.cpp (the uint8_t tz_offset variable used before calling getNowParts) to an
explicit neutral value such as 0 (or another clearly documented sentinel) so the
type and intent are clear and getNowParts will overwrite it as intended.

In `@src/sensors/AOSongDHT.cpp`:
- Around line 15-23: Replace the magic literal 5 passed to
setAllowedMeasurementRetries in the AOSongDHT constructor with a named constant
(e.g., DHT_DEFAULT_RETRY_COUNT or kDefaultMeasurementRetries) declared near the
class or file scope so the retry policy is clear and easily tunable; update the
AOSongDHT constructor call to use that constant and add the constant definition
in the header or top of the source file adjacent to other DHT_* constants.

In `@src/sensors/KellerParent.cpp`:
- Around line 124-126: The final aggregated success check redundantly
re-validates waterPressureBar and waterTemperatureC; update the expression that
assigns to success (the line using success &= ...) to only validate waterDepthM
(e.g., ensure !isnan(waterDepthM) && waterDepthM != -9999) since
waterPressureBar and waterTemperatureC were already validated earlier, keeping
the same pattern/semantics used elsewhere in KellerParent.cpp.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 155: Replace explicit boolean comparisons with idiomatic expressions:
change occurrences of "success == false" to "!success" in the loop condition
inside MaxBotixSonar::... (the while using variable success and rangeAttempts)
and similarly replace "_convertCm == true" with "_convertCm" (and any other
occurrences like the one around line 189) to use direct boolean variables;
update only the conditional expressions — do not alter the surrounding logic or
variable names.

In `@src/sensors/ProcessorAnalog.cpp`:
- Around line 52-66: The code does two priming analogRead() calls on _dataPin
before the actual measurement; remove one of those redundant priming reads so
there is only a single discard read prior to capturing rawAnalog. Specifically,
in ProcessorAnalog.cpp replace the two consecutive analogRead(_dataPin) priming
calls with a single analogRead(_dataPin) priming call that precedes the float
rawAnalog = analogRead(_dataPin) measurement, leaving the conversion to
sensorValue_analog and the subsequent
verifyAndAddMeasurementResult(PROCESSOR_ANALOG_VAR_NUM, sensorValue_analog) and
bumpMeasurementAttemptCount(true) logic unchanged.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (12)
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md (1)

14-15: Fix typo: “place holders” → “placeholders”.

Minor readability issue in the documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md`
around lines 14 - 15, Change the misspelled phrase "place holders" to the
correct single-word "placeholders" in the README example text; locate the
sentence containing "`12345678-abcd-1234-ef00-1234567890ab` place holders" and
replace it so it reads "...replace the `12345678-abcd-1234-ef00-1234567890ab`
placeholders in this example." Ensure only the spelling is corrected and
surrounding punctuation and emphasis remain unchanged.
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md (2)

26-26: Use “UUIDs” (plural) in headings and ToC.

Both headings refer to multiple identifiers, so “UUIDs” reads correctly and matches the plural usage elsewhere.

Also applies to: 95-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md` at line
26, Update the TOC and heading text that currently read "Set the universally
unique identifiers (UUID) for each variable" to use the plural form "Set the
universally unique identifiers (UUIDs) for each variable" (and likewise update
any other occurrence such as the instance around lines referenced 95-95); search
for the exact string "Set the universally unique identifiers (UUID) for each
variable" and replace it with "Set the universally unique identifiers (UUIDs)
for each variable" so headings and links consistently use the plural "UUIDs".

56-57: Consider keeping the explanatory sentence inside the list item.

Line 57 renders as a separate paragraph, which may detach it from the bullet. If you want it under the same bullet, indent or add a list marker.

Example formatting tweak
-- Change the text `YourLoggerID` in this section of code to your loggerID.
-For most DRWI installations, the logger ID was assigned by the Stroud Water Research Center before the station was built.
+- Change the text `YourLoggerID` in this section of code to your loggerID.
+  For most DRWI installations, the logger ID was assigned by the Stroud Water Research Center before the station was built.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md` around
lines 56 - 57, The explanatory sentence after the list item was rendered as a
separate paragraph; edit the README so the sentence stays part of the same
bullet containing `YourLoggerID` by either indenting that line under the bullet
(prefix with two spaces) or adding a list marker to it, ensuring the explanatory
text remains within the same list item that mentions `YourLoggerID`.
extras/AWS_IoT_SetCertificates/ReadMe.md (1)

177-178: Prefer “username” as one word.
Minor style tweak for consistency with common usage.

✏️ Proposed edit
-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.
+If your SIM card requires a username 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.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md` around lines 177 - 178, Replace the
two-word "user name" with the single word "username" in the README sentence that
references GPRS credentials (the symbols gprsUser and gprsPass) so it reads
"username and password"; also ensure the sentence referencing SerialAT and the
`#define` SerialMon Serial line remains unchanged and continues to confirm wiring
and port selection for SerialAT.
src/sensors/MaxBotixSonar.cpp (1)

189-189: Minor: Simplify boolean comparison.

_convertCm == true can be simplified to just _convertCm for cleaner, more idiomatic C++.

♻️ Suggested fix
-            if (_convertCm == true) { result *= 10; }
+            if (_convertCm) { result *= 10; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.cpp` at line 189, The if-statement in
MaxBotixSonar.cpp uses an unnecessary boolean comparison (`_convertCm == true`);
simplify it by changing the condition to just `_convertCm` where the conversion
occurs (the block that multiplies `result` by 10), e.g., update the conditional
in the method that adjusts `result` to use `_convertCm` directly instead of
comparing to true.
src/sensors/CampbellOBS3.cpp (1)

100-113: Consider adding debug logging for out-of-range voltage readings.

When adcVoltage falls outside the valid range, the function silently returns failure without any debug output. Adding a debug message for this case would help with field troubleshooting when sensors produce unexpected readings.

🔧 Optional: Add debug log for out-of-range case
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage out of range:"), adcVoltage);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` around lines 100 - 113, The function currently
returns failure when adcVoltage is outside the allowed range without any
logging; add a debug log (using MS_DBG or the existing logging macro) in the
out-of-range path that reports the adcVoltage value and that it was rejected by
the range check before calling bumpMeasurementAttemptCount(success); place this
log where the code currently skips the calibration branch (refer to adcVoltage,
calibResult, _x2_coeff_A/_x1_coeff_B/_x0_coeff_C, verifyAndAddMeasurementResult,
OBS3_TURB_VAR_NUM) so you still return via bumpMeasurementAttemptCount(success)
but with a clear message for field troubleshooting.
src/publishers/ThingSpeakPublisher.cpp (1)

124-125: Consider using int16_t for the return variable to match the function signature.

The function returns int16_t but uses a bool retVal internally (line 125), which is implicitly converted on return (line 205). While this works correctly, using int16_t throughout would improve code expressiveness and consistency with the function signature.

♻️ Suggested refactor for type consistency
 int16_t ThingSpeakPublisher::publishData(Client* outClient, bool) {
-    bool retVal = false;
+    int16_t retVal = 0;

Then update line 197 accordingly:

         delay(1000);
-        retVal = false;
+        retVal = 0;

Also applies to: 205-205

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/ThingSpeakPublisher.cpp` around lines 124 - 125, The function
ThingSpeakPublisher::publishData currently declares a local bool retVal while
the function returns int16_t; change retVal's type to int16_t and use numeric
status values (e.g., 0 for failure, 1 for success or the project’s defined
status codes) instead of true/false, then update all assignments and return
sites that set or return retVal so they assign the appropriate int16_t values;
ensure any conditional logic that previously relied on boolean semantics
explicitly compares or converts to the int16_t status as needed.
src/sensors/GroPointParent.cpp (1)

204-207: Consider explicit handling for unimplemented models.

The default case logs a debug message but silently leaves success and successT as false. For clarity and future maintainability, consider adding an explicit comment or setting the flags to false explicitly, so future readers understand this is intentional rather than an oversight.

♻️ Optional: Explicit default handling
         default: {
             // Get Values
             MS_DBG(F("Other GroPoint models not yet implemented."));
+            success  = false;
+            successT = false;
+            break;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.cpp` around lines 204 - 207, The switch default
branch in GroPointParent.cpp currently only logs MS_DBG and leaves the variables
success and successT unchanged; explicitly set success = false and successT =
false (or add a clear comment like "intentionally unimplemented model — mark as
failure") inside the default block so readers and later code see the intended
behavior; reference the default case in the same switch and the variables
success and successT when making this change.
src/sensors/TurnerCyclops.cpp (1)

105-115: Optional: log when ADC voltage is out of range.

This makes field diagnostics easier without changing behavior.

💡 Suggested debug log
 if (adcVoltage < 3.6 && adcVoltage > -0.3) {
     // Apply the unique calibration curve for the given sensor
     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);
     success = true;
+} else {
+    MS_DBG(F("  adcVoltage out of range:"), adcVoltage);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 105 - 115, Add a debug log when
adcVoltage falls outside the allowed range so field diagnostics record the
out-of-range event without changing behavior: in the if/else that checks "if
(adcVoltage < 3.6 && adcVoltage > -0.3)" keep the existing calibration code that
computes calibResult using _conc_std, _volt_std and _volt_blank and calls
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage), and add an
else branch that logs a clear debug message (including the adcVoltage value and
an identifier like CYCLOPS_VAR_NUM or "Cyclops ADC out of range") so operators
can see when readings were ignored.
docs/For-Developers/Developer-Setup.md (1)

93-135: Use placeholder paths in lib_deps examples to keep docs portable.

The hard-coded C:\Users\... paths look user-specific and may confuse contributors on other machines. Consider replacing them with a placeholder (e.g., <path-to-repo>), and note that users should update to their local path.

💡 Example pattern
-    Adafruit BusIO=symlink://C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\Adafruit BusIO
+    Adafruit BusIO=symlink://<path-to-repo>/lib/Adafruit BusIO
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/For-Developers/Developer-Setup.md` around lines 93 - 135, The lib_deps
block contains hard-coded user-specific Windows paths (entries like "Adafruit
BusIO=symlink://C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\Adafruit
BusIO") which makes the example non-portable; update the lib_deps examples to
use a clear placeholder (e.g., "<path-to-repo>" or "{repo-root}") instead of the
absolute C:\Users\... paths for each dependency line, and add a brief note near
the lib_deps section instructing contributors to replace the placeholder with
their local repository path before using the symlink entries.
src/SensorBase.h (1)

430-442: Active‑low/high power‑pin TODO noted.
If you want, I can draft an inversion flag/helper design or open a follow‑up issue for the active‑low/high power‑pin support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.h` around lines 430 - 442, The todo notes that power pins may
be active-LOW or active-HIGH; add an explicit inversion flag/property (e.g., a
bool like _powerActiveLow or an enum powerActiveLevel) to SensorBase and update
powerUp() and the corresponding powerDown()/powerOff logic to drive `#_powerPin`
to the correct active/inactive level based on that flag, while still clearing
`#_millisPowerOn` and updating `#_sensorStatus`; ensure constructors/defaults set
the flag (default to active-HIGH for backwards compatibility), update any
power-related docs/comments, and expose a setter or constructor parameter so
derived classes can specify active-low hardware.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

34-96: Consider reducing constructor duplication.

All constructors repeat the same initialization sequence:

setHost("monitormywatershed.org");
setPath("/api/data-stream/");
setPort(80);

While this works correctly, a private helper method could reduce duplication. However, this is a minor style concern and the current approach is explicit and clear.

♻️ Optional: Extract common initialization
private:
    void initDefaults() {
        setHost("monitormywatershed.org");
        setPath("/api/data-stream/");
        setPort(80);
    }

Then call initDefaults() in each constructor body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 34 - 96,
Multiple MonitorMyWatershedPublisher constructors duplicate the same
host/path/port setup; add a private helper like initDefaults() that calls
setHost("monitormywatershed.org"), setPath("/api/data-stream/"), setPort(80) and
invoke initDefaults() from each MonitorMyWatershedPublisher constructor body
(e.g. the overloads shown:
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher(), the Logger&
variants, and the Client* variants) to centralize initialization and remove the
repeated calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/EnviroDIY_Monitoring_Kit/ReadMe.md`:
- Around line 90-93: The phrase "Replace the `Your..`" is ambiguous; update the
README text to use concrete placeholder examples and clearer wording—e.g.,
change it to "Replace the placeholders `YourSSID` and `YourPassword` (or the APN
`YourAPN`) with the SSID/password or APN assigned to your network." Keep the
note about APN assignment and the Hologram example (`"hologram"`) intact so
readers know to use that APN when applicable.

In `@examples/ReadMe.md`:
- Line 154: Change the typo in the README link text: replace "LTEG" with "LTE"
in the markdown link whose label currently reads "[The LTEG DRWI Citizen Science
example on GitHub]" so it matches the section heading and description; update
the link label to "[The LTE DRWI Citizen Science example on GitHub]" (leave the
URL unchanged).

In `@setupGitFilters.bat`:
- Line 1: The batch script uses LF-only line endings which can break cmd.exe
parsing; convert the file setupGitFilters.bat to use CRLF line endings (ensure
your editor or git core.autocrlf setting writes CRLF) or add a .gitattributes
rule to enforce CRLF for *.bat files; make sure the single line containing
"@echo off" is saved with CRLF so Windows cmd executes the script correctly.
- Around line 9-15: The git filter commands currently force PowerShell with
"-ExecutionPolicy Bypass" (in the filter lines for smudgePasswords and
disableDebug invoking filters/cleanPasswords.ps1, filters/smudgePasswords.ps1,
filters/cleanDebugConfig.ps1, filters/smudgeDebugConfig.ps1); change these git
config values to avoid lowering the user's execution policy — either remove the
-ExecutionPolicy flag entirely or replace it with a safer default such as
"-ExecutionPolicy RemoteSigned", or implement a wrapper that checks/asks the
user before using Bypass; update the four git config entries
(filter.smudgePasswords.clean, filter.smudgePasswords.smudge,
filter.disableDebug.clean, filter.disableDebug.smudge) accordingly.

In `@setupGitFilters.ps1`:
- Around line 7-15: The git filter commands currently force PowerShell with
-ExecutionPolicy Bypass (see git config entries for
filter.smudgePasswords.clean, filter.smudgePasswords.smudge,
filter.disableDebug.clean, filter.disableDebug.smudge); change these to avoid
weakening host security by either removing the -ExecutionPolicy flag entirely or
replacing it with a safer default such as -ExecutionPolicy RemoteSigned, and
update any README or setup instructions to document that users can opt in to a
different policy if they choose; ensure the four git config invocations
reference the adjusted command string so filters run without using Bypass by
default.

In `@src/SensorBase.h`:
- Around line 527-532: Fix the typo in the clearValues docstring: change "setal"
to "set all" in the comment that documents clearValues (the block describing
clearing values array, numberGoodMeasurementsMade, _measurementAttemptsCompleted
and _retryAttemptsMade) in SensorBase.h so the sentence reads "set all values in
numberGoodMeasurementsMade to 0".

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 172-179: When _voltageDividerFactor is <= 0 the code currently
logs a debug message but continues using unscaled adcVoltage; change this to
bail out early by calling bumpMeasurementAttemptCount(false) and returning from
the measurement function instead of proceeding with calibration. Locate the
block that checks _voltageDividerFactor in the measurement routine (the one that
later computes adcVoltage and turbidity) and replace the else branch so it
invokes bumpMeasurementAttemptCount(false) and returns immediately, ensuring no
further calibration uses the invalid factor.

In `@src/VariableArray.cpp`:
- Around line 405-450: In completeUpdate(), handle the case when wake==false so
sensors that are already powered don't stall: after the wake-attempt block,
detect sensors where wake==false (the incoming parameter),
sensorList[i]->getStatusBit(Sensor::WAKE_ATTEMPTED)==0 and
sensorList[i]->getStatusBit(Sensor::WAKE_SUCCESSFUL)==0 but
sensorList[i]->isWarmedUp(...) (or otherwise ready); for those, mark them as
implicitly woken (set the WAKE_SUCCESSFUL status bit or otherwise call the
sensor API to record a successful wake) so the subsequent MEASUREMENT_ATTEMPTED
/ startSingleMeasurement() logic can run and nSensorsCompleted can advance.
Ensure you use the existing status-bit API on Sensor (e.g., setStatusBit or
equivalent) and make the change in completeUpdate() so
updateAllSensors()/LoggerBase test mode proceeds.

---

Duplicate comments:
In @.gitattributes:
- Around line 40-42: The .gitattributes references two custom filters
(smudgePasswords and disableDebug) but no corresponding filter driver
definitions exist; add filter driver entries for filter.smudgePasswords and
filter.disableDebug in the repository Git config (or instruct contributors to
add to their global gitconfig) with the appropriate clean/smudge/required
settings and point them at the scripts/commands that implement the behavior (or
remove the attribute lines if the filters are not used). Ensure the config keys
match the attribute names exactly (filter.smudgePasswords.*,
filter.disableDebug.*) so Git can locate the drivers during checkout/commit.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The code calls a nonstandard method forceModemBaud on
modem.gsmModem; first search the repo for forceModemBaud to confirm whether the
EnviroDIY TinyGSM fork defines it, and if it is not present, remove that call
and replace it with a supported TinyGSM approach such as calling
TinyGsmAutoBaud(modemSerial) for initial auto-baud or
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) after AT sync; keep
references to modem.gsmModem, modemSerial and modemBaud, and add any necessary
error handling/logging around the replacement call.

In `@examples/example_dependencies.json`:
- Line 29: The pinned Adafruit NeoPixel version "~1.15.4" may not exist
upstream; verify the tag exists in the Adafruit_NeoPixel repo or Arduino Library
Manager, and if it does not, update the manifest entry that sets the NeoPixel
dependency version (the string "~1.15.4") to a valid existing tag/semver (e.g.,
match an existing "1.15.x" tag or use the closest available release), or relax
the constraint to a valid range; ensure you replace only the version token for
the NeoPixel dependency and commit the corrected manifest.

In `@examples/examples.dox`:
- Around line 69-70: The `@ref` link uses the old anchor name `examples_outdated`
while the page was renamed to `page_examples_outdated`; update the link target
in the examples file so the reference matches the page anchor. Locate the lines
showing `@page page_examples_outdated` and the link `[Examples for Outdated
Hardware](`@ref` examples_outdated)` and change the `@ref` target to
`page_examples_outdated` so they match.

In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: The README text has the pluralization corrected to
"UUIDs" (see the phrase "View Token UUID List") so no content change is
required; however remove the stray review metadata tags like
"[duplicate_comment]" and "[approve_code_changes]" from the file if present so
the README contains only user-facing text and the intended emphasis ("very,
very, very careful") remains intact.

In `@src/modems/SIMComSIM7000.h`:
- Around line 76-78: The include and type references for TinyGSM's SIM7000 SSL
variant may not exist in the EnviroDIY fork; verify whether
TinyGsmClientSIM7000SSL.h and the type TinyGsmSim7000SSL exist and, if not,
switch to the EnviroDIY-supported declarations (e.g., rely on
TINY_GSM_MODEM_SIM7000SSL and the standard TinyGSM client types) in
SIMComSIM7000.h and related code paths (references around lines 76 and 224-228).
If the fork uses a different header/type, replace the include and type names
with the fork's equivalents or add preprocessor guards to include the correct
header and typedef the correct client class so compilation works against the
EnviroDIY TinyGSM fork.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 349-355: The current logic sets minTime to 0 when _alwaysAutoFocus
is false, which lets elapsed_since_wake_up > minTime pass immediately; change
the minTime calculation so when _alwaysAutoFocus is false it uses
_stabilizationTime_ms (not 0L). In other words update the minTime expression
around _alwaysAutoFocus/HYDROCAM_AUTOFOCUS_TIME_MS to set minTime =
(_alwaysAutoFocus ? HYDROCAM_AUTOFOCUS_TIME_MS + _stabilizationTime_ms :
_stabilizationTime_ms) so stability detection uses the base stabilization delay;
leave maxTime logic as-is.

In `@src/sensors/GroPointParent.cpp`:
- Around line 210-239: The function currently preserves partial readings by
calling verifyAndAddMeasurementResult for moisture when success is true and
temperature when successT is true, but still returns
bumpMeasurementAttemptCount((success && successT)), forcing retries on partial
success; if the intended behavior is that any successful reading (moisture or
temperature) should mark the measurement attempt as successful, change the
return expression in the function that calls bumpMeasurementAttemptCount to use
(success || successT) instead of (success && successT), keeping the existing
verifyAndAddMeasurementResult calls intact.

In `@src/sensors/KnownProcessors.h`:
- Around line 259-288: Fallback defaults and compile-time warnings for
LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER are
correctly added; keep these safe defaults and associated `#pragma` message blocks
as-is, ensuring the macros LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, and
BATTERY_MULTIPLIER remain defined with the provided defaults so consumers
without processor-specific definitions get the warnings and safe values at
compile time.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop in MaxBotixSonar (the rangeAttempts loop
around _stream->parseInt, _triggerPin triggering and handling
_convertCm/_maxRange) uses a hard-coded 25 attempts which can block ~6.25s;
change it to use a time-budget instead: record a start timestamp (millis())
before the loop and replace the rangeAttempts < 25 check with an elapsed-time
check (stop when elapsed >= HRXL_MEASUREMENT_TIME_MS or a passed-in remaining
measurement budget), still incrementing a attempts counter for diagnostics;
ensure each iteration respects the stream timeout and breaks promptly when the
time budget is exhausted, leaving result set to the failure sentinel (-9999) if
no valid reading found.

In `@src/sensors/MaximDS3231.cpp`:
- Around line 57-69: The current MaximDS3231::addSingleMeasurementResult calls
rtc.getTemperature() and unconditionally returns
bumpMeasurementAttemptCount(true), which will suppress retries even if
rtc.getTemperature() produced NaN; change the flow to check tempVal (from
rtc.getTemperature()) for NaN after reading (remember
verifyAndAddMeasurementResult already ignores NaN), and only call
bumpMeasurementAttemptCount(true) when tempVal is a valid number — otherwise
call bumpMeasurementAttemptCount(false) (or return failure) so the retry
semantics are preserved.

In `@src/sensors/ProcessorStats.cpp`:
- Around line 33-42: Before calling strcmp check that _version is non-null and
handle unknown strings by logging/indicating the unsupported version rather than
silently setting _batteryMultiplier to -1; update the block that compares
_version ("v0.1"/"v0.2") to first guard _version != nullptr, then keep the
existing assignments to _batteryMultiplier for "v0.1" and "v0.2" and add a
diagnostic (e.g., a debug/log call or assert) in the else branch when setting
_batteryMultiplier = -1 to make the unsupported-version state explicit;
reference the _version and _batteryMultiplier symbols in ProcessorStats.cpp when
making this change.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 105-121: requestFrom's return value (number of bytes received)
must be checked, not just truthiness; change the call to capture the returned
byte count from _i2c->requestFrom(static_cast<uint8_t>(_i2cAddressHex),
static_cast<uint8_t>(4)) and only proceed when the count equals an expected
value (1 or 4) before parsing SerialBuffer; also limit the read loop to that
returned count (and validate post-read that byte_in == returned_count), and on
mismatch log the issue and call bumpMeasurementAttemptCount(false) instead of
continuing to parse partial data. Reference symbols: requestFrom,
_i2cAddressHex, SerialBuffer, byte_in, bumpMeasurementAttemptCount.

In `@src/sensors/SensirionSHT4x.cpp`:
- Around line 107-120: The current code unconditionally assigns temp_val and
humid_val from temp_event and humidity_event before checking ret_val; change the
flow so that sht4x_internal.getEvent(&humidity_event, &temp_event) is called,
then check ret_val and isnan() before updating temp_val and humid_val—only
assign to temp_val and humid_val when ret_val is true and the values are valid
(not NaN); leave the sentinel values (e.g., -9999) intact when getEvent fails
and keep MS_DBG calls using those preserved sentinels for clearer diagnostic
output.

In `@src/sensors/TIINA219.cpp`:
- Around line 40-63: Sensor::setup() return value is ignored in TIINA219::setup
leading to inconsistent status bits; change TIINA219::setup to capture the
boolean result of Sensor::setup() and combine it (logical AND) with
ina219_phy.begin(_i2c) when deciding overall success, and only set/clear status
bits or power-down when the combined success is false; ensure you still perform
the power-up/warm-up/power-down sequence (checkPowerOn, powerUp, waitForWarmUp,
powerDown) but use the combined result to setStatusBit(ERROR_OCCURRED) and
clearStatusBit(SETUP_SUCCESSFUL) as appropriate.

In `@src/VariableArray.cpp`:
- Around line 372-446: The debug-only local variables String sName and String
cycCount are declared inside `#if` defined(MS_VARIABLEARRAY_DEBUG) ||
defined(MS_VARIABLEARRAY_DEBUG_DEEP) but used later in MS_DBG(...) calls outside
that guard, causing compile errors; fix by moving the declarations of sName and
cycCount (or declaring minimal fallbacks) so they always exist before any MS_DBG
usage (e.g., declare String sName; String cycCount; before the `#if` block and
assign inside the guarded block), or alternatively wrap the MS_DBG(...) calls
that reference sName/cycCount with the same debug guard; update references in
the function that uses sensorList[i], MS_DBG, and the Sensor methods
(getSensorNameAndLocation, getNumberCompleteMeasurementsAttempts,
getNumberRetryAttemptsMade) accordingly.

---

Nitpick comments:
In `@docs/For-Developers/Developer-Setup.md`:
- Around line 93-135: The lib_deps block contains hard-coded user-specific
Windows paths (entries like "Adafruit
BusIO=symlink://C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\Adafruit
BusIO") which makes the example non-portable; update the lib_deps examples to
use a clear placeholder (e.g., "<path-to-repo>" or "{repo-root}") instead of the
absolute C:\Users\... paths for each dependency line, and add a brief note near
the lib_deps section instructing contributors to replace the placeholder with
their local repository path before using the symlink entries.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md`:
- Line 26: Update the TOC and heading text that currently read "Set the
universally unique identifiers (UUID) for each variable" to use the plural form
"Set the universally unique identifiers (UUIDs) for each variable" (and likewise
update any other occurrence such as the instance around lines referenced 95-95);
search for the exact string "Set the universally unique identifiers (UUID) for
each variable" and replace it with "Set the universally unique identifiers
(UUIDs) for each variable" so headings and links consistently use the plural
"UUIDs".
- Around line 56-57: The explanatory sentence after the list item was rendered
as a separate paragraph; edit the README so the sentence stays part of the same
bullet containing `YourLoggerID` by either indenting that line under the bullet
(prefix with two spaces) or adding a list marker to it, ensuring the explanatory
text remains within the same list item that mentions `YourLoggerID`.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_NoCellular/ReadMe.md`:
- Around line 14-15: Change the misspelled phrase "place holders" to the correct
single-word "placeholders" in the README example text; locate the sentence
containing "`12345678-abcd-1234-ef00-1234567890ab` place holders" and replace it
so it reads "...replace the `12345678-abcd-1234-ef00-1234567890ab` placeholders
in this example." Ensure only the spelling is corrected and surrounding
punctuation and emphasis remain unchanged.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 177-178: Replace the two-word "user name" with the single word
"username" in the README sentence that references GPRS credentials (the symbols
gprsUser and gprsPass) so it reads "username and password"; also ensure the
sentence referencing SerialAT and the `#define` SerialMon Serial line remains
unchanged and continues to confirm wiring and port selection for SerialAT.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 34-96: Multiple MonitorMyWatershedPublisher constructors duplicate
the same host/path/port setup; add a private helper like initDefaults() that
calls setHost("monitormywatershed.org"), setPath("/api/data-stream/"),
setPort(80) and invoke initDefaults() from each MonitorMyWatershedPublisher
constructor body (e.g. the overloads shown:
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher(), the Logger&
variants, and the Client* variants) to centralize initialization and remove the
repeated calls.

In `@src/publishers/ThingSpeakPublisher.cpp`:
- Around line 124-125: The function ThingSpeakPublisher::publishData currently
declares a local bool retVal while the function returns int16_t; change retVal's
type to int16_t and use numeric status values (e.g., 0 for failure, 1 for
success or the project’s defined status codes) instead of true/false, then
update all assignments and return sites that set or return retVal so they assign
the appropriate int16_t values; ensure any conditional logic that previously
relied on boolean semantics explicitly compares or converts to the int16_t
status as needed.

In `@src/SensorBase.h`:
- Around line 430-442: The todo notes that power pins may be active-LOW or
active-HIGH; add an explicit inversion flag/property (e.g., a bool like
_powerActiveLow or an enum powerActiveLevel) to SensorBase and update powerUp()
and the corresponding powerDown()/powerOff logic to drive `#_powerPin` to the
correct active/inactive level based on that flag, while still clearing
`#_millisPowerOn` and updating `#_sensorStatus`; ensure constructors/defaults set
the flag (default to active-HIGH for backwards compatibility), update any
power-related docs/comments, and expose a setter or constructor parameter so
derived classes can specify active-low hardware.

In `@src/sensors/CampbellOBS3.cpp`:
- Around line 100-113: The function currently returns failure when adcVoltage is
outside the allowed range without any logging; add a debug log (using MS_DBG or
the existing logging macro) in the out-of-range path that reports the adcVoltage
value and that it was rejected by the range check before calling
bumpMeasurementAttemptCount(success); place this log where the code currently
skips the calibration branch (refer to adcVoltage, calibResult,
_x2_coeff_A/_x1_coeff_B/_x0_coeff_C, verifyAndAddMeasurementResult,
OBS3_TURB_VAR_NUM) so you still return via bumpMeasurementAttemptCount(success)
but with a clear message for field troubleshooting.

In `@src/sensors/GroPointParent.cpp`:
- Around line 204-207: The switch default branch in GroPointParent.cpp currently
only logs MS_DBG and leaves the variables success and successT unchanged;
explicitly set success = false and successT = false (or add a clear comment like
"intentionally unimplemented model — mark as failure") inside the default block
so readers and later code see the intended behavior; reference the default case
in the same switch and the variables success and successT when making this
change.

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 189: The if-statement in MaxBotixSonar.cpp uses an unnecessary boolean
comparison (`_convertCm == true`); simplify it by changing the condition to just
`_convertCm` where the conversion occurs (the block that multiplies `result` by
10), e.g., update the conditional in the method that adjusts `result` to use
`_convertCm` directly instead of comparing to true.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 105-115: Add a debug log when adcVoltage falls outside the allowed
range so field diagnostics record the out-of-range event without changing
behavior: in the if/else that checks "if (adcVoltage < 3.6 && adcVoltage >
-0.3)" keep the existing calibration code that computes calibResult using
_conc_std, _volt_std and _volt_blank and calls
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage), and add an
else branch that logs a clear debug message (including the adcVoltage value and
an identifier like CYCLOPS_VAR_NUM or "Cyclops ADC out of range") so operators
can see when readings were ignored.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/SensorBase.cpp (1)

478-526: ⚠️ Potential issue | 🟠 Major

Use a while loop to properly exercise retry settings in Sensor::update().

The fixed for loop treats each iteration as a separate measurement attempt and bypasses the retry mechanism. Since clearValues() resets _measurementAttemptsCompleted and _retryAttemptsMade, retries should take effect within each measurement attempt. A while loop checking _measurementAttemptsCompleted < _measurementsToAverage ensures retries execute before advancing to the next measurement, matching the behavior in VariableArray::completeUpdate().

🛠️ Suggested fix (retry-aware loop)
-    // loop through as many measurements as requested
-    for (uint8_t j = 0; j < _measurementsToAverage; j++) {
+    // loop until we have the requested number of completed measurements
+    while (_measurementAttemptsCompleted < _measurementsToAverage) {
         // start a measurement
         ret_val &= startSingleMeasurement();
         // wait for the measurement to finish
         waitForMeasurementCompletion();
         // get the measurement result
         ret_val &= addSingleMeasurementResult();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SensorBase.cpp` around lines 478 - 526, The current for-loop in
Sensor::update() iterates a fixed number of times and advances measurements
without allowing the per-measurement retry logic to run; replace the for
(uint8_t j = 0; j < _measurementsToAverage; j++) loop with a retry-aware while
loop that continues while _measurementAttemptsCompleted <
_measurementsToAverage, invoking startSingleMeasurement(),
waitForMeasurementCompletion(), and addSingleMeasurementResult() inside that
loop so retries (which update _measurementAttemptsCompleted and
_retryAttemptsMade after clearValues()) can occur before moving to the next
measurement; keep clearValues(), clearMeasurementStatus(), waitForStability(),
and the existing wake/power checks unchanged.
src/VariableArray.cpp (1)

284-415: ⚠️ Potential issue | 🟠 Major

Verify wake=false semantics to prevent silent measurement skipping.

The concern is valid. When updateAllSensors() calls completeUpdate(false, false, false, false), the logic at lines 399–403 will skip sensors if WAKE_SUCCESSFUL is not already set. Since checkPowerOn() only checks and manages power state (clearing WAKE_SUCCESSFUL if power is lost, but never setting it), sensors without a prior successful wake will be silently marked complete and skipped, resulting in no measurements taken. The function is deprecated, but it remains callable and lacks documentation of this requirement. Either document that WAKE_SUCCESSFUL must be pre-set before calling updateAllSensors(), or adjust the logic to treat already-powered sensors as implicitly meeting the wake requirement when wake=false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 284 - 415, The current logic in
completeUpdate (called by updateAllSensors) marks sensors complete when
wake==false if WAKE_SUCCESSFUL is not set, causing silent skips; update the
wake-check condition around Sensor::WAKE_ATTEMPTED / Sensor::WAKE_SUCCESSFUL to
treat an already powered sensor as implicitly awake when wake==false by also
checking the sensor power-status bit (e.g., Sensor::POWER_ON or an isPowered()
method set by checkPowerOn()); specifically, in the block that sets
_measurementAttemptsCompleted, change the condition to allow continuation if
wake==false and the sensor reports powered, otherwise keep existing behavior;
alternatively add a clear comment in updateAllSensors/completeUpdate documenting
that WAKE_SUCCESSFUL must be pre-set if you don’t want implicit power-as-awake
behavior.
🧹 Nitpick comments (7)
src/sensors/GroPointParent.cpp (1)

204-207: Consider setting error bit for unimplemented models.

The default case correctly returns failure, but for consistency with other error paths (e.g., wake() at line 107), consider setting the ERROR_OCCURRED status bit to aid debugging.

💡 Optional: Add error status for unsupported models
         default: {
             // Get Values
             MS_DBG(F("Other GroPoint models not yet implemented."));
+            setStatusBit(ERROR_OCCURRED);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.cpp` around lines 204 - 207, In the default branch
of the model switch in GroPointParent.cpp (the block that currently logs "Other
GroPoint models not yet implemented."), set the ERROR_OCCURRED status bit the
same way other error paths do (see wake()), so the device status reflects the
failure consistently before returning/exit; keep the MS_DBG message but also
update the status via the same status-setting mechanism (e.g., the same
setStatus/status_ pattern used in wake()) to mirror other error handling.
src/sensors/KellerParent.cpp (1)

95-127: Simplify duplicate validity checks to improve readability.

Pressure/temperature validity is checked twice, and success &= ... re-evaluates already-validated fields. Consider computing validity flags once and deriving success from them.

♻️ Suggested cleanup
-    success = (!isnan(waterPressureBar) && waterPressureBar != -9999 &&
-               !isnan(waterTemperatureC) && waterTemperatureC != -9999);
+    const bool pressureValid =
+        (!isnan(waterPressureBar) && waterPressureBar != -9999);
+    const bool tempValid =
+        (!isnan(waterTemperatureC) && waterTemperatureC != -9999);
+    success = pressureValid && tempValid;
@@
-    success &= (!isnan(waterPressureBar) && waterPressureBar != -9999 &&
-                !isnan(waterTemperatureC) && waterTemperatureC != -9999 &&
-                !isnan(waterDepthM) && waterDepthM != -9999);
+    const bool depthValid = (!isnan(waterDepthM) && waterDepthM != -9999);
+    success = success && depthValid;

As per coding guidelines: “Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KellerParent.cpp` around lines 95 - 127, The code repeats
pressure/temperature validity checks — replace the duplicated logic by computing
validity flags once: after calling _ksensor.getValues(waterPressureBar,
waterTemperatureC) create bool pressureValid = (!isnan(waterPressureBar) &&
waterPressureBar != -9999) and bool tempValid = (!isnan(waterTemperatureC) &&
waterTemperatureC != -9999); set success = pressureValid && tempValid; only when
success is true call _ksensor.calcWaterDepthM(...) to compute waterDepthM and
compute waterPressure_mBar = 1000 * waterPressureBar, then compute depthValid =
(!isnan(waterDepthM) && waterDepthM != -9999) and finally set success = success
&& depthValid; remove the second block that re-evaluates pressure/temperature so
all validity checks are centralized around these flags.
src/modems/DigiXBee3GBypass.cpp (1)

110-110: Consider using decimal literal instead of octal.

The value 02 is an octal literal (leading zero), which happens to equal decimal 2. While functionally correct, this could confuse maintainers who might expect decimal. Consider using 2 for clarity.

✨ Suggested change
-        gsmModem.sendAT(GF("DO"), 02);
+        gsmModem.sendAT(GF("DO"), 2);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/DigiXBee3GBypass.cpp` at line 110, Replace the octal literal used
in the call to gsmModem.sendAT(GF("DO"), 02) with an explicit decimal literal to
avoid confusion; change the second argument from 02 to 2 in the gsmModem.sendAT
call so it clearly represents decimal two.
examples/menu_a_la_carte/ReadMe.md (1)

1267-1268: Consider tightening the wording to avoid repeated intensifiers.

This reads a bit cleaner without the triple “very.”

💬 Proposed edit
-If using this method, be very, very, very careful to make sure the order of your variables exactly matches the order of your UUIDs.
+If using this method, be extremely careful to make sure the order of your variables exactly matches the order of your UUIDs.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/menu_a_la_carte/ReadMe.md` around lines 1267 - 1268, The sentence
that warns about ordering currently uses a triple intensifier ("be very, very,
very careful"); replace that wording with a concise alternative to improve
readability — e.g., change "be very, very, very careful to make sure the order
of your variables exactly matches the order of your UUIDs." to "be careful to
ensure the order of your variables exactly matches the order of your UUIDs." or
"take care to ensure the order of your variables matches the order of your UUIDs
exactly." Update the sentence in the README where the warning appears to use one
of these concise phrasings.
src/VariableArray.cpp (1)

299-315: Acknowledge that VLAs are already used throughout the codebase; clarify portability implications.

Sensor* sensorList[_sensorCount]; is a Variable Length Array (VLA), a non-standard C++ extension supported by avr-g++. This pattern is already used elsewhere in the codebase (e.g., LoggerBase.cpp, SDI12Sensors.cpp). If portability across different compilers is a concern, consider a broader refactoring to replace all VLAs with fixed-capacity buffers or dynamic allocation. Otherwise, this usage is consistent with the project's existing approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 299 - 315, Replace the VLA declaration
Sensor* sensorList[_sensorCount]; with a portable allocation and cleanup to
avoid compiler-specific VLAs: allocate an array of Sensor* of size _sensorCount
(e.g., with new Sensor*[_sensorCount] or malloc) before the loop, use the same
logic that fills sensorList (referencing _variableCount, isLastVarFromSensor,
arrayOfVars[i]->parentSensor and addedSensors), validate addedSensors matches
_sensorCount, then free/delete the allocated sensorList before returning; make
sure all return paths free the allocation to avoid leaks.
src/publishers/MonitorMyWatershedPublisher.h (2)

309-317: TODO for initial transmission count.
If you want, I can help add a setter or constructor parameter to make this user-configurable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 309 - 317, The
hard-coded initial transmission count _initialTransmissionsRemaining in
MonitorMyWatershedPublisher is not user-configurable; add either a constructor
parameter to MonitorMyWatershedPublisher (e.g., uint8_t initialTransmissions =
5) or a public setter (setInitialTransmissionsRemaining(uint8_t)) that assigns
to _initialTransmissionsRemaining, update any constructors or factory calls to
accept/pass the new parameter, and document the new API and default value so
callers can override the initial one-minute transmission count.

201-208: Document registration-token lifetime (raw pointer storage).
setToken stores the pointer directly, so temporaries could dangle. Consider adding a warning like the host/path setters (or copy into owned storage).

✏️ Suggested doc clarification
     /**
      * `@brief` Set the site registration token
      *
+     * `@warning` The caller must guarantee that the passed C-string remains
+     * valid for the lifetime of this object or until another call to setToken().
+     * Do not pass temporary strings or stack-allocated C-strings that may be
+     * destroyed before all publish calls are complete.
+     *
      * `@param` registrationToken The registration token for the site on Monitor
      * My Watershed.
      */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 201 - 208, The
setToken(const char* registrationToken) declaration stores the raw pointer
directly which can dangle if the caller passes a temporary; update the
documentation for setToken in MonitorMyWatershedPublisher to explicitly state
the caller must ensure the C-string remains valid for the lifetime of the
publisher (mirroring the host/path setter warnings) or, alternatively, change
the implementation to take ownership by copying the token into owned storage
(e.g., a std::string member) and document that behavior; reference setToken and
the class MonitorMyWatershedPublisher in the doc comment so reviewers can find
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-116: The os.walk loop can still descend into excluded
directories because you call continue without modifying the mutable subdirs
list; update the loop in generate_job_matrix.py so that when folder_name is in
the excluded set (the set containing ".history", "logger_test", "archive",
"tests", menu_example_name) you prune subdirs (e.g., clear or filter the
_subdirs variable) before skipping so os.walk does not descend, keeping
non_menu_examples, examples_path, folder_name and filename logic intact.

In `@examples/baro_rho_correction/ReadMe.md`:
- Line 50: Update the README text that currently uses the concatenated token
"YourLoggerID" to the standard phrasing "Your logger ID" (or "logger ID") for
consistency; locate the instance where the sample instruction reads
`YourLoggerID` and replace it with "Your logger ID" so the wording matches the
rest of the document and avoids LanguageTool flags.

In `@examples/double_logger/ReadMe.md`:
- Around line 49-53: Normalize the phrasing to use "logger ID" consistently:
update the README text that currently says "loggerID" to read "logger ID" so it
matches the heading; retain the actual variable name LoggerID in the code
snippet (no code change needed) but update the surrounding comment/markdown to
consistently use "logger ID" in both heading and bullet text for readability.

In `@examples/simple_logging/ReadMe.md`:
- Line 43: The instruction text uses lowercase "loggerID" while the code
examples use `LoggerID`; update the README line that currently reads "Change the
text `YourLoggerID` in this section of code to your loggerID or serial number:"
to use the same casing as the code (e.g., "LoggerID") so the identifiers
match—specifically replace the lowercase "loggerID" with "LoggerID" (referencing
the `YourLoggerID` token and the `LoggerID` identifier in the examples).

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 23-33: The two EverlightALSPT19 constructors register a different
number of calculated variables; update the pin-based constructor to pass
ALSPT19_INC_CALC_VARIABLES into its Sensor(...) base-call so it uses the same
incCalcValues as the built-in constructor. Locate the pin-based
EverlightALSPT19::EverlightALSPT19(...) constructor and add
ALSPT19_INC_CALC_VARIABLES as the final argument in the Sensor(...) initializer
list to match the built-in constructor’s behavior.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 78-85: The comment above the validation block still mentions a
fixed 14‑bar ceiling while the code now uses _maxPressure to validate press;
update the comment to reference the dynamic _maxPressure (e.g., note that
pressure is validated against _maxPressure * 1000.0) and remove the hardcoded
"14bar (14,000 mbar)" text so the comment accurately reflects the check in the
if condition around temp, press and
verifyAndAddMeasurementResult(MS5803_TEMP_VAR_NUM, temp).

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 132-146: The negative-value handling for MCP3421 readings is wrong
because flipping bytes with ~res1/~res2/~res3 produces one's complement; fix by
interpreting the 18-bit sample as a signed value instead of bit-flipping:
assemble the 18-bit raw sample from res1/res2/res3 (use the same bit positions
used with bitRead), then sign-extend or subtract (1<<18) when the sign bit (D17
/ bitRead(res1,1)) is set to get the correct negative magnitude, and then
compute res using that signed integer (instead of doing ~resX and manual
per-byte math); alternatively, if you prefer the existing per-byte path,
implement proper two's-complement negation by flipping bits and then adding 1
across the full 18-bit value.

---

Outside diff comments:
In `@src/SensorBase.cpp`:
- Around line 478-526: The current for-loop in Sensor::update() iterates a fixed
number of times and advances measurements without allowing the per-measurement
retry logic to run; replace the for (uint8_t j = 0; j < _measurementsToAverage;
j++) loop with a retry-aware while loop that continues while
_measurementAttemptsCompleted < _measurementsToAverage, invoking
startSingleMeasurement(), waitForMeasurementCompletion(), and
addSingleMeasurementResult() inside that loop so retries (which update
_measurementAttemptsCompleted and _retryAttemptsMade after clearValues()) can
occur before moving to the next measurement; keep clearValues(),
clearMeasurementStatus(), waitForStability(), and the existing wake/power checks
unchanged.

In `@src/VariableArray.cpp`:
- Around line 284-415: The current logic in completeUpdate (called by
updateAllSensors) marks sensors complete when wake==false if WAKE_SUCCESSFUL is
not set, causing silent skips; update the wake-check condition around
Sensor::WAKE_ATTEMPTED / Sensor::WAKE_SUCCESSFUL to treat an already powered
sensor as implicitly awake when wake==false by also checking the sensor
power-status bit (e.g., Sensor::POWER_ON or an isPowered() method set by
checkPowerOn()); specifically, in the block that sets
_measurementAttemptsCompleted, change the condition to allow continuation if
wake==false and the sensor reports powered, otherwise keep existing behavior;
alternatively add a clear comment in updateAllSensors/completeUpdate documenting
that WAKE_SUCCESSFUL must be pre-set if you don’t want implicit power-as-awake
behavior.

---

Duplicate comments:
In @.gitattributes:
- Around line 41-43: The .gitattributes currently registers filters for patterns
"*.ino" (filter=smudgePasswords) and "ModSensorDebugConfig.h"
(filter=disableDebug) that were flagged as duplicate; remove the redundant or
duplicate filter entries and ensure only a single canonical mapping remains for
smudgePasswords and disableDebug. Locate the .gitattributes entries that
reference the symbols "*.ino", "smudgePasswords", "ModSensorDebugConfig.h", and
"disableDebug" and either delete the duplicate lines or merge them so each
filter driver is declared only once, and verify corresponding filter driver
definitions exist in the repo/git config.

In `@ChangeLog.md`:
- Around line 74-79: Condense the repetitive "Removed ..." bullets in
ChangeLog.md into one or two clearer sentences that list the items (e.g.,
_maxSamplesToAverage, countMaxToAverage(), unnecessary copy doc calls, redundant
powerUp/powerDown overrides, EnviroDIY portal refs, and defines in examples) and
then add a separate sentence explaining the One Definition Rule concern for
TinyGSM/MQTT defines; keep the same content but vary sentence starts and use
commas or a bulleted sub-list to improve readability.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 480-498: Replace the unsupported TinyGSM call forceModemBaud with
the supported setBaud/auto-baud workflow: remove calls to forceModemBaud
(occurrences using modem.gsmModem.forceModemBaud(modemSerial,
static_cast<uint32_t>(modemBaud))) and instead either call
modem.setBaud(modemBaud) or rely on the existing auto-baud sequence (e.g.,
ensure modem.modemWake() and gsmModem.testAT() are used to negotiate baud),
keeping references to modemSerial, modemBaud, modem.modemWake, and
gsmModem.testAT for context and confirming compilation.

In `@examples/example_dependencies.json`:
- Line 29: The comment about the NeoPixel dependency version "~1.15.4" is a
duplicate — remove this redundant review note and consolidate feedback so only
the original comment remains; if a change to the dependency is required (per the
original comment), make that single change to the version string (replace
"~1.15.4" with the agreed-upon version spec) instead of leaving duplicated
review messages.

In `@examples/examples.dox`:
- Around line 69-70: The `@ref` target in the markdown link is incorrect: change
the link target in the line containing [Examples for Outdated Hardware](`@ref`
examples_outdated) to use the renamed page anchor page_examples_outdated so it
matches the `@page` declaration (update `@ref` examples_outdated -> `@ref`
page_examples_outdated).

In `@examples/ReadMe.md`:
- Line 111: The sentence uses mixed tense; update the sentence so it reads in
consistent present tense and flows better—for example, change "This example is
intended to be used like an a la carte menu of all possible options where you
select only the portions of code pertinent to you and delete everything else."
to a present-tense phrasing such as "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." Make the edit in the
README line containing that sentence so wording is present-tense and consistent.

In `@src/LoggerModem.h`:
- Around line 991-992: Fix the duplicated/misworded doc comment in
LoggerModem.h: update the comment that currently reads "The digital pin number
of the pin on the mcu attached to the hard or panic reset pin of the modem."
(the doc block describing the modem hard/panic reset digital pin) to a clear
phrasing such as "The digital pin number on the MCU attached to the modem's hard
(panic) reset pin." Ensure you update the comment in the LoggerModem.h doc block
that describes the reset pin so the wording is corrected and unambiguous.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: Confirm whether the TinyGSM dependency exposes the header
TinyGsmClientSIM7000SSL.h and the class/symbol TinyGsmSim7000SSL; if they are
missing, change the include and type usage in SIMComSIM7000.h to use the
standard SIM7000 SSL integration (e.g., the existing TinyGsmClientSIM7000.h /
TinyGsmSim7000 symbol) or wrap the include/type selection with preprocessor
checks (e.g., `#if` defined(...) / `#else`) so compilation falls back to the
standard SIM7000 SSL variant; update references to TinyGsmClientSIM7000SSL.h and
TinyGsmSim7000SSL in the file to use the fallback symbol when the SSL-specific
symbols are not present.

In `@src/modems/SodaqUBeeU201.h`:
- Line 78: No changes required: the header include was intentionally switched to
the modem-specific TinyGsmClientUBLOX.h to match the custom TinyGsmUBLOX types
used elsewhere (e.g., in SodaqUBeeU201 class), so keep the include of
"TinyGsmClientUBLOX.h" and any usages of TinyGsmUBLOX/TinyGsmClientUBLOX as-is
to remain compatible with the EnviroDIY fork.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 189-261: The code sets _filename regardless of transfer success,
which can expose callers of getLastSavedImageName() to incomplete files; change
the logic in GeoluxHydroCam::addSingleMeasurementResult so _filename is assigned
only when bytes_transferred == image_size (the success condition computed from
bytes_transferred and image_size) and, on failure, optionally remove the partial
file via imgFile (or the SD API) before returning; adjust any related state
updates (verifyAndAddMeasurementResult and final return via
bumpMeasurementAttemptCount) to remain unchanged.

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-274: The GeoluxHydroCam constructor signature inserted
powerPin2 between powerPin and imageResolution which is a breaking change for
positional callers; restore backward compatibility by adding overloads that
preserve the old parameter order (keep a constructor overload matching
GeoluxHydroCam(Stream* stream, int8_t powerPin, Logger& baseLogger, const char*
imageResolution = "1600x1200", const char* filePrefix = nullptr, bool
alwaysAutoFocus = false) and/or an overload that accepts powerPin2 as the last
optional parameter), or alternatively move powerPin2 to the end of the parameter
list; update or add a brief comment above the constructors noting the change if
you intend a breaking API change and run the provided rg command to find and
update call sites if you change the order.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 103-121: The code currently treats any non-zero return from
_i2c->requestFrom(...) as success and may parse fewer than 1 or 4 bytes; change
it to capture the returned byte count (auto bytesRead = _i2c->requestFrom(...)),
require bytesRead to be exactly 1 or exactly 4 (otherwise call
bumpMeasurementAttemptCount(false) and return), and then read exactly bytesRead
bytes into SerialBuffer (using the existing loop but bound by bytesRead rather
than available()) before any version/tip parsing; keep using SerialBuffer,
byte_in, getSensorNameAndLocation(), and bumpMeasurementAttemptCount(false) to
preserve existing logging/error handling.

In `@src/VariableArray.cpp`:
- Around line 383-407: sName and cycCount are declared only inside the
MS_VARIABLEARRAY_DEBUG(/_DEEP) guard but are used in MS_DBG calls outside that
guard (e.g., the MS_DBG call after the wake-failure check), causing build
failures when VariableArray debug is disabled; fix by moving the declarations of
String sName and String cycCount (computed from
sensorList[i]->getSensorNameAndLocation(),
getNumberCompleteMeasurementsAttempts(), and getNumberRetryAttemptsMade()) above
the debug-guard so they exist unconditionally, or alternatively wrap the MS_DBG
calls that reference sName/cycCount with the same MS_VARIABLEARRAY_DEBUG(/_DEEP)
conditional so those variables are only referenced when declared.

---

Nitpick comments:
In `@examples/menu_a_la_carte/ReadMe.md`:
- Around line 1267-1268: The sentence that warns about ordering currently uses a
triple intensifier ("be very, very, very careful"); replace that wording with a
concise alternative to improve readability — e.g., change "be very, very, very
careful to make sure the order of your variables exactly matches the order of
your UUIDs." to "be careful to ensure the order of your variables exactly
matches the order of your UUIDs." or "take care to ensure the order of your
variables matches the order of your UUIDs exactly." Update the sentence in the
README where the warning appears to use one of these concise phrasings.

In `@src/modems/DigiXBee3GBypass.cpp`:
- Line 110: Replace the octal literal used in the call to
gsmModem.sendAT(GF("DO"), 02) with an explicit decimal literal to avoid
confusion; change the second argument from 02 to 2 in the gsmModem.sendAT call
so it clearly represents decimal two.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 309-317: The hard-coded initial transmission count
_initialTransmissionsRemaining in MonitorMyWatershedPublisher is not
user-configurable; add either a constructor parameter to
MonitorMyWatershedPublisher (e.g., uint8_t initialTransmissions = 5) or a public
setter (setInitialTransmissionsRemaining(uint8_t)) that assigns to
_initialTransmissionsRemaining, update any constructors or factory calls to
accept/pass the new parameter, and document the new API and default value so
callers can override the initial one-minute transmission count.
- Around line 201-208: The setToken(const char* registrationToken) declaration
stores the raw pointer directly which can dangle if the caller passes a
temporary; update the documentation for setToken in MonitorMyWatershedPublisher
to explicitly state the caller must ensure the C-string remains valid for the
lifetime of the publisher (mirroring the host/path setter warnings) or,
alternatively, change the implementation to take ownership by copying the token
into owned storage (e.g., a std::string member) and document that behavior;
reference setToken and the class MonitorMyWatershedPublisher in the doc comment
so reviewers can find the change.

In `@src/sensors/GroPointParent.cpp`:
- Around line 204-207: In the default branch of the model switch in
GroPointParent.cpp (the block that currently logs "Other GroPoint models not yet
implemented."), set the ERROR_OCCURRED status bit the same way other error paths
do (see wake()), so the device status reflects the failure consistently before
returning/exit; keep the MS_DBG message but also update the status via the same
status-setting mechanism (e.g., the same setStatus/status_ pattern used in
wake()) to mirror other error handling.

In `@src/sensors/KellerParent.cpp`:
- Around line 95-127: The code repeats pressure/temperature validity checks —
replace the duplicated logic by computing validity flags once: after calling
_ksensor.getValues(waterPressureBar, waterTemperatureC) create bool
pressureValid = (!isnan(waterPressureBar) && waterPressureBar != -9999) and bool
tempValid = (!isnan(waterTemperatureC) && waterTemperatureC != -9999); set
success = pressureValid && tempValid; only when success is true call
_ksensor.calcWaterDepthM(...) to compute waterDepthM and compute
waterPressure_mBar = 1000 * waterPressureBar, then compute depthValid =
(!isnan(waterDepthM) && waterDepthM != -9999) and finally set success = success
&& depthValid; remove the second block that re-evaluates pressure/temperature so
all validity checks are centralized around these flags.

In `@src/VariableArray.cpp`:
- Around line 299-315: Replace the VLA declaration Sensor*
sensorList[_sensorCount]; with a portable allocation and cleanup to avoid
compiler-specific VLAs: allocate an array of Sensor* of size _sensorCount (e.g.,
with new Sensor*[_sensorCount] or malloc) before the loop, use the same logic
that fills sensorList (referencing _variableCount, isLastVarFromSensor,
arrayOfVars[i]->parentSensor and addedSensors), validate addedSensors matches
_sensorCount, then free/delete the allocated sensorList before returning; make
sure all return paths free the allocation to avoid leaks.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/sensors/BoschBMP3xx.h (1)

8-10: ⚠️ Potential issue | 🟡 Minor

Stale documentation: BoschBMP3xx_Humidity is referenced but doesn't exist.

The @brief mentions BoschBMP3xx_Humidity as a variable subclass, but the BMP388/BMP390 sensors don't measure humidity and no such class is defined in this file. Since this PR is cleaning up the variable indices, consider updating this comment to remove the Humidity reference.

📝 Proposed fix
 * `@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.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/BoschBMP3xx.h` around lines 8 - 10, Update the stale class
documentation in the BoschBMP3xx header: remove the non-existent
BoschBMP3xx_Humidity from the `@brief` list and ensure only actual variable
subclasses (BoschBMP3xx_Temp, BoschBMP3xx_Pressure, BoschBMP3xx_Altitude) are
mentioned; edit the comment block at the top of the BoschBMP3xx declaration so
it accurately reflects the sensor capabilities and matches the defined classes.
src/sensors/BoschBME280.cpp (1)

14-29: ⚠️ Potential issue | 🟡 Minor

Add missing BME280_INC_CALC_VARIABLES parameter to first constructor.

The TwoWire* constructor (line 17) omits the incCalcValues parameter when calling the Sensor base class, causing it to default to 0. The Wire constructor (line 27) explicitly passes BME280_INC_CALC_VARIABLES (value 1). This creates inconsistent behavior between the two constructors.

🔧 Suggested fix
 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) {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/BoschBME280.cpp` around lines 14 - 29, The TwoWire* constructor
for BoschBME280 currently calls the Sensor base constructor without the
incCalcValues flag, causing inconsistent behavior versus the other constructor;
update the BoschBME280::BoschBME280(TwoWire* theI2C, ...) initializer list to
pass BME280_INC_CALC_VARIABLES to the Sensor(...) call (matching the
BoschBME280::BoschBME280(int8_t, ...) constructor) so both constructors set the
same incCalcValues behavior.
🧹 Nitpick comments (13)
src/sensors/SensirionSHT4x.h (1)

277-277: Consider adding documentation for the override.

The addSingleMeasurementResult() override lacks documentation. For better code clarity and expressiveness, consider adding either a @copydoc reference to inherit the base class documentation or a brief comment if this implementation has SHT4x-specific behavior.

📝 Suggested documentation addition
+    /**
+     * `@copydoc` Sensor::addSingleMeasurementResult()
+     */
     bool addSingleMeasurementResult(void) override;

As per coding guidelines: C++ header files should be reviewed for clean code and expressiveness principles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/SensirionSHT4x.h` at line 277, The declaration of the override
addSingleMeasurementResult() in SensirionSHT4x lacks documentation; update the
header so the method documents behavior—either add a `@copydoc` reference to
inherit the base-class doc or a short Doxygen comment describing any
SHT4x-specific behavior or return semantics for
SensirionSHT4x::addSingleMeasurementResult(). Ensure the comment is adjacent to
the declaration to satisfy header documentation guidelines.
src/sensors/ApogeeSQ212.cpp (1)

96-105: Consider adding debug output when voltage is out of range.

When adcVoltage falls outside the valid range (≥3.6V or ≤-0.3V), the code silently sets success = false without any debug logging. This could make it difficult to diagnose measurement failures in the field. Other failure paths in this function log debug messages.

📝 Proposed fix to add debug output
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage out of range:"), adcVoltage);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 105, When adcVoltage falls
outside the validated range in ApogeeSQ212.cpp, add a debug log before leaving
the block so failures are visible; specifically, in the branch where adcVoltage
>= 3.6 or adcVoltage <= -0.3 (the path that currently sets success = false),
call the existing debug logger (MS_DBG) to emit a clear message including
adcVoltage and the expected range (e.g., "adcVoltage out of range: <value>,
expected (-0.3, 3.6)"), and keep the existing behavior of not calling
verifyAndAddMeasurementResult and leaving success false so downstream logic is
unchanged.
src/sensors/AlphasenseCO2.cpp (1)

122-127: Consider naming calibration constants for clarity.
The resistor value and calibration coefficients are embedded as magic numbers; extracting them to const float values (or class constants) makes the calibration math easier to audit and maintain.

♻️ Suggested refactor
-        // 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;
+        // Convert voltage to current (mA) - assuming a 250 Ohm resistor is in
+        // series
+        const float kShuntOhms      = 250.0f;
+        const float kMilliampFactor = 1000.0f;
+        const float kSlope          = 312.5f;
+        const float kIntercept      = -1250.0f;
+        co2Current = (adcVoltage / kShuntOhms) * kMilliampFactor;
+        // Convert current to ppm (using a formula recommended by the sensor
+        // manufacturer)
+        calibResult = kSlope * co2Current + kIntercept;

As per coding guidelines: “Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 122 - 127, The calibration math
in AlphasenseCO2.cpp embeds magic numbers; refactor by extracting named
constants (e.g. a resistor value used when computing co2Current and the
calibration slope/offset used when computing calibResult) as const float (or
static constexpr) fields such as kSenseResistorOhm, kMilliAmpMultiplier (if
needed), CO2_CAL_SLOPE and CO2_CAL_OFFSET, and then rewrite the computations
that set co2Current and calibResult to use those constants and include brief
unit comments; keep names expressive and use class-level constants if these
values are sensor-specific (refer to co2Current and calibResult to locate the
calculations).
src/sensors/TIADS1x15.cpp (1)

96-102: Consider adding debug output when range validation fails.

When adcVoltage falls outside the valid range, the measurement is silently rejected. Adding a debug message would help diagnose sensor issues in the field.

♻️ Proposed enhancement
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // Apply the gain calculation, with a default gain of 10 V/V Gain
         calibResult = adcVoltage * _gain;
         MS_DBG(F("  calibResult:"), calibResult);
         verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult);
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage"), adcVoltage, F("V out of valid range"));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 96 - 102, Add a debug log for the
branch that rejects out-of-range voltages so field issues are visible: in the
range validation around adcVoltage (the if that currently sets calibResult,
calls verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult), sets
success and uses _gain), add an else that logs the rejected adcVoltage (and
optionally the expected range and _gain) via MS_DBG or the existing logger
before leaving the function so failed validations are visible in logs.
src/sensors/AnalogElecConductivity.cpp (1)

84-95: Minor: Unit abbreviation inconsistency in debug output.

Line 89 logs "Water EC (uSm/cm)" but the variable naming (EC_uScm, sensorEC_uScm) and standard SI convention suggest the unit should be uS/cm (microSiemens per centimeter). The current string could be misread as µS·m/cm.

Suggested fix
     sensorEC_uScm = readEC(_dataPin);
-    MS_DBG(F("Water EC (uSm/cm)"), sensorEC_uScm);
+    MS_DBG(F("Water EC (uS/cm)"), sensorEC_uScm);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AnalogElecConductivity.cpp` around lines 84 - 95, The debug
output string has a unit typo: update the MS_DBG call that logs "Water EC
(uSm/cm)" to use the correct unit abbreviation "uS/cm" to match the variable
names (sensorEC_uScm, EC_uScm) and SI convention; locate the MS_DBG invocation
in AnalogElecConductivity.cpp that prints Water EC and change the literal
accordingly so the debug message reads "Water EC (uS/cm)".
src/sensors/KellerParent.cpp (1)

124-126: Consider simplifying redundant validation.

The checks for waterPressureBar and waterTemperatureC are redundant here since they were already validated at lines 109-110 and haven't changed. Only the waterDepthM check is meaningful at this point.

♻️ Suggested simplification
-    success &= (!isnan(waterPressureBar) && waterPressureBar != -9999 &&
-                !isnan(waterTemperatureC) && waterTemperatureC != -9999 &&
-                !isnan(waterDepthM) && waterDepthM != -9999);
+    success &= (!isnan(waterDepthM) && waterDepthM != -9999);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/KellerParent.cpp` around lines 124 - 126, The boolean
accumulation at the end redundantly re-checks waterPressureBar and
waterTemperatureC; update the expression in KellerParent.cpp (the line updating
success) to only validate waterDepthM (i.e., check !isnan(waterDepthM) &&
waterDepthM != -9999) since waterPressureBar and waterTemperatureC were already
validated earlier (variables: waterPressureBar, waterTemperatureC, waterDepthM,
and the success flag).
src/sensors/MaxBotixSonar.cpp (1)

189-189: Minor style: redundant comparison to true.

The explicit == true comparison is unnecessary for boolean values.

♻️ Suggested simplification
-            if (_convertCm == true) { result *= 10; }
+            if (_convertCm) { result *= 10; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.cpp` at line 189, The if-statement in
MaxBotixSonar.cpp uses a redundant comparison (if (_convertCm == true)); change
it to a direct boolean check (if (_convertCm) { result *= 10; }) to simplify the
condition while keeping the same behavior; locate the check around the code
manipulating result in the function/method that performs the sonar conversion
and replace the equality comparison with a plain boolean test on _convertCm.
src/sensors/TurnerTurbidityPlus.cpp (1)

170-195: Voltage divider factor is now correctly applied; minor observability suggestion.

The past review concern about _voltageDividerFactor not being applied is now resolved—it's applied at line 175 before calibration. The decision to continue with raw voltage when the factor is invalid (lines 176-186) is intentional per maintainer preference to preserve data for post-processing.

Consider adding a debug message when the voltage range check fails (line 171) to aid troubleshooting:

♻️ Optional: Add debug output for out-of-range voltage
     // The ADS1X15 outputs a max value corresponding to Vcc + 0.3V
     if (adcVoltage < 5.3 && adcVoltage > -0.3) {
         if (_voltageDividerFactor > 0) {
             // Apply voltage divider factor if using a voltage divider to step
             // down the voltage
             adcVoltage *= _voltageDividerFactor;
         } else {
             // ...
         }
         // ...
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage out of range:"), String(adcVoltage, 3));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerTurbidityPlus.cpp` around lines 170 - 195, Add a debug log
when the ADC voltage falls outside the valid ADS1X15 range checked by the if
(adcVoltage < 5.3 && adcVoltage > -0.3) condition: when that check fails, call
MS_DBG to emit a concise message including the offending adcVoltage value and
any identifying context (e.g., sensor name or _sensorId if available) so
out-of-range readings are observable; leave the existing handling inside the
true branch (voltage divider application, calibResult calculation in
verifyAndAddMeasurementResult with TURBIDITY_PLUS_VAR_NUM and
TURBIDITY_PLUS_VOLTAGE_VAR_NUM) unchanged.
src/sensors/TurnerCyclops.cpp (1)

105-118: Name the voltage bounds instead of magic numbers.

It will make the range check self-documenting and easier to adjust.

♻️ Proposed refactor
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    const float kMinAdcVoltage = -0.3f;
+    const float kMaxAdcVoltage = 3.6f;
+    if (adcVoltage < kMaxAdcVoltage && adcVoltage > kMinAdcVoltage) {

As per coding guidelines: "**/*.cpp: Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 105 - 118, Replace the magic
numeric bounds in the adcVoltage range check with named constants to make the
intent clear and adjustable: introduce descriptive constants (e.g., ADC_VOLT_MIN
= -0.3 and ADC_VOLT_MAX = 3.6 or better names like
ADS_POWERED_VOLTAGE_MAX/ADS_TOLERANCE_NEGATIVE_MIN) and use them in the if
condition that references adcVoltage; keep the existing calibration calculation
using _conc_std, _volt_std, and _volt_blank, then call
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage) as before,
and finally return via bumpMeasurementAttemptCount(success). Ensure the
constants are declared with appropriate scope and comments explaining they
reflect the ADS range when powered at 3.3V.
extras/AWS_IoT_SetCertificates/ReadMe.md (1)

121-121: Optional: Consider more concise phrasing.

The phrase "all of the others" could be shortened to "all the others" for slightly more concise writing, though this is a minor stylistic preference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md` at line 121, Update the phrasing in
the README instruction under the section starting with `// Select your modem:`
by replacing "all of the others" with the more concise "all the others" so the
sentence reads "...remove the slashes (`//`) before the modem that you want to
use and add slashes to all the others." Keep the rest of the sentence unchanged
to preserve the instruction semantics.
docs/For-Developers/Developer-Setup.md (1)

96-135: Consider making lib_deps paths explicit placeholders.
The absolute Windows paths read like fixed requirements. Calling them out as replace‑with‑your‑path (or using a placeholder) would reduce copy‑paste confusion for new contributors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/For-Developers/Developer-Setup.md` around lines 96 - 135, Replace the
hard-coded Windows absolute paths in the lib_deps block with explicit
placeholders and an explanatory note so contributors must substitute their local
ModularSensors path; update the lib_deps entries (the lines beginning with
"Adafruit BusIO=", "Adafruit GFX Library=", etc.) to use a single placeholder
token (e.g. <PATH_TO_MODULAR_SENSORS> or ${MODULAR_SENSORS_PATH>) instead of
"C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\...", and add
one short sentence above the list telling users to replace that placeholder with
their own absolute path.
src/modems/LoggerModemMacros.h (1)

460-471: Unused TinyGSMType parameter in null secure client macro.

The MS_MODEM_CREATE_NULL_SECURE_CLIENTS macro accepts TinyGSMType as a parameter but doesn't use it since it only returns nullptr. While this maintains API consistency with other macros, you could document this in the macro's docstring to clarify the parameter is for signature compatibility only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/LoggerModemMacros.h` around lines 460 - 471, The macro
MS_MODEM_CREATE_NULL_SECURE_CLIENTS currently accepts a TinyGSMType parameter
but does not use it; update the macro's comment/docstring above its definition
to state that TinyGSMType is intentionally unused and present only to keep the
API/signature consistent with other macros (reference the macro name
MS_MODEM_CREATE_NULL_SECURE_CLIENTS and the generated methods
specificModem::createSecureClient) so readers understand why the parameter
exists.
src/sensors/GeoluxHydroCam.cpp (1)

251-252: Consider not setting _filename when transfer is incomplete.

The comment explicitly states the filename is stored "even if the transfer was incomplete." This means getLastSavedImageName() may return a filename for a corrupted/incomplete image file, which could be misleading to downstream consumers.

If this is intentional (e.g., for debugging or partial recovery), consider documenting this behavior in getLastSavedImageName(). Otherwise, consider only setting _filename when success is true.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GeoluxHydroCam.cpp` around lines 251 - 252, The code currently
assigns _filename unconditionally in GeoluxHydroCam (the block that sets
"_filename = filename"), which causes getLastSavedImageName() to expose names
for incomplete/corrupted transfers; change the logic to only assign _filename
when the transfer completed successfully (i.e., when the local variable/flag
success is true), or alternatively add documentation to getLastSavedImageName()
if the current behavior is intentional; update references to _filename
assignment in the relevant transfer/save routine and ensure callers rely on the
documented/conditional semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ChangeLog.md`:
- Around line 15-17: The changelog incorrectly labels the capitalization change
of setInitialShortIntervals(...) as BREAKING; update the entry to mark this as
"Deprecated" or "Renamed" instead and note that the old symbol
setinitialShortIntervals(...) remains available via the compatibility shim in
LoggerBase.h, so existing code is unaffected; adjust the wording to indicate
deprecation/rename and mention the compatibility shim for backward
compatibility.

In `@examples/AWS_IoT_Core/ReadMe.md`:
- Around line 64-66: In the inline example under the section starting with
"Start [variable_arrays]" the UUID sample
`"12345678-abcd-1234-ef00-1234567890ab` is missing its closing quote; update
that sample to include the trailing quote (i.e.,
`"12345678-abcd-1234-ef00-1234567890ab"`) so it matches the empty-quote example
(`""`) on the next line and avoids confusion.

In `@examples/logging_to_MMW/ReadMe.md`:
- Around line 52-63: Update the prose to use “logger ID” (two words) instead of
the camelCase “loggerID” for readability; specifically change the sentence that
instructs users to replace the placeholder and the inline comment referring to
the variable (which references the LoggerID constant) so the README text reads
“logger ID” while still showing the code variable name LoggerID in code blocks.

In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md`:
- Line 48: Replace the in-sentence token `YourLoggerID` with a human-readable
phrase like "logger ID" (or "logger ID/serial number") in the README text so it
reads naturally; update the sentence that currently says "Change the text
`YourLoggerID` in this section of code to your loggerID or serial number:" to
use "logger ID" (or "logger ID/serial number") instead of the camelCase
variable-looking `loggerID`.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Line 178: The README incorrectly tells users to set the modem AT-port by
changing `#define` SerialMon Serial; instead instruct them to update the SerialAT
definition (not SerialMon) — e.g., change the SerialAT mapping for your board
(use `#define` SerialAT SerialBee on Mega/Leonardo or the SoftwareSerial
declaration SerialAT(2, 3) for Uno/Nano) so the modem AT commands go to the
correct port; leave SerialMon as the debug console.

In `@src/sensors/ProcessorStats.cpp`:
- Around line 22-51: The comment for the Sodaq One block is misleading about
"different battery pins and multipliers" while only _batteryMultiplier is
changed; update the comment to accurately reflect behaviour (only the multiplier
is version-dependent and _batteryPin remains the default), or if the hardware
requires different pins per version, implement conditional assignments to
_batteryPin for ARDUINO_SODAQ_ONE/ARDUINO_SODAQ_ONE_BETA based on _version;
reference the conditional block that checks strcmp(_version, "v0.1") / "v0.2"
and the symbols _batteryMultiplier and _batteryPin when making the change so the
comment and implementation stay consistent.

---

Outside diff comments:
In `@src/sensors/BoschBME280.cpp`:
- Around line 14-29: The TwoWire* constructor for BoschBME280 currently calls
the Sensor base constructor without the incCalcValues flag, causing inconsistent
behavior versus the other constructor; update the
BoschBME280::BoschBME280(TwoWire* theI2C, ...) initializer list to pass
BME280_INC_CALC_VARIABLES to the Sensor(...) call (matching the
BoschBME280::BoschBME280(int8_t, ...) constructor) so both constructors set the
same incCalcValues behavior.

In `@src/sensors/BoschBMP3xx.h`:
- Around line 8-10: Update the stale class documentation in the BoschBMP3xx
header: remove the non-existent BoschBMP3xx_Humidity from the `@brief` list and
ensure only actual variable subclasses (BoschBMP3xx_Temp, BoschBMP3xx_Pressure,
BoschBMP3xx_Altitude) are mentioned; edit the comment block at the top of the
BoschBMP3xx declaration so it accurately reflects the sensor capabilities and
matches the defined classes.

---

Duplicate comments:
In @.gitattributes:
- Around line 31-43: The .gitattributes entries reference git filter drivers
filter.smudgePasswords and filter.disableDebug but those drivers are not defined
anywhere, so add explicit git config definitions and/or setup steps to register
them: update the setupGitFilters.bat / setupGitFilters.ps1 (and any setup .sh)
to run the necessary git config --local entries for filter.smudgePasswords and
filter.disableDebug (and include how they map to smudge/clean/driver programs),
commit/ship the filter driver scripts referenced by those configs, and document
the required setup in the repo README so contributors don't get warnings; also
add a small verification step to your existing setup or CI that checks for the
presence of the git config keys for filter.smudgePasswords and
filter.disableDebug.

In `@continuous_integration/generate_job_matrix.py`:
- Around line 105-121: The os.walk loop currently uses `continue` to skip
excluded `folder_name`s but still descends into those directories; modify the
walk so you prune the `_subdirs` list in-place to remove excluded folder names
before recursion (e.g., filter `_subdirs` to exclude ".history", "logger_test",
"archive", "tests", and `menu_example_name`) so the loop never walks into them;
update the loop that builds `non_menu_examples` (refer to variables
`non_menu_examples`, `root`, `_subdirs`, `files`, `folder_name`, and
`menu_example_name`) to perform this in-place modification and keep the existing
debug `use_verbose` behavior unchanged.

In `@examples/baro_rho_correction/ReadMe.md`:
- Around line 50-54: Update the wording to use “logger ID” consistently in the
README section (do not rename the actual variable). Replace the phrase
`loggerID` in explanatory text and the inline comment that precedes the code
snippet so it reads e.g. “Logger ID, also becomes the prefix…” while leaving the
code variable name LoggerID unchanged to avoid breaking examples.

In `@examples/double_logger/ReadMe.md`:
- Around line 49-54: Update the README to use the phrase "logger ID"
consistently (space-separated) instead of "loggerID": change the explanatory
sentence and the example string literal value in the code snippet (const char
*LoggerID = "YourLoggerID";) to use "Your logger ID" (or similar) so the wording
matches the heading; do not change the LoggerID variable name in code, only the
human-facing text and example string.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to modem.gsmModem.forceModemBaud(...) is invalid
because TinyGSM doesn't expose forceModemBaud; replace it with a supported API:
either call modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) to set the
serial bitrate directly or use TinyGsmAutoBaud(SerialAT, min, max) to
auto-negotiate; update both occurrences (the calls around the PRINTOUT and the
duplicate at lines ~496-498) and keep the same variables (modem.gsmModem,
modemSerial, modemBaud) when wiring the correct method.

In `@examples/example_dependencies.json`:
- Around line 26-30: The dependency entry for "Adafruit NeoPixel" uses version
"~1.15.4" which may not exist; verify that tag "1.15.4" is published in the
Adafruit_NeoPixel repo and if it is missing, update the "version" value to a
valid existing release (e.g., latest stable tag or exact semver that exists) so
dependency resolution succeeds; check and replace the "version": "~1.15.4" value
in the Adafruit NeoPixel entry accordingly.

In `@examples/examples.dox`:
- Around line 67-77: Update the broken `@ref` anchor: replace occurrences of `@ref`
examples_outdated with `@ref` page_examples_outdated (the renamed page anchor) so
the `@subpage/`@page links (e.g., the `@subpage` line and the link [Examples for
Outdated Hardware](`@ref` ...)) point to the correct symbol
page_examples_outdated.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_Mayfly1/ReadMe.md`:
- Line 5: The README sentence was corrected to "This example also makes use" but
the review metadata tag "[duplicate_comment]" remains; remove the duplicate or
leftover review marker and ensure the README content in ReadMe.md (the paragraph
describing Meter Hydros 21, SIM7080G LTEbee, and Mayfly v1.0/v1.1) contains no
leftover review tokens so the file only includes the final user-facing text.

In `@examples/simple_logging/ReadMe.md`:
- Line 43: Update the README instruction to use consistent casing for the
identifier: change the sentence that currently says "YourLoggerID" or "loggerID"
so it matches the code's identifier `LoggerID`; ensure the instruction text
refers to `LoggerID` (capital L and I) to avoid casing mismatch confusion.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Line 177: The GPRS credentials text mistakenly repeated the symbol gprsUser
instead of referencing the password; update the GPRS credentials sentence in the
README to use gprsUser for the username and gprsPass for the password (i.e.,
replace the duplicate gprsUser with gprsPass in the GPRS credentials section) so
it correctly instructs users to enter gprsUser and gprsPass in the ino file and
recompile/re-upload.

In `@src/LoggerModem.h`:
- Around line 991-992: Update the comment in LoggerModem.h that describes the
digital pin: insert the missing preposition "to" so the sentence reads "attached
to the hard or panic reset pin of the modem" (the comment describing the digital
pin number on the MCU). Also correct the review/summary text that claimed a
duplicated "the" — change it to accurately state the actual issue is a missing
preposition "to" rather than a duplicated word.

In `@src/modems/SIMComSIM7000.h`:
- Line 77: Check whether the EnviroDIY TinyGSM fork you depend on actually
provides the header and symbol TinyGsmClientSIM7000SSL.h and TinyGsmSim7000SSL;
if they are missing, replace the specific include with the standard TinyGSM
approach: include the common TinyGsmClient/TinyGsm headers, define
TINY_GSM_MODEM_SIM7000SSL (or the appropriate modem macro), and use the standard
secure client type (e.g., TinyGsmClient with the SSL modem typedef) where
SIM7000-specific types were referenced; update any uses of
TinyGsmClientSIM7000SSL or TinyGsmSim7000SSL to the corresponding standard
TinyGsmClient/TinyGsm modem typedefs so the code compiles against the fork you
actually vendor.

In `@src/modems/SodaqUBeeU201.h`:
- Line 78: The include was changed to the modem-specific header
TinyGsmClientUBLOX.h for the SodaqUBeeU201 driver; ensure all references that
previously used the generic TinyGsmClient API are updated to the UBLOX-specific
types and constructors used by TinyGsmClientUBLOX.h—search for usages of
TinyGsmClient (and any constructors or methods in the SodaqUBeeU201 class) and
replace them with the corresponding TinyGsmClientUBLOX types/method names so the
file compiles against the modem-specific header.

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 14-33: The two EverlightALSPT19 constructors register different
incCalcValues because the pin-based constructor calls Sensor(...) with 8 args
(defaulting incCalcValues) while the built-in constructor passes
ALSPT19_INC_CALC_VARIABLES as a 9th arg; update the pin-based constructor
EverlightALSPT19::EverlightALSPT19(int8_t powerPin, int8_t dataPin, ...) to pass
ALSPT19_INC_CALC_VARIABLES into the Sensor(...) initializer (matching the
built-in overload) so both constructors use the same incCalcValues.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 350-355: minTime is set to 0 when _alwaysAutoFocus is false,
allowing readiness checks to run immediately; change the ternary so that when
_alwaysAutoFocus is false minTime is assigned _stabilizationTime_ms (while
keeping the autofocus branch as HYDROCAM_AUTOFOCUS_TIME_MS +
_stabilizationTime_ms) and leave maxTime as currently set
(HYDROCAM_AUTOFOCUS_TIME_MAX + HYDROCAM_STABILIZATION_TIME_MAX when autofocus
on, otherwise HYDROCAM_STABILIZATION_TIME_MAX) so elapsed_since_wake_up >
minTime only becomes true after the intended stabilization period; update the
expression that assigns minTime in GeoluxHydroCam.cpp accordingly (referencing
minTime, _alwaysAutoFocus, _stabilizationTime_ms, HYDROCAM_AUTOFOCUS_TIME_MS).

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-282: The new GeoluxHydroCam constructors changed parameter
order by inserting powerPin2 before imageResolution, breaking positional-call
compatibility for existing callers; restore backward compatibility by either
providing an overload that keeps the original signature order
(GeoluxHydroCam(..., int8_t powerPin, const char* imageResolution, const char*
filePrefix = nullptr, bool alwaysAutoFocus = false)) or move the new parameter
powerPin2 to the end of the parameter list so existing four-argument positional
calls still bind to imageResolution; update both Stream* and Stream& constructor
declarations (the GeoluxHydroCam constructors) and ensure default values remain
consistent.

In `@src/sensors/GroPointParent.cpp`:
- Around line 165-239: The method GroPointParent::addSingleMeasurementResult
currently treats partial reads as failures by returning
bumpMeasurementAttemptCount(success && successT); change this to consider
partial success (moisture OR temperature) as overall success by returning
bumpMeasurementAttemptCount(success || successT); keep the existing
verifyAndAddMeasurementResult calls (they already only add when success or
successT are true) and ensure you still only call
bumpMeasurementAttemptCount(false) earlier when MEASUREMENT_SUCCESSFUL is not
set.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop currently uses a fixed 25-iteration cap
(variables rangeAttempts, success) which can block for many stream timeouts;
replace the hard-coded max attempts with a time-budget guard using millis():
record a start timestamp before the while loop and change the condition to also
break when elapsed time exceeds a configurable measurement timeout (e.g.,
HRXL_MEASUREMENT_TIME_MS or a new _measurementTimeoutMs member), preserving the
existing trigger logic (_triggerPin, digitalWrite) and result checks
(_stream->parseInt(), _maxRange, _convertCm); ensure the loop sets result =
-9999 and exits gracefully when the time budget is exhausted so the main loop
cannot be stalled.

In `@src/sensors/MeaSpecMS5803.cpp`:
- Around line 83-84: Update the static "14 bar" comment to describe the dynamic
upper bound based on the _maxPressure member: note that the code uses
_maxPressure (in bar) converted to mbar via _maxPressure * 1000.0, so the
comment should state the pressure range is determined by _maxPressure (bar) and
converted to mbar for the runtime check (see the _maxPressure and the
_maxPressure * 1000.0 usage).

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 132-146: The code incorrectly uses bitwise NOT (~res1/etc.) to try
to get two's complement; instead assemble the 18-bit ADC sample from res1, res2,
res3 into a single signed integer (e.g. raw = (res1 << 16) | (res2 << 8) |
res3), sign-extend the 18-bit value properly (if the 18th bit is set subtract (1
<< 18) or use arithmetic shift on a signed int32), then compute res = raw *
<LSB_voltage> (apply the existing scaling factors once to the signed raw value);
remove the one‑off subtraction hack (res -= 0.015625) and any per-byte bitRead
manipulations—use the assembled signed raw value for all downstream
calculations.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 109-121: The I2C read loop and buffer handling in
RainCounterI2C::(the scope containing SerialBuffer and byte_in) are correct:
SerialBuffer[4] is zero-initialized, the while condition uses byte_in < 4 to
prevent overruns, and the early return via bumpMeasurementAttemptCount(false) on
byte_in < 1 is appropriate; no code changes are required for SerialBuffer,
byte_in, _i2c->available()/read() usage or the early-exit logic.

In `@src/VariableArray.cpp`:
- Around line 383-389: The local variables sName and cycCount are declared only
under the MS_VARIABLEARRAY_DEBUG/MS_VARIABLEARRAY_DEBUG_DEEP guard but
MS_DBG(...) calls reference them unconditionally; move the declarations for
sName and cycCount so they are defined outside (before) the conditional or
alternatively guard the MS_DBG calls with the same preprocessor condition;
locate the creation of sName and cycCount around
sensorList[i]->getSensorNameAndLocation(),
getNumberCompleteMeasurementsAttempts(), and getNumberRetryAttemptsMade() and
ensure those symbols are available whenever MS_DBG is compiled in (or wrap the
MS_DBG uses) to avoid undefined variable errors.

---

Nitpick comments:
In `@docs/For-Developers/Developer-Setup.md`:
- Around line 96-135: Replace the hard-coded Windows absolute paths in the
lib_deps block with explicit placeholders and an explanatory note so
contributors must substitute their local ModularSensors path; update the
lib_deps entries (the lines beginning with "Adafruit BusIO=", "Adafruit GFX
Library=", etc.) to use a single placeholder token (e.g.
<PATH_TO_MODULAR_SENSORS> or ${MODULAR_SENSORS_PATH>) instead of
"C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\...", and add
one short sentence above the list telling users to replace that placeholder with
their own absolute path.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Line 121: Update the phrasing in the README instruction under the section
starting with `// Select your modem:` by replacing "all of the others" with the
more concise "all the others" so the sentence reads "...remove the slashes
(`//`) before the modem that you want to use and add slashes to all the others."
Keep the rest of the sentence unchanged to preserve the instruction semantics.

In `@src/modems/LoggerModemMacros.h`:
- Around line 460-471: The macro MS_MODEM_CREATE_NULL_SECURE_CLIENTS currently
accepts a TinyGSMType parameter but does not use it; update the macro's
comment/docstring above its definition to state that TinyGSMType is
intentionally unused and present only to keep the API/signature consistent with
other macros (reference the macro name MS_MODEM_CREATE_NULL_SECURE_CLIENTS and
the generated methods specificModem::createSecureClient) so readers understand
why the parameter exists.

In `@src/sensors/AlphasenseCO2.cpp`:
- Around line 122-127: The calibration math in AlphasenseCO2.cpp embeds magic
numbers; refactor by extracting named constants (e.g. a resistor value used when
computing co2Current and the calibration slope/offset used when computing
calibResult) as const float (or static constexpr) fields such as
kSenseResistorOhm, kMilliAmpMultiplier (if needed), CO2_CAL_SLOPE and
CO2_CAL_OFFSET, and then rewrite the computations that set co2Current and
calibResult to use those constants and include brief unit comments; keep names
expressive and use class-level constants if these values are sensor-specific
(refer to co2Current and calibResult to locate the calculations).

In `@src/sensors/AnalogElecConductivity.cpp`:
- Around line 84-95: The debug output string has a unit typo: update the MS_DBG
call that logs "Water EC (uSm/cm)" to use the correct unit abbreviation "uS/cm"
to match the variable names (sensorEC_uScm, EC_uScm) and SI convention; locate
the MS_DBG invocation in AnalogElecConductivity.cpp that prints Water EC and
change the literal accordingly so the debug message reads "Water EC (uS/cm)".

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-105: When adcVoltage falls outside the validated range in
ApogeeSQ212.cpp, add a debug log before leaving the block so failures are
visible; specifically, in the branch where adcVoltage >= 3.6 or adcVoltage <=
-0.3 (the path that currently sets success = false), call the existing debug
logger (MS_DBG) to emit a clear message including adcVoltage and the expected
range (e.g., "adcVoltage out of range: <value>, expected (-0.3, 3.6)"), and keep
the existing behavior of not calling verifyAndAddMeasurementResult and leaving
success false so downstream logic is unchanged.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 251-252: The code currently assigns _filename unconditionally in
GeoluxHydroCam (the block that sets "_filename = filename"), which causes
getLastSavedImageName() to expose names for incomplete/corrupted transfers;
change the logic to only assign _filename when the transfer completed
successfully (i.e., when the local variable/flag success is true), or
alternatively add documentation to getLastSavedImageName() if the current
behavior is intentional; update references to _filename assignment in the
relevant transfer/save routine and ensure callers rely on the
documented/conditional semantics.

In `@src/sensors/KellerParent.cpp`:
- Around line 124-126: The boolean accumulation at the end redundantly re-checks
waterPressureBar and waterTemperatureC; update the expression in
KellerParent.cpp (the line updating success) to only validate waterDepthM (i.e.,
check !isnan(waterDepthM) && waterDepthM != -9999) since waterPressureBar and
waterTemperatureC were already validated earlier (variables: waterPressureBar,
waterTemperatureC, waterDepthM, and the success flag).

In `@src/sensors/MaxBotixSonar.cpp`:
- Line 189: The if-statement in MaxBotixSonar.cpp uses a redundant comparison
(if (_convertCm == true)); change it to a direct boolean check (if (_convertCm)
{ result *= 10; }) to simplify the condition while keeping the same behavior;
locate the check around the code manipulating result in the function/method that
performs the sonar conversion and replace the equality comparison with a plain
boolean test on _convertCm.

In `@src/sensors/SensirionSHT4x.h`:
- Line 277: The declaration of the override addSingleMeasurementResult() in
SensirionSHT4x lacks documentation; update the header so the method documents
behavior—either add a `@copydoc` reference to inherit the base-class doc or a
short Doxygen comment describing any SHT4x-specific behavior or return semantics
for SensirionSHT4x::addSingleMeasurementResult(). Ensure the comment is adjacent
to the declaration to satisfy header documentation guidelines.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 96-102: Add a debug log for the branch that rejects out-of-range
voltages so field issues are visible: in the range validation around adcVoltage
(the if that currently sets calibResult, calls
verifyAndAddMeasurementResult(TIADS1X15_VAR_NUM, calibResult), sets success and
uses _gain), add an else that logs the rejected adcVoltage (and optionally the
expected range and _gain) via MS_DBG or the existing logger before leaving the
function so failed validations are visible in logs.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 105-118: Replace the magic numeric bounds in the adcVoltage range
check with named constants to make the intent clear and adjustable: introduce
descriptive constants (e.g., ADC_VOLT_MIN = -0.3 and ADC_VOLT_MAX = 3.6 or
better names like ADS_POWERED_VOLTAGE_MAX/ADS_TOLERANCE_NEGATIVE_MIN) and use
them in the if condition that references adcVoltage; keep the existing
calibration calculation using _conc_std, _volt_std, and _volt_blank, then call
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage) as before,
and finally return via bumpMeasurementAttemptCount(success). Ensure the
constants are declared with appropriate scope and comments explaining they
reflect the ADS range when powered at 3.3V.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 170-195: Add a debug log when the ADC voltage falls outside the
valid ADS1X15 range checked by the if (adcVoltage < 5.3 && adcVoltage > -0.3)
condition: when that check fails, call MS_DBG to emit a concise message
including the offending adcVoltage value and any identifying context (e.g.,
sensor name or _sensorId if available) so out-of-range readings are observable;
leave the existing handling inside the true branch (voltage divider application,
calibResult calculation in verifyAndAddMeasurementResult with
TURBIDITY_PLUS_VAR_NUM and TURBIDITY_PLUS_VOLTAGE_VAR_NUM) unchanged.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/For-Developers/Developer-Setup.md (1)

57-135: ⚠️ Potential issue | 🟡 Minor

Avoid hard‑coded user paths in lib_deps example and fix the typo.

Line 72 says “director” → should be “directory”. Also, the absolute C:\Users\sdamiano\... paths in lib_deps will not work for other developers; please replace with placeholders (or an environment-variable style path) and apply consistently to all entries.

📄 Suggested doc tweak
-; Set the director for the code you want to test from
+; Set the directory for the code you want to test from
 ...
-    Adafruit BusIO=symlink://C:\Users\sdamiano\Documents\GitHub\EnviroDIY\ModularSensors\lib\Adafruit BusIO
+    Adafruit BusIO=symlink://<path-to-your-repo>\lib\Adafruit BusIO
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/For-Developers/Developer-Setup.md` around lines 57 - 135, Fix the typo
in the explanatory paragraph by changing “director” to “directory” and remove
the hard‑coded absolute Windows user paths in the sample platformio.ini
lib_deps; update the src_dir comment and each lib_deps entry (e.g., the Adafruit
BusIO, Adafruit GFX Library, etc.) to use a reusable placeholder or environment
variable format (for example <local_lib_path>/Adafruit BusIO or
${LIB_DIR}/Adafruit BusIO) or relative paths (../lib/Adafruit BusIO) so the
example works for other developers and apply that change consistently across all
lib_deps entries referenced in the snippet.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

359-369: ⚠️ Potential issue | 🟠 Major

Missing guard for zero records produces invalid JSON.

When records == 0, this code produces "timestamp":, (no value between colon and comma), which is malformed JSON. The guard in calculateJsonSize() returns early but doesn't prevent flushDataBuffer() from executing with an empty buffer.

🐛 Proposed fix: add early return for empty buffer
+    int records = _logBuffer.getNumRecords();
+    if (records == 0) {
+        MS_DBG(F("No records to send, skipping flush"));
+        return 204;  // No Content
+    }
+
     // Open a TCP/IP connection to Monitor My Watershed
     MS_DBG(F("Connecting client"));
     MS_START_DEBUG_TIMER;
     if (outClient->connect(monitorMWHost, monitorMWPort)) {

Add this check before line 328, and then update line 359 to remove the redundant call:

-        int records = _logBuffer.getNumRecords();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 359 - 369, The
code writes a "timestamp" value even when _logBuffer.getNumRecords() == 0,
producing malformed JSON; in the function that emits this block
(flushDataBuffer) add an early guard: if (_logBuffer.getNumRecords() == 0)
return immediately so no timestamp field or trailing comma is emitted, and then
remove the now-redundant trailing txBufferAppend(',') in the timestamp-emission
block; reference _logBuffer.getNumRecords(), flushDataBuffer(), and
txBufferAppend when locating and updating the code.
🧹 Nitpick comments (9)
src/sensors/TIADS1x15.cpp (1)

94-99: Replace magic voltage limits with named constants.

The 3.6 and -0.3 thresholds are hardware-specific; giving them names improves expressiveness and makes future supply/PGA adjustments safer.

🧹 Suggested tweak
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    const float kAdsMaxVoltage = 3.6f;   // VDD + 0.3V (3.3V supply)
+    const float kAdsMinVoltage = -0.3f;  // GND - 0.3V
+    if (adcVoltage < kAdsMaxVoltage && adcVoltage > kAdsMinVoltage) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 94 - 99, The adcVoltage bounds check
in TIADS1x15.cpp uses magic numbers (3.6 and -0.3); replace them with clearly
named constants (e.g., ADS_INPUT_V_MAX and ADS_INPUT_V_MIN or
ADC_VOLTAGE_VALID_MAX_V / ADC_VOLTAGE_VALID_MIN_V) declared near the top of the
file or as class-level static constexpr inside the TIADS1x15 class, then use
those constants in the if (adcVoltage < ... && adcVoltage > ...) check and
update the inline comment to reference the constant names and that they
correspond to the ADS input range at 3.3V supply; keep the existing calibResult
= adcVoltage * _gain and MS_DBG call unchanged.
src/sensors/MaxBotixSonar.h (1)

149-153: Documentation states 166ms but macro value is 250ms.

The comment says the HRXL takes 166ms to complete a measurement, but HRXL_MEASUREMENT_TIME_MS is set to 250. Consider updating the comment to explain why 250ms was chosen (e.g., margin for stream timeout alignment, or the new retry behavior).

📝 Suggested documentation fix
-/// `@brief` Sensor::_measurementTime_ms; the HRXL takes 166ms to complete a
-/// measurement.  It outputs results at least every 166ms.
+/// `@brief` Sensor::_measurementTime_ms; the HRXL outputs results at least every
+/// 166ms, but we use 250ms to match the stream timeout.
 /// `@note` Because this sensor allows up to 25 retries if a measurement fails,
 /// the actual time to get a measurement may be much longer than 166ms.
 `#define` HRXL_MEASUREMENT_TIME_MS 250
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaxBotixSonar.h` around lines 149 - 153, The comment says the
HRXL takes 166ms but the macro HRXL_MEASUREMENT_TIME_MS is 250; reconcile these
by either setting HRXL_MEASUREMENT_TIME_MS to 166 if you meant the raw sensor
measurement time, or update the comment above
Sensor::_measurementTime_ms/HRXL_MEASUREMENT_TIME_MS to explicitly state that
250ms was chosen to provide margin for stream timeout alignment and up to 25
retries (or other retry/timeout rationale) so the documentation matches the
macro value.
src/sensors/GroPointParent.cpp (1)

204-207: Consider a debug warning for unsupported models.

The default case silently leaves success and successT as false, causing the function to return failure. While the log message is helpful during debug builds, consider adding a setStatusBit(ERROR_OCCURRED) or a more prominent debug message to aid troubleshooting if an unsupported model is accidentally configured.

💡 Optional enhancement
         default: {
             // Get Values
             MS_DBG(F("Other GroPoint models not yet implemented."));
+            setStatusBit(ERROR_OCCURRED);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.cpp` around lines 204 - 207, In the default branch
of the switch in GroPointParent.cpp (where MS_DBG(F("Other GroPoint models not
yet implemented.")) is logged), set a more visible error state so failures from
unsupported models are obvious: call setStatusBit(ERROR_OCCURRED) (or the
project-specific error-bit setter used elsewhere) and/or emit a higher-priority
log (e.g., MS_LOG or MS_ERR) mentioning the unsupported model, and ensure the
local flags success and successT remain false so the function returns failure;
update the default case to use setStatusBit(ERROR_OCCURRED) and a clear error
log referencing the model identifier.
src/sensors/EverlightALSPT19.cpp (1)

59-73: Consider: Zero-value load resistor could cause division by zero.

The calculation on line 69 divides by _loadResistor * 1000. If a user mistakenly passes 0.0 for loadResistor, this would cause undefined behavior.

This is likely a configuration-time concern rather than a runtime issue, and zero-ohm load resistors are physically invalid. If this is intentionally left to the user to configure correctly, the current approach is acceptable—just ensure it's documented in the constructor's parameter description.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/EverlightALSPT19.cpp` around lines 59 - 73, The current
calculation in the else branch can divide by zero when _loadResistor is 0.0 (see
sensor_adc handling and the current_val computation); add a guard before
computing current_val and lux_val that checks _loadResistor (and optionally
PROCESSOR_ADC_MAX if desired) is non-zero/valid and, if zero, set current_val
and lux_val to 0.0 (or return/throw/log per project convention) while still
computing volt_val, and document/validate _loadResistor in the EverlightALSPT19
constructor or configuration path so invalid zero resistances are caught early.
src/sensors/GroPointParent.h (1)

172-174: Consider keeping brief Doxygen for wake to preserve API docs.
Removing the doc block drops generated documentation for this API; a short @copydoc keeps clarity without verbosity.

♻️ Suggested doc stub
-    bool wake(void) override;
+    /**
+     * `@copydoc` Sensor::wake
+     */
+    bool wake(void) override;

As per coding guidelines: **/*.h: Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/GroPointParent.h` around lines 172 - 174, Add a short Doxygen
stub above GroPointParent::wake to preserve generated docs; for example, add a
one-line comment like "/** `@copydoc` BaseSensor::wake */" or a brief "@brief Wake
sensor and prepare for reading" depending on the real base method, so the
declaration "bool wake(void) override;" keeps API docs. Ensure the comment is
placed immediately above the wake declaration inside the GroPointParent class.
src/sensors/ApogeeSQ212.cpp (1)

96-105: TODO: Voltage range verification for SQ-212 sensor.

The @todo comment indicates the voltage range check (< 3.6V and > -0.3V) needs verification against the SQ-212 sensor specifications. The current range is based on ADS behavior when powered at 3.3V, but the sensor's actual output range should be confirmed.

The SQ-212 datasheet specifies a 0-400 mV output range (0-2.5V with amplifier), so the upper bound of 3.6V is conservative but safe.

Would you like me to open an issue to track verification of the SQ-212 voltage range, or help draft updated range validation based on the sensor datasheet specifications?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 105, The voltage-range check
for the SQ-212 uses ADS-derived bounds (adcVoltage < 3.6 && adcVoltage > -0.3)
and must be replaced with validated sensor-specific bounds from the SQ-212
datasheet; update the check around adcVoltage in the block that computes
calibResult (uses SQ212_CALIBRATION_FACTOR, SQ212_PAR_VAR_NUM,
SQ212_VOLTAGE_VAR_NUM) to use explicit constants (e.g., SQ212_MIN_VOLTAGE = 0.0
and SQ212_MAX_VOLTAGE = 0.4 for the raw sensor output, or 2.5 if an amplifier is
present), remove the `@todo`, and add a clear out-of-range log/early-return path
when adcVoltage is outside those constants so verifyAndAddMeasurementResult is
only called for valid voltages.
src/publishers/EnviroDIYPublisher.h (1)

8-33: The brief has been properly updated; minor comment inconsistency remains.

The file-level brief (lines 8-10) now correctly describes EnviroDIYPublisher as a typedef reference for backward compatibility. However, the section comment at line 23 still references "EnviroDIY data portal receivers" which is inconsistent with the new Monitor My Watershed branding.

Consider updating line 23 to align with the new naming:

 // ============================================================================
-//  Functions for the EnviroDIY data portal receivers.
+//  Backward compatibility typedef for Monitor My Watershed publisher.
 // ============================================================================
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/EnviroDIYPublisher.h` around lines 8 - 33, Update the section
comment that currently reads "Functions for the EnviroDIY data portal
receivers." to match the Monitor My Watershed naming; change it to something
like "Functions for the Monitor My Watershed data portal receivers." Ensure the
comment near the typedef for EnviroDIYPublisher (typedef
MonitorMyWatershedPublisher EnviroDIYPublisher) and the surrounding block
comment reflect the new branding for consistency.
src/VariableArray.cpp (1)

299-301: Replace VLA with fixed-size array and bounds checking.

Sensor* sensorList[_sensorCount]; uses a variable-length array, which is a GNU extension and not standard C++. This may cause compatibility issues on non-GCC toolchains. Use MAX_NUMBER_VARS (defined as 21) as the upper bound with validation, consistent with other arrays in the codebase like sensorValues and variables.

♻️ Suggested fix
-    Sensor* sensorList[_sensorCount];
+    Sensor* sensorList[MAX_NUMBER_VARS];
+    if (_sensorCount > MAX_NUMBER_VARS) {
+        MS_DBG(F("ERROR: _sensorCount exceeds MAX_NUMBER_VARS"));
+        return false;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableArray.cpp` around lines 299 - 301, sensorList is declared as a
variable-length array (Sensor* sensorList[_sensorCount]) which is non-standard;
replace it with a fixed-size array using MAX_NUMBER_VARS and validate
_sensorCount before use: declare Sensor* sensorList[MAX_NUMBER_VARS]; ensure you
do not iterate or add more than MAX_NUMBER_VARS by clamping or returning early
if _sensorCount > MAX_NUMBER_VARS, update any loops that use addedSensors to
respect MAX_NUMBER_VARS, and mirror bounds-checking patterns used for
sensorValues and variables so no out-of-bounds writes occur.
src/publishers/MonitorMyWatershedPublisher.cpp (1)

106-129: Documentation comments on setters are misleading.

The comments "Returns the data destination" are copy-pasted to setter methods (setHost, setPath, setPort) but setters don't return anything meaningful.

📝 Suggested fix for comments
-// Returns the data destination
+// Sets the data destination host
 void MonitorMyWatershedPublisher::setHost(const char* host) {
     monitorMWHost = host;
 }

-// Returns the data destination
+// Sets the data destination path
 void MonitorMyWatershedPublisher::setPath(const char* endpoint) {
     monitorMWPath = endpoint;
 }

-// Returns the data destination
+// Sets the data destination port
 void MonitorMyWatershedPublisher::setPort(int port) {
     monitorMWPort = port;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 106 - 129, The
comments above the setter functions are incorrect: replace the misleading
"Returns the data destination" comment on MonitorMyWatershedPublisher::setHost,
::setPath, and ::setPort with concise setter descriptions (e.g., "Sets the data
destination host", "Sets the data destination path/endpoint", "Sets the data
destination port") that accurately describe the action; ensure the getter
methods (MonitorMyWatershedPublisher::getPath, ::getPort) retain return-oriented
comments if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_DigiLTE/ReadMe.md`:
- Around line 26-27: Update the heading/TOC entries that read "Set the
universally unique identifiers (UUID) for each variable" to use the plural form
"UUIDs" (e.g., "Set the universally unique identifiers (UUIDs) for each
variable"); apply the same change to the duplicate occurrences referenced (the
TOC entry and the heading instances around the second occurrence).

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 146-150: The current guard prevents size calculation with records
== 0 but does not stop flushDataBuffer from being called with an empty buffer;
update flushDataBuffer to immediately return (and optionally log) when the
internal record count (records) is 0 to avoid emitting malformed JSON like
`"timestamp":,`; locate flushDataBuffer in MonitorMyWatershedPublisher.cpp and
add an early check for records == 0 at the top, returning before any JSON
assembly (or alternatively, ensure all callers check records and never call
flushDataBuffer with zero records).

In `@src/sensors/AnalogElecConductivity.cpp`:
- Around line 78-95: The code currently calls bumpMeasurementAttemptCount(true)
even when readEC(_dataPin) returns invalid (NaN/inf), so change
addSingleMeasurementResult to validate sensorEC_uScm after readEC: use
isnan()/isfinite() (or equivalent) to detect invalid readings from
readEC(_dataPin); if invalid, do not call verifyAndAddMeasurementResult and
instead return bumpMeasurementAttemptCount(false) so retries continue; only call
verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, sensorEC_uScm)
followed by bumpMeasurementAttemptCount(true) when the value is valid.

---

Outside diff comments:
In `@docs/For-Developers/Developer-Setup.md`:
- Around line 57-135: Fix the typo in the explanatory paragraph by changing
“director” to “directory” and remove the hard‑coded absolute Windows user paths
in the sample platformio.ini lib_deps; update the src_dir comment and each
lib_deps entry (e.g., the Adafruit BusIO, Adafruit GFX Library, etc.) to use a
reusable placeholder or environment variable format (for example
<local_lib_path>/Adafruit BusIO or ${LIB_DIR}/Adafruit BusIO) or relative paths
(../lib/Adafruit BusIO) so the example works for other developers and apply that
change consistently across all lib_deps entries referenced in the snippet.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 359-369: The code writes a "timestamp" value even when
_logBuffer.getNumRecords() == 0, producing malformed JSON; in the function that
emits this block (flushDataBuffer) add an early guard: if
(_logBuffer.getNumRecords() == 0) return immediately so no timestamp field or
trailing comma is emitted, and then remove the now-redundant trailing
txBufferAppend(',') in the timestamp-emission block; reference
_logBuffer.getNumRecords(), flushDataBuffer(), and txBufferAppend when locating
and updating the code.

---

Duplicate comments:
In `@ChangeLog.md`:
- Around line 75-79: The ChangeLog has repetitive "Removed" at the start of
several consecutive bullets (items referencing _maxSamplesToAverage,
VariableArray, countMaxToAverage(), powerUp, powerDown, EnviroDIY data portal,
and example defines); rewrite these bullets for better readability by varying
the verbs and combining related removals into one or two concise lines (e.g.,
group all API/parameter removals like "_maxSamplesToAverage" and
"countMaxToAverage()" under a single removal sentence, merge power-related
changes for "powerUp" and "powerDown" into one line, and summarize removed
references/defines together), ensuring each referenced symbol (VariableArray,
countMaxToAverage(), powerUp, powerDown, EnviroDIY) appears where relevant.

In `@continuous_integration/generate_job_matrix.py`:
- Around line 320-336: Ensure the example filtering uses the basename before
applying pattern checks: extract example_name =
os.path.basename(example).lower() and then use example_name in the three
conditional checks for "data_saving", "mayfly", and "drwi" to avoid matching
parts of the full path; keep the subsequent call to
create_logged_command(compiler=compiler, group_title=example,
code_subfolder=example, pio_env=pio_env) unchanged so only intended examples are
added to command_list.
- Around line 105-122: The previous review concerns have been resolved: the
exclusion now uses folder_name = os.path.basename(root) and subdirs.clear()
prevents descending into excluded directories, and the filename comparison uses
f"{folder_name}.ino"; no further changes are required in the non_menu_examples
discovery loop in generate_job_matrix.py.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-509: The code calls the nonexistent TinyGSM API forceModemBaud
on modem.gsmModem (using modemSerial and modemBaud) which prevents compilation;
replace this with a supported TinyGSM flow: after syncing AT with
modem.gsmModem.testAT() failing, call TinyGsmAutoBaud(modemSerial) or
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) (or perform
TinyGsmAutoBaud then setBaud) to force the baud, ensuring you use the correct
modemSerial and modemBaud variables and keep the surrounding testAT() logic
intact.

In `@examples/example_dependencies.json`:
- Around line 26-30: The dependency entry for "Adafruit NeoPixel" currently pins
version "~1.15.4" which may not exist in the Arduino Library Manager; verify
that the version string "~1.15.4" for the dependency with "name": "Adafruit
NeoPixel" is actually published and, if it is not, update the version field to
the latest available published version (or remove the tilde to use the exact
latest stable semver) in examples/example_dependencies.json so dependency
resolution will succeed.

In `@extras/AWS_IoT_SetCertificates/ReadMe.md`:
- Around line 177-178: Fix the credential and serial references: update the text
to say the SIM unlock credentials use the fields gprsUser and gprsPass (use
"username" instead of "user name") and correct the SerialAT reference to point
to the AT serial define (e.g., indicate that you should set `#define` SerialAT
Serial1), not `#define` SerialMon Serial1; mention SerialMon is for debug console
and SerialAT is for modem AT commands so readers set the proper define.

In `@src/modems/SodaqUBeeU201.h`:
- Line 235: Public API type changed from TinyGsm to TinyGsmUBLOX for the GSM
modem declaration (symbol TinyGsmUBLOX in SodaqUBeeU201.h); update the release
notes and public API migration guide to document this breaking change, listing
the old type name (TinyGsm), the new type (TinyGsmUBLOX), why it changed, any
necessary code updates for consumers, and a short example showing how to migrate
existing code that used TinyGsm to the new TinyGsmUBLOX type.

In `@src/publishers/UbidotsPublisher.cpp`:
- Around line 119-120: Update the error message string passed to PRINTOUT in
UbidotsPublisher.cpp so it doesn't end with "!."; locate the PRINTOUT call that
currently prints "A sampling feature UUID must be set before publishing data to
Ubidots!." and change it to a single, properly punctuated sentence such as "A
sampling feature UUID must be set before publishing data to Ubidots!" (or use a
period instead of the exclamation if preferred).

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 350-355: minTime is set to 0 when _alwaysAutoFocus is false,
allowing elapsed_since_wake_up > minTime to be true immediately; change the
minTime calculation in GeoluxHydroCam.cpp so that when _alwaysAutoFocus is false
it uses _stabilizationTime_ms (not 0L). Update the ternary that sets minTime to:
when _alwaysAutoFocus is true use HYDROCAM_AUTOFOCUS_TIME_MS +
_stabilizationTime_ms, otherwise use _stabilizationTime_ms; keep maxTime logic
unchanged and ensure references to minTime, _alwaysAutoFocus, and
_stabilizationTime_ms are adjusted accordingly.

In `@src/sensors/GeoluxHydroCam.h`:
- Around line 270-280: The two GeoluxHydroCam constructor overloads change the
argument order by inserting powerPin2 before imageResolution, breaking callers
that pass imageResolution as the 4th positional argument; update the API to
preserve backward compatibility by either adding an overload that keeps the
original parameter order or by moving powerPin2 to the end of the parameter
list, and then update/verify all call sites of GeoluxHydroCam(...) (use the
provided ripgrep snippet) to ensure no legacy positional uses remain;
specifically adjust the GeoluxHydroCam(Stream*, ...) and GeoluxHydroCam(Stream&,
...) declarations to include a compatibility overload or reorder parameters so
imageResolution remains the 4th parameter.

In `@src/sensors/GroPointParent.cpp`:
- Around line 210-239: The current code returns
bumpMeasurementAttemptCount((success && successT)) to force retries when either
moisture or temperature fails; leave this AND logic as-is since
verifyAndAddMeasurementResult stores partial results independently and
clearValues() is run at the start of each update cycle, but if you want to treat
any partial data as a successful measurement instead, replace the return
expression with bumpMeasurementAttemptCount((success || successT)); reference
functions/vars: verifyAndAddMeasurementResult, bumpMeasurementAttemptCount,
clearValues, success, successT.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The retry loop in MaxBotixSonar (the while loop using
rangeAttempts and parseInt on _stream) can block far longer than allowed by
HRXL_MEASUREMENT_TIME_MS; change the loop to enforce a hard time budget instead
of a fixed 25-attempt limit—capture a start timestamp (e.g., millis()) before
the loop and, inside the loop, break/stop retries when elapsed >=
HRXL_MEASUREMENT_TIME_MS (or a configurable per-measurement timeout), so calls
to _stream->parseInt() and repeated triggering via _triggerPin cannot exceed the
measurement time; keep the existing success/result handling but replace the
fixed rangeAttempts-only condition with a combined attempts-and-time check.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 100-162: The rawSample assembly in the measurement parsing omits
D17 (bit 1 of res1), so the sign-extension path yields incorrect magnitudes;
change the rawSample construction (variable rawSample using res1/res2/res3) to
include both low two bits of res1 (e.g., use (res1 & 0x03) << 16 or combine
bitRead(res1,1) and bitRead(res1,0)) before OR-ing with res2 and res3 so the
later sign-extension (int32_t signedSample = rawSample | 0xFFFC0000) and the D17
check (bitRead(res1,1)) operate on the correct 18-bit value.

---

Nitpick comments:
In `@src/publishers/EnviroDIYPublisher.h`:
- Around line 8-33: Update the section comment that currently reads "Functions
for the EnviroDIY data portal receivers." to match the Monitor My Watershed
naming; change it to something like "Functions for the Monitor My Watershed data
portal receivers." Ensure the comment near the typedef for EnviroDIYPublisher
(typedef MonitorMyWatershedPublisher EnviroDIYPublisher) and the surrounding
block comment reflect the new branding for consistency.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 106-129: The comments above the setter functions are incorrect:
replace the misleading "Returns the data destination" comment on
MonitorMyWatershedPublisher::setHost, ::setPath, and ::setPort with concise
setter descriptions (e.g., "Sets the data destination host", "Sets the data
destination path/endpoint", "Sets the data destination port") that accurately
describe the action; ensure the getter methods
(MonitorMyWatershedPublisher::getPath, ::getPort) retain return-oriented
comments if needed.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-105: The voltage-range check for the SQ-212 uses ADS-derived
bounds (adcVoltage < 3.6 && adcVoltage > -0.3) and must be replaced with
validated sensor-specific bounds from the SQ-212 datasheet; update the check
around adcVoltage in the block that computes calibResult (uses
SQ212_CALIBRATION_FACTOR, SQ212_PAR_VAR_NUM, SQ212_VOLTAGE_VAR_NUM) to use
explicit constants (e.g., SQ212_MIN_VOLTAGE = 0.0 and SQ212_MAX_VOLTAGE = 0.4
for the raw sensor output, or 2.5 if an amplifier is present), remove the `@todo`,
and add a clear out-of-range log/early-return path when adcVoltage is outside
those constants so verifyAndAddMeasurementResult is only called for valid
voltages.

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 59-73: The current calculation in the else branch can divide by
zero when _loadResistor is 0.0 (see sensor_adc handling and the current_val
computation); add a guard before computing current_val and lux_val that checks
_loadResistor (and optionally PROCESSOR_ADC_MAX if desired) is non-zero/valid
and, if zero, set current_val and lux_val to 0.0 (or return/throw/log per
project convention) while still computing volt_val, and document/validate
_loadResistor in the EverlightALSPT19 constructor or configuration path so
invalid zero resistances are caught early.

In `@src/sensors/GroPointParent.cpp`:
- Around line 204-207: In the default branch of the switch in GroPointParent.cpp
(where MS_DBG(F("Other GroPoint models not yet implemented.")) is logged), set a
more visible error state so failures from unsupported models are obvious: call
setStatusBit(ERROR_OCCURRED) (or the project-specific error-bit setter used
elsewhere) and/or emit a higher-priority log (e.g., MS_LOG or MS_ERR) mentioning
the unsupported model, and ensure the local flags success and successT remain
false so the function returns failure; update the default case to use
setStatusBit(ERROR_OCCURRED) and a clear error log referencing the model
identifier.

In `@src/sensors/GroPointParent.h`:
- Around line 172-174: Add a short Doxygen stub above GroPointParent::wake to
preserve generated docs; for example, add a one-line comment like "/** `@copydoc`
BaseSensor::wake */" or a brief "@brief Wake sensor and prepare for reading"
depending on the real base method, so the declaration "bool wake(void)
override;" keeps API docs. Ensure the comment is placed immediately above the
wake declaration inside the GroPointParent class.

In `@src/sensors/MaxBotixSonar.h`:
- Around line 149-153: The comment says the HRXL takes 166ms but the macro
HRXL_MEASUREMENT_TIME_MS is 250; reconcile these by either setting
HRXL_MEASUREMENT_TIME_MS to 166 if you meant the raw sensor measurement time, or
update the comment above Sensor::_measurementTime_ms/HRXL_MEASUREMENT_TIME_MS to
explicitly state that 250ms was chosen to provide margin for stream timeout
alignment and up to 25 retries (or other retry/timeout rationale) so the
documentation matches the macro value.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 94-99: The adcVoltage bounds check in TIADS1x15.cpp uses magic
numbers (3.6 and -0.3); replace them with clearly named constants (e.g.,
ADS_INPUT_V_MAX and ADS_INPUT_V_MIN or ADC_VOLTAGE_VALID_MAX_V /
ADC_VOLTAGE_VALID_MIN_V) declared near the top of the file or as class-level
static constexpr inside the TIADS1x15 class, then use those constants in the if
(adcVoltage < ... && adcVoltage > ...) check and update the inline comment to
reference the constant names and that they correspond to the ADS input range at
3.3V supply; keep the existing calibResult = adcVoltage * _gain and MS_DBG call
unchanged.

In `@src/VariableArray.cpp`:
- Around line 299-301: sensorList is declared as a variable-length array
(Sensor* sensorList[_sensorCount]) which is non-standard; replace it with a
fixed-size array using MAX_NUMBER_VARS and validate _sensorCount before use:
declare Sensor* sensorList[MAX_NUMBER_VARS]; ensure you do not iterate or add
more than MAX_NUMBER_VARS by clamping or returning early if _sensorCount >
MAX_NUMBER_VARS, update any loops that use addedSensors to respect
MAX_NUMBER_VARS, and mirror bounds-checking patterns used for sensorValues and
variables so no out-of-bounds writes occur.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/WatchDogs/WatchDogSAMD.h (1)

148-155: ⚠️ Potential issue | 🟡 Minor

Trailing + characters in doxygen comments.

Lines 149 and 153 have errant + characters at the end of the @brief descriptions ("finish.+"). These should be removed.

✏️ Proposed fix
     /**
-     * `@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();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/WatchDogs/WatchDogSAMD.h` around lines 148 - 155, Remove the stray '+'
characters at the end of the doxygen `@brief` descriptions for the two functions:
update the comment lines above waitForWDTBitSync() and waitForGCLKBitSync() so
they end with "finish." (or simply "finish") instead of "finish.+", ensuring
both doxygen briefs are clean and consistent.
🧹 Nitpick comments (7)
src/sensors/AlphasenseCO2.cpp (1)

119-133: Consider logging out-of-range voltage to aid field diagnostics.

When the voltage is out of the expected range, the function now silently returns failure. A brief debug log (including the measured voltage) would make troubleshooting much easier without changing behavior.

📝 Suggested tweak
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // Convert voltage to current (mA) - assuming a 250 Ohm resistor is in
         // series
         co2Current = (adcVoltage / 250) * 1000;
@@
         verifyAndAddMeasurementResult(ALPHASENSE_CO2_VOLTAGE_VAR_NUM,
                                       adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  ADC voltage out of range:"), String(adcVoltage, 3));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/AlphasenseCO2.cpp` around lines 119 - 133, The ADC voltage check
block in AlphasenseCO2.cpp silently skips out-of-range readings—add a debug log
using the existing logger (e.g., MS_DBG or the project's debug macro) just
before the function returns when adcVoltage is not in the valid range (the if
handling adcVoltage < 3.6 && adcVoltage > -0.3), printing a short message with
the measured adcVoltage and that it is out of range; do not change the success
flag or measurement flow, only add the log for diagnostics.
src/VariableBase.h (1)

341-353: Consider correcting adjacent docstrings.

The docstrings for getVarUUIDString() (lines 342-345) and getVarUUID() (lines 349-352) incorrectly state "Get the customized code for the variable" when they should reference UUID retrieval. Since this PR is already improving UUID documentation, these would be good candidates for correction.

📝 Suggested docstring corrections
     /**
-     * `@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);
     // 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
      *
-     * `@return` The customized code for the variable
+     * `@return` The variable's UUID as a C-string
      */
     const char* getVarUUID(void);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/VariableBase.h` around lines 341 - 353, The docstrings for
getVarUUIDString() and getVarUUID() incorrectly say "Get the customized code for
the variable"; update both comments to accurately describe UUID retrieval—e.g.,
change the brief/return descriptions for getVarUUIDString() to state it returns
the variable's UUID as a String and for getVarUUID() to state it returns the
variable's UUID as a C-style const char* (or nullptr if not assigned), keeping
the function names getVarUUIDString and getVarUUID to locate the correct
declarations.
examples/ReadMe.md (1)

167-167: Optional: Consider simpler wording.

The phrase "prior to 2020" could be simplified to "before 2020" for slightly more concise and conversational documentation.

📝 Optional simplification
-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.
+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 to archival and reference purposes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ReadMe.md` at line 167, Update the sentence in the README that
describes the 2G DRWI Citizen Science example: replace the phrase "prior to
2020" with the simpler "before 2020" in the line mentioning "The 2G DRWI Citizen
Science example" so it reads more concise and conversational while preserving
the rest of the archival warning about 2G GPRSbee boards no longer functioning
in the USA.
docs/Further-Reading/Sleep-Configurations.md (1)

189-190: Typo fix looks good; consider clarifying line 190.

The correction of "tri-strate" to "tri-state" on line 189 is accurate. However, line 190 contains confusing double-negative wording: "No pin modes are not changed." Consider simplifying to either "Pin modes are not changed when..." or "No pin modes are changed when..." for better clarity.

📝 Proposed clarification
 > You can re-enable pin tri-state by calling `Logger::disablePinTristate(false)`.
-> No pin modes are **not** changed when the `disablePinTristate()` function is called, only when the `systemSleep()` function is called.
+> Pin modes are **not** changed when the `disablePinTristate()` function is called, only when the `systemSleep()` function is called.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/Further-Reading/Sleep-Configurations.md` around lines 189 - 190, Change
the confusing double-negative sentence on line referencing the
disablePinTristate/systemSleep behavior: replace "No pin modes are **not**
changed when the `disablePinTristate()` function is called, only when the
`systemSleep()` function is called." with a clear, positive phrasing such as
"Pin modes are not changed when `disablePinTristate()` is called; they are only
changed when `systemSleep()` is called." Reference the existing identifiers
Logger::disablePinTristate(), disablePinTristate(), and systemSleep() to ensure
the meaning remains precise.
src/sensors/TIADS1x15.cpp (1)

94-101: Make ADC range bounds self‑documenting and configurable.

The hardcoded -0.3 / 3.6 bounds are magic numbers; extracting them to named constants (or deriving from supply voltage) would improve expressiveness and align with the TODO.

Do you want me to draft a small patch (or open an issue) to parameterize these bounds?

♻️ Suggested refactor
-    // `@todo` Verify the range based on the actual power supplied to the ADS.
-    // Here we are using the range of the ADS when it is powered at 3.3V
-    if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+    // `@todo` Verify the range based on the actual power supplied to the ADS.
+    // Here we are using the range of the ADS when it is powered at 3.3V
+    constexpr float kMinAdcVoltage = -0.3f;
+    constexpr float kMaxAdcVoltage =  3.6f;
+    if (adcVoltage > kMinAdcVoltage && adcVoltage < kMaxAdcVoltage) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIADS1x15.cpp` around lines 94 - 101, The ADC input range check
in TIADS1x15.cpp currently uses magic numbers (-0.3 and 3.6) around adcVoltage;
replace these with named, configurable symbols (e.g., kAdcMinVoltage and
kAdcMaxVoltage) or compute them from the ADC/supply (Vref or _vSupply) and
document them next to the declaration; update the condition that guards
calibResult = adcVoltage * _gain and the surrounding logic (including MS_DBG and
verifyAndAddMeasurementResult calls that use calibResult and TIADS1X15_VAR_NUM)
to use the new constants so the bounds are self‑documenting and can be
overridden or derived at runtime.
docs/For-Developers/Developer-Setup.md (1)

21-45: Consider adding Unix/Linux alternative for Git filter setup.

The manual setup commands use PowerShell scripts, which may not be available on Linux/macOS development environments. Consider adding bash-based filter alternatives for cross-platform support.

✏️ Optional: Add Unix/Linux alternative
 **Manual Setup:**
 If you prefer to configure the filters manually, run these Git commands from the repository root:
 
+**For Unix/Linux/macOS:**
+```bash
+git config --local filter.smudgePasswords.clean "bash filters/cleanPasswords.sh"
+git config --local filter.smudgePasswords.smudge "bash filters/smudgePasswords.sh"
+git config --local filter.disableDebug.clean "bash filters/cleanDebugConfig.sh"
+git config --local filter.disableDebug.smudge "bash filters/smudgeDebugConfig.sh"
+```
+
+**For Windows:**
 ```bash
 git config --local filter.smudgePasswords.clean "powershell -ExecutionPolicy Bypass -File filters/cleanPasswords.ps1"

Note: This would require creating corresponding bash scripts in the filters/ directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/For-Developers/Developer-Setup.md` around lines 21 - 45, Add
cross-platform Git filter instructions by including bash-based alternatives
alongside the PowerShell commands in the "Git Filter Setup" section; add git
config examples that use "bash filters/cleanPasswords.sh", "bash
filters/smudgePasswords.sh", "bash filters/cleanDebugConfig.sh", and "bash
filters/smudgeDebugConfig.sh" and mention that corresponding shell scripts
(filters/cleanPasswords.sh, filters/smudgePasswords.sh,
filters/cleanDebugConfig.sh, filters/smudgeDebugConfig.sh) must be added to the
filters/ directory and made executable for Unix/macOS users.
src/publishers/MonitorMyWatershedPublisher.h (1)

201-208: Document registration token lifetime like host/path.
setToken stores a raw pointer; without a warning, it’s easy to pass a temporary C-string and end up with a dangling token at publish time. Consider mirroring the setHost/setPath warning language.

✏️ Suggested doc tweak
     /**
      * `@brief` Set the site registration token
      *
+     * `@warning` The caller must guarantee that the passed C-string remains
+     * valid for the lifetime of this object or until another call to setToken().
+     * Do not pass temporary strings or stack-allocated C-strings that may
+     * be destroyed before all publish calls are complete.
+     *
      * `@param` registrationToken The registration token for the site on Monitor
      * My Watershed.
      */
     void setToken(const char* registrationToken);
As per coding guidelines `**/*.h`: Review the C++ code, point out issues relative to principles of clean code, expressiveness, and performance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.h` around lines 201 - 208, The
setToken(const char* registrationToken) declaration stores a raw pointer which
can dangle if callers pass a temporary C-string; update the API or docs: either
change setToken to accept and store a std::string (or const std::string& and
make an internal copy) on the MonitorMyWatershedPublisher class, or at minimum
expand the comment for setToken to mirror setHost/setPath and explicitly warn
that the caller must ensure the C-string outlives the publisher (i.e., document
ownership/lifetime), and adjust internal members (token field) accordingly to
hold an owning std::string if you choose the safer API change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Around line 54-55: Combine the two bullet lines into a single continuous
bullet by indenting the second sentence so it remains part of the same list
item, and normalize the identifier casing to "LoggerID" (replace occurrences of
"loggerID" and "YourLoggerID" with "LoggerID"/"YourLoggerID" as appropriate) to
keep the bullet list consistent and the identifier clear.
- Around line 65-66: Indent the continuation line so it stays part of the bullet
list: in ReadMe.md adjust the second line of the bullet (the line starting "Use
numbers from the side...") to be indented to the same column as the bullet text
(e.g., add two spaces or the same number of spaces used after the dash in the
preceding line) so the continuation remains inside the list item.

In `@examples/OutdatedExamples/simple_logging_LearnEnviroDIY/ReadMe.md`:
- Line 9: Rephrase the awkward usage sentence in ReadMe.md to separate
capability vs. preference and remove the informal contraction; replace the
current sentence with a clear alternative such as: "Use this example to deploy a
logger when you cannot stream live data or do not want to upload data to Monitor
My Watershed" or "This example is for deploying a logger when you only need
local SD card storage without live data streaming or uploading to Monitor My
Watershed" so the intent is unambiguous and grammatically correct.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 132-145: rawSample construction only included D16 and omitted D17
from res1, so negative values are wrong; extract both low two bits from res1
(res1 & 0x03) and assemble the full 18‑bit sample as ((res1 & 0x03) << 16) |
(res2 << 8) | res3, then test the sign bit on the assembled rawSample (bit 17)
to decide sign extension: if bit 17 set, sign‑extend (e.g., signedSample =
rawSample | 0xFFFC0000) and multiply signedSample by 0.015625, else multiply
rawSample by 0.015625; update uses of rawSample, res1, res2, res3, and
signedSample accordingly.

---

Outside diff comments:
In `@src/WatchDogs/WatchDogSAMD.h`:
- Around line 148-155: Remove the stray '+' characters at the end of the doxygen
`@brief` descriptions for the two functions: update the comment lines above
waitForWDTBitSync() and waitForGCLKBitSync() so they end with "finish." (or
simply "finish") instead of "finish.+", ensuring both doxygen briefs are clean
and consistent.

---

Duplicate comments:
In `@ChangeLog.md`:
- Around line 75-80: The bullets in ChangeLog.md repeatedly start with
"Removed", hurting readability; edit the listed bullets to combine related
removals and vary phrasing so they read as a concise changelog—for example,
merge the `_maxSamplesToAverage` and `countMaxToAverage()` removal into one line
referencing both symbols, group the `powerUp`/`powerDown` overrides removal into
a single entry, shorten the note about doc call removals, and rephrase the
TinyGSM/MQTT/ModSensorConfig.h/One Definition Rule warning into a single
explanatory sentence mentioning TinyGSM, MQTT, and ModSensorConfig.h to avoid
duplication and improve flow.

In `@examples/double_logger/ReadMe.md`:
- Around line 49-53: Update the README text to consistently use "logger ID"
(with a space) in the explanatory sentence and the code comment; ensure the code
snippet remains unchanged (const char *LoggerID = "YourLoggerID";) but update
the preceding instruction to say "Change the text `YourLoggerID` in this section
of code to your logger ID or serial number" so the phrasing matches the heading
and comment referencing LoggerID.

In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 481-483: The call to modem.gsmModem.forceModemBaud(...) is
invalid; replace it with a supported TinyGSM approach: either perform an
auto-bauding step using TinyGsmAutoBaud(modemSerial, min, max) before
initializing the modem, or, after AT sync, call
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) (or the equivalent
setBaud on your modem wrapper) to set the baud; use the existing symbols
modemSerial, modemBaud and modem.gsmModem to locate the call and update it to
one of these supported APIs.
- Around line 496-498: The call to modem.gsmModem.forceModemBaud(...) is invalid
for the TinyGSM cellular setup; remove that call and configure the modem baud
using the supported TinyGSM/Stream methods instead (e.g., initialize the serial
stream with modemSerial.begin(...) or use the TinyGSM modem instance's baud
configuration method such as setBaud if available), referencing modem.gsmModem,
modemSerial, and modemBaud to locate and replace the erroneous forceModemBaud
invocation.

In `@examples/examples.dox`:
- Around line 67-77: Update the broken Doxygen cross-reference: replace the
outdated reference token `@ref examples_outdated` with the renamed anchor `@ref
page_examples_outdated` so the link matches the `@page page_examples_outdated`
anchor; locate the `@ref examples_outdated` occurrence (between the `@page
page_examples_outdated` and `@subpage example_learn_envirodiy` entries) and
change it to `@ref page_examples_outdated`.

In `@src/LoggerBase.h`:
- Around line 235-256: The getter getInitialShortIntervals currently returns
_remainingShortIntervals (wrong state); change it to return the initial value
member (e.g., _initialShortIntervals) and keep the legacy wrappers
setinitialShortIntervals and getinitialShortIntervals delegating to
setInitialShortIntervals and getInitialShortIntervals respectively; update any
tests or usages that assume the getter returns the initial configured interval
value.

In `@src/LoggerModem.h`:
- Around line 991-992: The doc comment in LoggerModem.h for the modem reset pin
contains a typo: change the phrase "attached the hard or panic reset pin of the
modem" to "attached to the hard or panic reset pin of the modem" in the comment
block that documents the digital pin number of the MCU hard/panic reset pin (the
brief description above the modem reset pin member).

In `@src/modems/SIMComSIM7000.h`:
- Line 77: The new include and type TinyGsmClientSIM7000SSL.h /
TinyGsmSim7000SSL may not exist in the TinyGSM version used; verify the project
dependency uses the EnviroDIY fork (which provides custom TinyGSM headers) or
else revert to the generic TinyGSM client pattern. If the fork is not
guaranteed, change references to use the standard TinyGsmClient (and underlying
modem class, e.g., TinyGsm) or add conditional compilation: detect the presence
of TinyGsmClientSIM7000SSL.h (or a build-flag) and include/use
TinyGsmClientSIM7000SSL / TinyGsmSim7000SSL only when available, otherwise fall
back to TinyGsmClient / TinyGsmSim7000 to ensure portability and successful
compilation.

In `@src/publishers/UbidotsPublisher.cpp`:
- Around line 109-120: The review approves the comment and error message update
in UbidotsPublisher::publishData; no code changes are required—just ensure the
log message in UbidotsPublisher::publishData still checks
_baseLogger->getSamplingFeatureUUID() and prints the corrected punctuated
message ("A sampling feature UUID must be set before publishing data to
Ubidots!") and that the file comment references Ubidots consistently.

In `@src/sensors/AnalogElecConductivity.cpp`:
- Around line 91-95: The code currently marks every EC measurement as successful
regardless of value; change the logic in the read path around
verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, sensorEC_uScm)
so you validate sensorEC_uScm first (e.g., reject the sentinel -9999 and
out-of-range or NaN values) and only call verifyAndAddMeasurementResult and
return bumpMeasurementAttemptCount(true) when the value is valid; otherwise do
not add the measurement and return bumpMeasurementAttemptCount(false) so retries
can occur.

In `@src/sensors/GeoluxHydroCam.cpp`:
- Around line 350-355: minTime is incorrectly set to 0 when _alwaysAutoFocus is
false, allowing readiness checks to run immediately; change the ternary so that
when _alwaysAutoFocus is false minTime uses _stabilizationTime_ms (not 0L), and
ensure maxTime remains calculated as before using HYDROCAM_AUTOFOCUS_TIME_MAX +
HYDROCAM_STABILIZATION_TIME_MAX when _alwaysAutoFocus is true otherwise
HYDROCAM_STABILIZATION_TIME_MAX, updating the expressions that initialize
minTime and maxTime (refer to minTime, maxTime, _alwaysAutoFocus,
_stabilizationTime_ms, HYDROCAM_AUTOFOCUS_TIME_MS, HYDROCAM_AUTOFOCUS_TIME_MAX,
HYDROCAM_STABILIZATION_TIME_MAX).

In `@src/sensors/KnownProcessors.h`:
- Around line 259-288: No change required—the fallback macro definitions for
LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER in
KnownProcessors.h already provide sensible defaults and informative pragma
warnings; keep the existing `#ifndef/`#define blocks and pragma message text
surrounding LOGGER_BOARD, OPERATING_VOLTAGE, BATTERY_PIN, and BATTERY_MULTIPLIER
as-is.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-196: The retry loop currently uses a fixed rangeAttempts cap
which with the existing _stream timeout can block for seconds; replace the
attempts-count guard with a time‑budget guard by capturing a start timestamp
(e.g. unsigned long startMs = millis()) before the while and change the
condition to while(!success && (millis() - startMs) < HRXL_MEASUREMENT_TIME_MS)
(or another configured per‑sensor budget constant), keeping the existing trigger
logic (_triggerPin, digitalWrite), read via _stream->parseInt(), and validation
against _maxRange/_convertCm; ensure you still call
verifyAndAddMeasurementResult(HRXL_VAR_NUM, result) on success and return
bumpMeasurementAttemptCount(success) at the end.

In `@src/VariableArray.cpp`:
- Around line 383-407: The local strings sName and cycCount are only declared
under MS_VARIABLEARRAY_DEBUG / MS_VARIABLEARRAY_DEBUG_DEEP but are referenced by
an unconditional MS_DBG call, which can break compilation if MS_DBG is enabled
without those flags; fix this by either moving the declarations of sName and
cycCount out of the `#if` so they are always defined (e.g., declare and initialize
them unconditionally using sensorList[i]->getSensorNameAndLocation() and the
cycle count expression) or by wrapping the MS_DBG call with the same
MS_VARIABLEARRAY_DEBUG / MS_VARIABLEARRAY_DEBUG_DEEP guards; update the code
around sensorList[i], getSensorNameAndLocation(),
getNumberCompleteMeasurementsAttempts(), getNumberRetryAttemptsMade(), and the
MS_DBG invocation to ensure consistent guarding.

---

Nitpick comments:
In `@docs/For-Developers/Developer-Setup.md`:
- Around line 21-45: Add cross-platform Git filter instructions by including
bash-based alternatives alongside the PowerShell commands in the "Git Filter
Setup" section; add git config examples that use "bash
filters/cleanPasswords.sh", "bash filters/smudgePasswords.sh", "bash
filters/cleanDebugConfig.sh", and "bash filters/smudgeDebugConfig.sh" and
mention that corresponding shell scripts (filters/cleanPasswords.sh,
filters/smudgePasswords.sh, filters/cleanDebugConfig.sh,
filters/smudgeDebugConfig.sh) must be added to the filters/ directory and made
executable for Unix/macOS users.

In `@docs/Further-Reading/Sleep-Configurations.md`:
- Around line 189-190: Change the confusing double-negative sentence on line
referencing the disablePinTristate/systemSleep behavior: replace "No pin modes
are **not** changed when the `disablePinTristate()` function is called, only
when the `systemSleep()` function is called." with a clear, positive phrasing
such as "Pin modes are not changed when `disablePinTristate()` is called; they
are only changed when `systemSleep()` is called." Reference the existing
identifiers Logger::disablePinTristate(), disablePinTristate(), and
systemSleep() to ensure the meaning remains precise.

In `@examples/ReadMe.md`:
- Line 167: Update the sentence in the README that describes the 2G DRWI Citizen
Science example: replace the phrase "prior to 2020" with the simpler "before
2020" in the line mentioning "The 2G DRWI Citizen Science example" so it reads
more concise and conversational while preserving the rest of the archival
warning about 2G GPRSbee boards no longer functioning in the USA.

In `@src/publishers/MonitorMyWatershedPublisher.h`:
- Around line 201-208: The setToken(const char* registrationToken) declaration
stores a raw pointer which can dangle if callers pass a temporary C-string;
update the API or docs: either change setToken to accept and store a std::string
(or const std::string& and make an internal copy) on the
MonitorMyWatershedPublisher class, or at minimum expand the comment for setToken
to mirror setHost/setPath and explicitly warn that the caller must ensure the
C-string outlives the publisher (i.e., document ownership/lifetime), and adjust
internal members (token field) accordingly to hold an owning std::string if you
choose the safer API change.

In `@src/sensors/AlphasenseCO2.cpp`:
- Around line 119-133: The ADC voltage check block in AlphasenseCO2.cpp silently
skips out-of-range readings—add a debug log using the existing logger (e.g.,
MS_DBG or the project's debug macro) just before the function returns when
adcVoltage is not in the valid range (the if handling adcVoltage < 3.6 &&
adcVoltage > -0.3), printing a short message with the measured adcVoltage and
that it is out of range; do not change the success flag or measurement flow,
only add the log for diagnostics.

In `@src/sensors/TIADS1x15.cpp`:
- Around line 94-101: The ADC input range check in TIADS1x15.cpp currently uses
magic numbers (-0.3 and 3.6) around adcVoltage; replace these with named,
configurable symbols (e.g., kAdcMinVoltage and kAdcMaxVoltage) or compute them
from the ADC/supply (Vref or _vSupply) and document them next to the
declaration; update the condition that guards calibResult = adcVoltage * _gain
and the surrounding logic (including MS_DBG and verifyAndAddMeasurementResult
calls that use calibResult and TIADS1X15_VAR_NUM) to use the new constants so
the bounds are self‑documenting and can be overridden or derived at runtime.

In `@src/VariableBase.h`:
- Around line 341-353: The docstrings for getVarUUIDString() and getVarUUID()
incorrectly say "Get the customized code for the variable"; update both comments
to accurately describe UUID retrieval—e.g., change the brief/return descriptions
for getVarUUIDString() to state it returns the variable's UUID as a String and
for getVarUUID() to state it returns the variable's UUID as a C-style const
char* (or nullptr if not assigned), keeping the function names getVarUUIDString
and getVarUUID to locate the correct declarations.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/sensors/MaximDS3231.cpp (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Documentation error: incorrect class name in @brief.

The @brief comment references MaximDS18 but this file implements MaximDS3231.

📝 Suggested fix
- * `@brief` Implements the MaximDS18 class.
+ * `@brief` Implements the MaximDS3231 class.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaximDS3231.cpp` at line 8, The file header's `@brief` incorrectly
names the class as "MaximDS18"; update the documentation to reference the
correct class "MaximDS3231" (or a neutral description like "Implements the
MaximDS3231 class") so the comment matches the implemented class MaximDS3231 in
this file; locate the `@brief` line near the top of src/sensors/MaximDS3231.cpp
and replace "MaximDS18" with "MaximDS3231".
src/modems/DigiXBeeWifi.cpp (1)

221-227: ⚠️ Potential issue | 🟠 Major

Copy-paste error: Wrong AT command for RSSI PWM setting.

Line 221 checks GF("D5") but should check GF("P0") based on the comment about enabling RSSI PWM output on DIO10/PWM0. The AT command "D5" was already used on line 208 for Associate/DIO5.

🐛 Proposed fix
         /** Enable RSSI PWM output on `DIO10` - this should be directly
          * attached to an LED if possible.  A higher PWM duty cycle (and
          * 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;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/DigiXBeeWifi.cpp` around lines 221 - 227, The change uses the
wrong AT parameter string: in the call to gsmModem.changeSettingIfNeeded that
sets up RSSI PWM for DIO10/PWM0 you currently pass GF("D5") (duplicate of the
DIO5/associate setting) — update that call to use GF("P0") so the code
checks/changes the correct AT command; keep the surrounding variable (changedP0)
and the logging (MS_DBG/MS_DEEP_DBG for DIO10/PWM0) intact.
src/sensors/ANBpH.h (1)

478-510: ⚠️ Potential issue | 🟠 Major

Constructor lacks validation; bounds are only enforced via debug warnings in setup().

The loggingIntervalMinutes parameter is inserted between powerPin and powerPin2, creating a backward-compatibility hazard. While bounds validation exists in the setup() method (lines 153–168 of ANBpH.cpp), it only logs MS_DBG warnings and silently corrects out-of-range values rather than failing fast. This deferred, weak validation means invalid values pass through construction undetected, making the constructor fragile to misuse.

Consider adding explicit bounds validation in the constructor to catch invalid values immediately and fail loudly, rejecting configurations that violate the documented constraints (10–240 minutes when power is cycled; 0 only when always powered).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ANBpH.h` around lines 478 - 510, The ANBpH constructors
(ANBpH(byte, Stream*, int8_t, int16_t, int8_t, int8_t, uint8_t) and ANBpH(byte,
Stream&, ...)) must validate loggingIntervalMinutes at construction instead of
relying on setup(); add checks that if the sensor power is being cycled
(powerPin != -1 or powerPin2 != -1) then loggingIntervalMinutes is between 10
and 240, and if the sensor is always powered (both powerPin and powerPin2 == -1)
then loggingIntervalMinutes may be 0 — otherwise reject the call by throwing
std::invalid_argument (or setting an explicit error/invalid state) with a clear
message; keep the same validation logic in both constructors and remove or
adjust the silent correction in setup() to avoid double-handling.
src/publishers/MonitorMyWatershedPublisher.cpp (2)

296-298: ⚠️ Potential issue | 🟡 Minor

Initial transmission counter decremented even when record creation fails.

_initialTransmissionsRemaining is decremented unconditionally, but if addRecord() fails (returns < 0), no record was actually added. This could cause the initial transmission count to be exhausted by failed record creations.

🔧 Proposed fix
+    if (record >= 0 && _initialTransmissionsRemaining > 0) {
-    if (_initialTransmissionsRemaining > 0) {
         _initialTransmissionsRemaining -= 1;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 296 - 298, The
code currently decrements _initialTransmissionsRemaining even when addRecord()
fails; change the logic so you call addRecord(...), capture its return value
(e.g., int rc = addRecord(...)) and only decrement
_initialTransmissionsRemaining when rc >= 0 (indicating success); update the
block in MonitorMyWatershedPublisher (the function containing the addRecord()
call and the _initialTransmissionsRemaining decrement) so failures do not
consume an initial transmission.

379-399: ⚠️ Potential issue | 🟡 Minor

Malformed JSON when variables count is zero.

If variables == 0, the for loop on line 379 never executes, leaving the JSON without a closing brace. The output would end with a trailing comma: "timestamp":["..."], instead of the properly closed "timestamp":["..."],}.

🔧 Proposed fix
         uint8_t variables = _logBuffer.getNumVariables();
+        if (variables == 0) {
+            // No variable data, close JSON immediately
+            txBufferAppend('}');
+        }
         for (uint8_t var = 0; var < variables; var++) {

Alternatively, add a guard at the start of flushDataBuffer to return early if there are no variables to send, similar to the records check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 379 - 399, The
JSON builder in flushDataBuffer fails when variables == 0 because the for loop
over variable index never runs and the closing '}' is never appended (leaving a
trailing comma after the timestamp); fix by adding a guard at the start of
flushDataBuffer to return early when variables == 0 (mirroring the records
check) or, if you prefer to always produce a JSON object, ensure that when
variables == 0 you append the closing brace (use txBufferAppend('}') or
equivalent) before returning; locate the logic in flushDataBuffer around the for
(uint8_t var = 0; var < variables; var++) loop and update it to handle
variables==0.
🧹 Nitpick comments (13)
examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md (1)

105-105: Add a comma to fix the run-on sentence.

✏️ Proposed fix
-- 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.
+- 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.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md` at line 105,
The sentence "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." is a run-on; insert a comma
after "code" (or after "code" phrase) to read: "After you are completely certain
that you have the order right in the variable section of your code, use the teal
"Copy" button..." so update the ReadMe sentence accordingly.
src/sensors/SensirionSHT4x.h (1)

277-277: Consider adding brief documentation for the override.

The addSingleMeasurementResult method declaration lacks explicit documentation after the copydoc removal. While the base class documentation applies via override, adding a brief @copydoc Sensor::addSingleMeasurementResult() (like other overridden methods in this class such as sleep() on line 280-285) would maintain consistency.

Suggested addition for consistency
+    /**
+     * `@copydoc` Sensor::addSingleMeasurementResult()
+     */
     bool addSingleMeasurementResult(void) override;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/SensirionSHT4x.h` at line 277, Add a brief copydoc comment above
the overridden declaration bool addSingleMeasurementResult(void) override; so it
matches the class's style (e.g., add "@copydoc
Sensor::addSingleMeasurementResult()" similar to the existing documentation on
sleep()). This keeps override documentation consistent and ensures the base
method docs are clearly referenced for addSingleMeasurementResult.
src/sensors/CampbellOBS3.cpp (1)

100-113: Consider addressing the TODO for voltage range verification.

The range check (adcVoltage < 3.6 && adcVoltage > -0.3) has a TODO noting uncertainty about whether this is correct for the OBS3 sensor. The current bounds are based on the ADS at 3.3V, but the OBS3 datasheet specifies a 0-2.5V output range.

The implementation correctly gates calibration and result submission on valid voltage, and the success flag pattern ensures proper retry behavior when out-of-range readings occur.

Would you like me to open an issue to track verifying the correct voltage range bounds against the OBS3 sensor specifications?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/CampbellOBS3.cpp` around lines 100 - 113, The voltage range guard
for OBS3 uses ADS bounds (adcVoltage < 3.6 && adcVoltage > -0.3) but the OBS3
datasheet specifies 0–2.5V; update the check around adcVoltage to enforce 0.0 <=
adcVoltage <= 2.5 so calibration (calibResult computed from _x2_coeff_A,
_x1_coeff_B, _x0_coeff_C) and result submission via
verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, ...) and
verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, ...) only occur for valid
OBS3 voltages, and optionally add a debug/log when a reading is out of range
before returning bumpMeasurementAttemptCount(success).
src/sensors/TIINA219.h (1)

452-453: cSpell directive placed after header guard.

The cSpell:ignore directive is positioned after #endif. While this works for spell-checking tools that scan the entire file, placing it just inside the header guard (before #endif) would be more conventional for C++ headers. This is a minor style consideration and not a blocking issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TIINA219.h` around lines 452 - 453, The cSpell directive is
currently placed after the header guard's closing `#endif`; move the comment
line `// cSpell:ignore TIINA219` so it sits inside the header guard scope (just
before the `#endif` that closes the guard in TIINA219.h) to follow conventional
header style while keeping the same directive text.
src/sensors/TurnerCyclops.cpp (1)

105-115: Consider extracting voltage bounds as named constants.

The TODO at line 105 acknowledges the need to verify the voltage range. When addressing this, consider defining the bounds (3.6V max, -0.3V min) as named constants in the header for clarity and easier maintenance.

💡 Optional improvement
+// In TurnerCyclops.h or at file scope:
+constexpr float CYCLOPS_ADC_VOLTAGE_MAX = 3.6f;   // ADS1x15 at 3.3V supply
+constexpr float CYCLOPS_ADC_VOLTAGE_MIN = -0.3f;

 // In the measurement function:
-if (adcVoltage < 3.6 && adcVoltage > -0.3) {
+if (adcVoltage < CYCLOPS_ADC_VOLTAGE_MAX && adcVoltage > CYCLOPS_ADC_VOLTAGE_MIN) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/TurnerCyclops.cpp` around lines 105 - 115, The magic numbers for
voltage bounds in the TurnerCyclops sensor (the if condition using 3.6 and -0.3
with adcVoltage) should be replaced with named constants declared in the
TurnerCyclops header; define e.g. kCyclopsMaxVoltage and kCyclopsMinVoltage (or
similarly descriptive names) and use them in the check around adcVoltage,
keeping the existing calibration logic that computes calibResult from
_conc_std/_volt_std/_volt_blank and calls
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage); update any
comments/TODO to reference the constants for easier future adjustment and
verification.
src/modems/SIMComSIM7080.cpp (1)

51-58: Duplicate AT command +CNETLIGHT=1 should be removed or corrected.

Lines 51 and 57 both send the identical command +CNETLIGHT=1. The second instance has a comment describing GPRS-specific behavior (64ms on/300ms off blinking), but the command is unchanged from the first call. Either remove the duplicate or update the second command with the appropriate parameter to match its documented intent.

♻️ Suggested fix: Remove duplicate command
     // Enable the netlight indicator
     gsmModem.sendAT(F("+CNETLIGHT=1"));
     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.
-    gsmModem.sendAT(F("+CNETLIGHT=1"));
-    gsmModem.waitResponse();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/SIMComSIM7080.cpp` around lines 51 - 58, The code sends the AT
command "+CNETLIGHT=1" twice via gsmModem.sendAT(...) with corresponding
gsmModem.waitResponse() calls; remove the duplicate to avoid redundant commands
(either delete the second gsmModem.sendAT(F("+CNETLIGHT=1")) and its
gsmModem.waitResponse() or replace the second call with the intended parameter
if the comment meant a different setting), locating the calls to gsmModem.sendAT
and gsmModem.waitResponse in SIMComSIM7080.cpp to make the change.
src/sensors/ApogeeSQ212.cpp (1)

96-105: Consider adding diagnostic logging for out-of-range voltage.

When adcVoltage falls outside the valid range, the measurement silently fails. Adding a debug log for the out-of-range condition would improve field troubleshooting.

💡 Suggested diagnostic improvement
     // `@todo` Verify the voltage range for the SQ-212 sensor
     // Here we are using the range of the ADS when it is powered at 3.3V
     if (adcVoltage < 3.6 && adcVoltage > -0.3) {
         // 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);
         verifyAndAddMeasurementResult(SQ212_PAR_VAR_NUM, calibResult);
         verifyAndAddMeasurementResult(SQ212_VOLTAGE_VAR_NUM, adcVoltage);
         success = true;
+    } else {
+        MS_DBG(F("  Voltage out of range:"), adcVoltage);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/ApogeeSQ212.cpp` around lines 96 - 105, The code currently
ignores out-of-range adcVoltage silently; add a diagnostic debug log in the else
branch after the existing if (adcVoltage < 3.6 && adcVoltage > -0.3) block that
logs the sensor id/name (SQ-212), the actual adcVoltage value and the expected
bounds (e.g. -0.3..3.6) so field troubleshooting can see why calibResult and
verifyAndAddMeasurementResult (SQ212_PAR_VAR_NUM, SQ212_VOLTAGE_VAR_NUM) were
not produced; use the same logging macro (MS_DBG or similar) used above and
leave success as false.
src/modems/DigiXBee3GBypass.h (1)

135-138: Add override specifiers to deleteClient and deleteSecureClient for consistency and safety.

Lines 136 and 138 override pure virtual methods from the loggerModem base class but lack the override keyword, while their corresponding create* methods include it. This inconsistency can mask bugs if the base class signature changes.

♻️ Proposed fix to add override specifiers
     virtual Client* createClient() override;
-    virtual void    deleteClient(Client* client);
+    virtual void    deleteClient(Client* client) override;
     virtual Client* createSecureClient() override;
-    virtual void    deleteSecureClient(Client* client);
+    virtual void    deleteSecureClient(Client* client) override;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modems/DigiXBee3GBypass.h` around lines 135 - 138, Add the missing
override specifiers to the deleteClient and deleteSecureClient declarations in
the DigiXBee3GBypass class so they match the base-class overrides; update the
declarations for deleteClient(Client* client) and deleteSecureClient(Client*
client) to include the override keyword (matching the already-overridden
createClient and createSecureClient signatures) and verify the parameter types
exactly match the loggerModem base class signatures.
src/sensors/MaximDS18.cpp (1)

183-209: Replace magic sentinel temps with named constants and fix Unicode portability issue.

  • DallasTemperature library defines DEVICE_DISCONNECTED_C (-127) as the formal constant for sensor disconnection; use it instead of the magic literal.
  • 85°C is the DS18B20 power-on reset (POR) register value returned when a conversion hasn't completed yet. It's distinct from the disconnect case, so either create a named constant for it or add a clearer comment explaining the distinction.
  • F("°C") embeds non-ASCII; replace with F(" degC") or F(" C") for encoding portability in Arduino environments.
Proposed diff
 bool MaximDS18::addSingleMeasurementResult(void) {
     // Immediately quit if the measurement was not successfully started
     if (!getStatusBit(MEASUREMENT_SUCCESSFUL)) {
         return bumpMeasurementAttemptCount(false);
     }

     bool  success = false;
     float result  = -9999;

     MS_DBG(getSensorNameAndLocation(), F("is reporting:"));
     result = _internalDallasTemp.getTempC(_OneWireAddress);
-    MS_DBG(F("  Received"), result, F("°C"));
+    MS_DBG(F("  Received"), result, F(" degC"));

     // 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 != DEVICE_DISCONNECTED_C) {
         // Put value into the array
         verifyAndAddMeasurementResult(DS18_TEMP_VAR_NUM, result);
         success = true;
     } else {
         MS_DBG(F("  Invalid measurement received from"),
                getSensorNameAndLocation());
     }

     // Return success value when finished
     return bumpMeasurementAttemptCount(success);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sensors/MaximDS18.cpp` around lines 183 - 209, In
MaximDS18::addSingleMeasurementResult replace the magic literals for invalid
temps with named constants: use DallasTemperature's DEVICE_DISCONNECTED_C
instead of -127 and introduce a named constant (e.g. DS18_POWER_ON_RESET_C or
DS18_POR_C) for the 85 POR sentinel, update the comparison to check result !=
DS18_POR_C && result != DEVICE_DISCONNECTED_C, add a short comment clarifying
that 85 is the POR/no-conversion value, and change the non-ASCII log suffix
F("°C") to an ASCII-safe string such as F(" degC") or F(" C") in MS_DBG calls so
the code is portable in Arduino environments.
setupGitFilters.ps1 (1)

5-21: Consider replacing Write-Host to satisfy PSScriptAnalyzer.

If you want to keep analyzer warnings clean, swap to Write-Output/Write-Information (or suppress the rule if colored output is desired).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@setupGitFilters.ps1` around lines 5 - 21, Replace all uses of Write-Host in
setupGitFilters.ps1 with a PSScriptAnalyzer-friendly cmdlet: change each
Write-Host "..." -ForegroundColor <Color> to Write-Output "..." (or to
Write-Information "..." if you want structured informational messages) and
remove the -ForegroundColor parameter; keep the git config commands and their
strings (filter.smudgePasswords, filter.disableDebug,
filters/cleanPasswords.ps1, filters/smudgePasswords.ps1,
filters/cleanDebugConfig.ps1, filters/smudgeDebugConfig.ps1) unchanged so the
messages still print but the script no longer triggers the Write-Host analyzer
rule.
docs/For-Developers/Developer-Setup.md (1)

37-45: Manual Git filter setup only works on Windows.

The manual setup instructions use PowerShell scripts (filters/cleanPasswords.ps1, etc.), which won't work on Linux or macOS. Consider adding cross-platform alternatives using bash scripts or noting this limitation.

📝 Consider adding cross-platform note
 **Manual Setup:**
 If you prefer to configure the filters manually, run these Git commands from the repository root:
+
+> **Note:** These commands use PowerShell scripts and work on Windows. For Linux/macOS, equivalent bash scripts would be needed.
 
 ```bash
 git config --local filter.smudgePasswords.clean "powershell -ExecutionPolicy Bypass -File filters/cleanPasswords.ps1"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/For-Developers/Developer-Setup.md` around lines 37 - 45, The manual Git
filter setup shown uses PowerShell scripts (filters/cleanPasswords.ps1,
filters/smudgePasswords.ps1, filters/cleanDebugConfig.ps1,
filters/smudgeDebugConfig.ps1) which only work on Windows; update
Developer-Setup.md to either (a) add cross-platform alternatives using bash/sh
scripts and corresponding git config examples for the same filter names
(filter.smudgePasswords.*, filter.disableDebug.*) or (b) clearly note the
Windows-only limitation and link to a separate section with Linux/macOS bash
equivalents (e.g., filters/cleanPasswords.sh and filters/smudgePasswords.sh) so
non-Windows developers know how to configure the filters.
src/publishers/MonitorMyWatershedPublisher.cpp (2)

34-96: Consider extracting common initialization to reduce duplication.

The same three-line initialization sequence (setHost, setPath, setPort) is repeated in all 7 constructors. This could be extracted to a private helper method for maintainability.

♻️ Proposed refactor

Add a private helper method and call it from each constructor:

// In header, add private method declaration
private:
    void initDefaults();

// In implementation
void MonitorMyWatershedPublisher::initDefaults() {
    setHost("monitormywatershed.org");
    setPath("/api/data-stream/");
    setPort(80);
}

Then replace the repeated lines in each constructor with initDefaults();

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 34 - 96,
Multiple constructors in MonitorMyWatershedPublisher repeat the same three-line
initialization (setHost, setPath, setPort); add a private helper method (e.g.,
MonitorMyWatershedPublisher::initDefaults) declared in the class and implement
it to call setHost("monitormywatershed.org"), setPath("/api/data-stream/"), and
setPort(80), then replace the repeated lines in every constructor (all
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher overloads) with a
single call to initDefaults() to eliminate duplication and centralize defaults.

106-127: Misleading comments on setter methods.

The comments for setHost, setPath, and setPort all say "Returns the data destination" but these are setter methods that return void. Update comments to accurately describe the operation.

📝 Proposed fix
-// Returns the data destination
+// Sets the Monitor My Watershed web host
 void MonitorMyWatershedPublisher::setHost(const char* host) {

-// Returns the data destination
+// Sets the Monitor My Watershed API path
 void MonitorMyWatershedPublisher::setPath(const char* endpoint) {

-// Returns the data destination
+// Sets the Monitor My Watershed API port
 void MonitorMyWatershedPublisher::setPort(int port) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/publishers/MonitorMyWatershedPublisher.cpp` around lines 106 - 127,
Update the misleading comments above the setter methods to accurately describe
their behavior: replace the "Returns the data destination" comment for
MonitorMyWatershedPublisher::setHost(const char*),
MonitorMyWatershedPublisher::setPath(const char*), and
MonitorMyWatershedPublisher::setPort(int) with clear setter descriptions such as
"Sets the data destination host", "Sets the data destination path", and "Sets
the data destination port" respectively so the comments match the void-returning
methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/EnviroDIY_Monitoring_Kit/ReadMe.md`:
- Line 90: The README line is ambiguous about which placeholders to replace;
update the sentence to explicitly state that the placeholders correspond to
different connection types by replacing it with a clearer instruction such as:
"Replace the placeholders with your actual network credentials: `YourAPN` for
cellular connections, or `YourWiFiSSID` and `YourWiFiPassword` for WiFi
connections." Ensure you change the existing sentence that mentions `YourAPN`,
`YourWiFiSSID`, and `YourWiFiPassword` so readers understand they should use
either the APN for cellular or both SSID and password for WiFi.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Line 26: Update the TOC entry and corresponding section heading from "UUID" to
"UUIDs" so the text and anchor are plural and consistent: change the link text
"[Set the universally unique identifiers (UUID) for each variable]" to use
"UUIDs" and update its anchor fragment to match the heading slug (e.g.,
"#set-the-universally-unique-identifiers-uuids-for-each-variable"), and also
update the actual section heading (and any duplicate at the other occurrence) to
"Set the universally unique identifiers (UUIDs) for each variable" so the
display text and anchor slug align.

In `@src/sensors/PaleoTerraRedox.cpp`:
- Around line 145-156: The sign-extension mask in the ADC assembly is wrong and
an unnecessary assignment exists: in the block that constructs adcValue from
res1/res2/res3 (the code that builds the 18-bit raw sample and checks sign via
"if (res1 & 0x02)"), replace the incorrect 0xFF000000 sign-extend mask with
0xFFFC0000 so bits 18–31 are set when bit17 is negative, and remove the
redundant "res = 0;" assignment since res is overwritten later; keep the sign
test on res1 & 0x02 and preserve the existing bit-extraction logic for
res1/res2/res3.

---

Outside diff comments:
In `@src/modems/DigiXBeeWifi.cpp`:
- Around line 221-227: The change uses the wrong AT parameter string: in the
call to gsmModem.changeSettingIfNeeded that sets up RSSI PWM for DIO10/PWM0 you
currently pass GF("D5") (duplicate of the DIO5/associate setting) — update that
call to use GF("P0") so the code checks/changes the correct AT command; keep the
surrounding variable (changedP0) and the logging (MS_DBG/MS_DEEP_DBG for
DIO10/PWM0) intact.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 296-298: The code currently decrements
_initialTransmissionsRemaining even when addRecord() fails; change the logic so
you call addRecord(...), capture its return value (e.g., int rc =
addRecord(...)) and only decrement _initialTransmissionsRemaining when rc >= 0
(indicating success); update the block in MonitorMyWatershedPublisher (the
function containing the addRecord() call and the _initialTransmissionsRemaining
decrement) so failures do not consume an initial transmission.
- Around line 379-399: The JSON builder in flushDataBuffer fails when variables
== 0 because the for loop over variable index never runs and the closing '}' is
never appended (leaving a trailing comma after the timestamp); fix by adding a
guard at the start of flushDataBuffer to return early when variables == 0
(mirroring the records check) or, if you prefer to always produce a JSON object,
ensure that when variables == 0 you append the closing brace (use
txBufferAppend('}') or equivalent) before returning; locate the logic in
flushDataBuffer around the for (uint8_t var = 0; var < variables; var++) loop
and update it to handle variables==0.

In `@src/sensors/ANBpH.h`:
- Around line 478-510: The ANBpH constructors (ANBpH(byte, Stream*, int8_t,
int16_t, int8_t, int8_t, uint8_t) and ANBpH(byte, Stream&, ...)) must validate
loggingIntervalMinutes at construction instead of relying on setup(); add checks
that if the sensor power is being cycled (powerPin != -1 or powerPin2 != -1)
then loggingIntervalMinutes is between 10 and 240, and if the sensor is always
powered (both powerPin and powerPin2 == -1) then loggingIntervalMinutes may be 0
— otherwise reject the call by throwing std::invalid_argument (or setting an
explicit error/invalid state) with a clear message; keep the same validation
logic in both constructors and remove or adjust the silent correction in setup()
to avoid double-handling.

In `@src/sensors/MaximDS3231.cpp`:
- Line 8: The file header's `@brief` incorrectly names the class as "MaximDS18";
update the documentation to reference the correct class "MaximDS3231" (or a
neutral description like "Implements the MaximDS3231 class") so the comment
matches the implemented class MaximDS3231 in this file; locate the `@brief` line
near the top of src/sensors/MaximDS3231.cpp and replace "MaximDS18" with
"MaximDS3231".

---

Duplicate comments:
In `@examples/EnviroDIY_Monitoring_Kit/EnviroDIY_Monitoring_Kit.ino`:
- Around line 496-503: Duplicate use of forceModemBaud in the cellular modem
setup is causing the same compilation failure as the WiFi section; remove or
conditionally compile out the modem.gsmModem.forceModemBaud(modemSerial,
static_cast<uint32_t>(modemBaud)) call (leave the PRINTOUT if desired) or wrap
it behind a debug-only macro (e.g. DEBUG_BAUD_DETECTION) so production builds do
not attempt to call forceModemBaud; target the block that checks
modem.gsmModem.testAT() and the forceModemBaud invocation to apply the change.
- Around line 479-486: The code calls a nonexistent TinyGSM method
modem.gsmModem.forceModemBaud (used after modem.gsmModem.testAT()), causing
compilation failure; replace this call with the supported approach: for initial
baud detection use TinyGsmAutoBaud(SerialAT, min, max) (or equivalent auto-baud
helper) before AT sync, or once AT is established call
modem.gsmModem.setBaud(static_cast<uint32_t>(modemBaud)) to set the baud; remove
the forceModemBaud invocation and update the logic around modem.gsmModem.testAT
/ modemSerial / modemBaud to use TinyGsmAutoBaud or setBaud accordingly.

In `@examples/example_dependencies.json`:
- Around line 26-30: The dependency entry for Adafruit NeoPixel uses a
non-existent/unpublished version "~1.15.4" which will break resolution; update
the "version" field in the Adafruit NeoPixel object (the JSON entry with "name":
"Adafruit NeoPixel", "owner": "adafruit", "url":
"https://github.com/adafruit/Adafruit_NeoPixel") to a published release such as
"~1.15.2" (or the actual latest tag you verify) so the dependency can be
resolved.

In `@examples/examples.dox`:
- Around line 67-77: Update the cross-reference to use the renamed page anchor:
replace the reference token "@ref examples_outdated" used in the link text
"[Examples for Outdated Hardware](`@ref` examples_outdated)" with "@ref
page_examples_outdated" so it matches the new page anchor name (refer to the
`@page` page_examples_outdated and the link text around it).

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Around line 54-55: The second line of the bullet that continues after "Change
the text `YourLoggerID`..." needs to be indented so it stays part of the same
list item; edit the ReadMe.md entry containing the literal `YourLoggerID` and
add two or more spaces (or a tab) at the start of the continuation line so it
aligns as the wrapped text of that bullet rather than a new paragraph/entry.
- Around line 65-66: The continuation line "Use numbers from the side of the
calibration sheet..." is not indented to belong to the preceding bullet "Change
_**all**_ of the `0.000E+00` and `1.000E+00` values...", so indent that
continuation line so it aligns under the bullet text (i.e., add two or more
spaces before "Use numbers..." so it wraps as part of the same markdown list
item), preserving the bullet marker and keeping the content as a single list
entry.

In `@examples/simple_logging/ReadMe.md`:
- Line 43: The README text and code sample use inconsistent casing for the
identifier: the prose says "logger ID" while the code sample uses
`LoggerID`/`YourLoggerID`; update the prose to match the code by changing
"logger ID" to "LoggerID" (or alternatively change the code sample to use
`loggerID` if you prefer camelCase) so the wording and the code sample use the
same identifier casing—look for occurrences of "YourLoggerID" and "LoggerID" in
the README and make them consistent.

In `@src/LoggerModem.h`:
- Around line 991-992: The doc-comment for the modem reset pin has a missing
preposition: change "attached the hard or panic reset pin of the modem." to
"attached to the hard or panic reset pin of the modem." — update the comment
block describing the digital pin number (the doc above the modem reset pin
member in LoggerModem.h) to insert the word "to" after "attached" so the
sentence reads correctly.

In `@src/modems/EspressifESP32.h`:
- Line 83: The include of TinyGsmClientESP32.h in EspressifESP32.h is correct
for the EnviroDIY fork (the line '#include "TinyGsmClientESP32.h"' in
src/modems/EspressifESP32.h) so no code changes are required; keep the include
as-is and proceed with the existing implementation referencing
TinyGsmClientESP32.
- Line 162: The declaration TinyGsmESP32 gsmModem is correct for the
ESP32-specific TinyGSM implementation (EnviroDIY fork) so no change is required;
keep the member as TinyGsmESP32 gsmModem and proceed with approving/merging the
change.

In `@src/modems/SodaqUBeeU201.h`:
- Around line 78-79: Document that the public API type for gsmModem has changed
from its previous type to TinyGsmUBLOX in the release notes and developer docs,
adding a clear migration note showing the affected symbol names (gsmModem,
TinyGsmUBLOX, SodaqUBeeU201) and concrete migration steps (what to replace and
any constructor or include changes); ensure the note references the header
inclusion change (TinyGsmClientUBLOX.h) and callsites mentioned around the
SodaqUBeeU201 usage (including the occurrences near the previous diff region and
the related code at the locations referenced as 232-235) so downstream users can
update types and includes accordingly.

In `@src/sensors/AnalogElecConductivity.cpp`:
- Around line 91-95: The code currently always marks the measurement attempt
successful; change it to validate sensorEC_uScm first (e.g., using std::isfinite
or !std::isnan) and only call
verifyAndAddMeasurementResult(ANALOGELECCONDUCTIVITY_EC_VAR_NUM, sensorEC_uScm)
and bumpMeasurementAttemptCount(true) when the value is valid; if the value is
invalid, do not add the measurement and call bumpMeasurementAttemptCount(false)
(or otherwise mark the attempt as failed) so retries are not suppressed.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 66-74: No code changes required: the ADC part number typos in the
comment block were already fixed to "TI ADS1115" and "TI ADS1015" (the comment
describing ADS library default settings in ApogeeSQ212.cpp), so approve and
merge as-is; no further edits to the ADS1115/ADS1015 references are necessary.

In `@src/sensors/EverlightALSPT19.cpp`:
- Around line 14-34: Both EverlightALSPT19 constructors must pass
ALSPT19_INC_CALC_VARIABLES to the Sensor base class to ensure the same number of
variables are registered; verify the two ctor initializers
(EverlightALSPT19::EverlightALSPT19(int8_t, int8_t, ...) and the conditional
EverlightALSPT19::EverlightALSPT19(uint8_t)) include ALSPT19_INC_CALC_VARIABLES
in the Sensor(...) parameter list and keep _supplyVoltage/_loadResistor
initialization consistent between them.

In `@src/sensors/GroPointParent.cpp`:
- Around line 210-239: The code currently stores moisture and temperature
results independently via verifyAndAddMeasurementResult(...) but returns
combined status using bumpMeasurementAttemptCount((success && successT)), which
is intentional to trigger retries when either sensor group fails; add or update
an inline comment next to the return to explicitly state that behavior and
reference verifyAndAddMeasurementResult and bumpMeasurementAttemptCount so
future readers know partial results are preserved while retries are controlled
by the combined success value.

In `@src/sensors/MaxBotixSonar.cpp`:
- Around line 154-192: The inner retry loop in MaxBotixSonar.cpp uses a fixed
rangeAttempts < 25 which, combined with the 250ms stream timeout, can block far
longer than HRXL_MEASUREMENT_TIME_MS; change the loop to bound retries by a time
budget instead: compute a deadline using millis() + HRXL_MEASUREMENT_TIME_MS (or
accept a passed measurement budget), then replace the rangeAttempts < 25
condition with a time check (while (!success && millis() < deadline)) and keep a
small safety cap on rangeAttempts to avoid pathological fast loops; update any
comments and keep the existing behavior around triggering (_triggerPin), reading
from _stream->parseInt()/read(), and result conversion (_convertCm, _maxRange)
unchanged.

In `@src/sensors/RainCounterI2C.cpp`:
- Around line 103-121: Capture the return value of _i2c->requestFrom into a
variable and validate it against the expected counts (e.g., 1 or 4) before
attempting to parse; only call _i2c->read() in a loop for the exact number of
bytes reported, populate SerialBuffer up to that count, then verify byte_in
equals the returned count and handle partial/short reads by calling
bumpMeasurementAttemptCount(false) (or other error path) instead of proceeding
with version/measurement parsing; update the logic around _i2c->requestFrom,
SerialBuffer, byte_in, _i2c->available(), and bumpMeasurementAttemptCount to
enforce this check.

In `@src/sensors/TurnerTurbidityPlus.cpp`:
- Around line 165-198: The PR comment shows a duplicate
approval/annotation—leave the code as-is (the voltage divider logic in the block
using _voltageDividerFactor that updates adcVoltage, computes calibResult, calls
verifyAndAddMeasurementResult for TURBIDITY_PLUS_VAR_NUM and
TURBIDITY_PLUS_VOLTAGE_VAR_NUM, then returns
bumpMeasurementAttemptCount(success) is correct) and remove the duplicate
reviewer tag/annotation ([approve_code_changes] / [duplicate_comment]) from the
review to avoid confusion; no code changes to _voltageDividerFactor, adcVoltage,
calibResult, verifyAndAddMeasurementResult, or bumpMeasurementAttemptCount are
required.

In `@src/VariableArray.cpp`:
- Around line 383-389: The code declares String variables sName and cycCount
only under the MS_VARIABLEARRAY_DEBUG / MS_VARIABLEARRAY_DEBUG_DEEP guard but
later uses them unconditionally in MS_DBG() calls, causing compile errors when
MS_DEBUGGING_STD is enabled without those flags; fix by ensuring sName and
cycCount are always declared before they are used (e.g., declare String sName;
String cycCount; prior to the `#if` and only assign them inside the guarded
block), or alternatively wrap the MS_DBG() calls that reference sName/cycCount
with the same `#if` defined(MS_VARIABLEARRAY_DEBUG) ||
defined(MS_VARIABLEARRAY_DEBUG_DEEP) guard so that variables and logging are
consistently conditioned (references: sName, cycCount,
sensorList[i]->getSensorNameAndLocation(),
getNumberCompleteMeasurementsAttempts(), getNumberRetryAttemptsMade(), and the
MS_DBG() calls around line 404).

---

Nitpick comments:
In `@docs/For-Developers/Developer-Setup.md`:
- Around line 37-45: The manual Git filter setup shown uses PowerShell scripts
(filters/cleanPasswords.ps1, filters/smudgePasswords.ps1,
filters/cleanDebugConfig.ps1, filters/smudgeDebugConfig.ps1) which only work on
Windows; update Developer-Setup.md to either (a) add cross-platform alternatives
using bash/sh scripts and corresponding git config examples for the same filter
names (filter.smudgePasswords.*, filter.disableDebug.*) or (b) clearly note the
Windows-only limitation and link to a separate section with Linux/macOS bash
equivalents (e.g., filters/cleanPasswords.sh and filters/smudgePasswords.sh) so
non-Windows developers know how to configure the filters.

In `@examples/OutdatedExamples/DRWI_CitizenScience/DRWI_2G/ReadMe.md`:
- Line 105: The sentence "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." is a run-on;
insert a comma after "code" (or after "code" phrase) to read: "After you are
completely certain that you have the order right in the variable section of your
code, use the teal "Copy" button..." so update the ReadMe sentence accordingly.

In `@setupGitFilters.ps1`:
- Around line 5-21: Replace all uses of Write-Host in setupGitFilters.ps1 with a
PSScriptAnalyzer-friendly cmdlet: change each Write-Host "..." -ForegroundColor
<Color> to Write-Output "..." (or to Write-Information "..." if you want
structured informational messages) and remove the -ForegroundColor parameter;
keep the git config commands and their strings (filter.smudgePasswords,
filter.disableDebug, filters/cleanPasswords.ps1, filters/smudgePasswords.ps1,
filters/cleanDebugConfig.ps1, filters/smudgeDebugConfig.ps1) unchanged so the
messages still print but the script no longer triggers the Write-Host analyzer
rule.

In `@src/modems/DigiXBee3GBypass.h`:
- Around line 135-138: Add the missing override specifiers to the deleteClient
and deleteSecureClient declarations in the DigiXBee3GBypass class so they match
the base-class overrides; update the declarations for deleteClient(Client*
client) and deleteSecureClient(Client* client) to include the override keyword
(matching the already-overridden createClient and createSecureClient signatures)
and verify the parameter types exactly match the loggerModem base class
signatures.

In `@src/modems/SIMComSIM7080.cpp`:
- Around line 51-58: The code sends the AT command "+CNETLIGHT=1" twice via
gsmModem.sendAT(...) with corresponding gsmModem.waitResponse() calls; remove
the duplicate to avoid redundant commands (either delete the second
gsmModem.sendAT(F("+CNETLIGHT=1")) and its gsmModem.waitResponse() or replace
the second call with the intended parameter if the comment meant a different
setting), locating the calls to gsmModem.sendAT and gsmModem.waitResponse in
SIMComSIM7080.cpp to make the change.

In `@src/publishers/MonitorMyWatershedPublisher.cpp`:
- Around line 34-96: Multiple constructors in MonitorMyWatershedPublisher repeat
the same three-line initialization (setHost, setPath, setPort); add a private
helper method (e.g., MonitorMyWatershedPublisher::initDefaults) declared in the
class and implement it to call setHost("monitormywatershed.org"),
setPath("/api/data-stream/"), and setPort(80), then replace the repeated lines
in every constructor (all
MonitorMyWatershedPublisher::MonitorMyWatershedPublisher overloads) with a
single call to initDefaults() to eliminate duplication and centralize defaults.
- Around line 106-127: Update the misleading comments above the setter methods
to accurately describe their behavior: replace the "Returns the data
destination" comment for MonitorMyWatershedPublisher::setHost(const char*),
MonitorMyWatershedPublisher::setPath(const char*), and
MonitorMyWatershedPublisher::setPort(int) with clear setter descriptions such as
"Sets the data destination host", "Sets the data destination path", and "Sets
the data destination port" respectively so the comments match the void-returning
methods.

In `@src/sensors/ApogeeSQ212.cpp`:
- Around line 96-105: The code currently ignores out-of-range adcVoltage
silently; add a diagnostic debug log in the else branch after the existing if
(adcVoltage < 3.6 && adcVoltage > -0.3) block that logs the sensor id/name
(SQ-212), the actual adcVoltage value and the expected bounds (e.g. -0.3..3.6)
so field troubleshooting can see why calibResult and
verifyAndAddMeasurementResult (SQ212_PAR_VAR_NUM, SQ212_VOLTAGE_VAR_NUM) were
not produced; use the same logging macro (MS_DBG or similar) used above and
leave success as false.

In `@src/sensors/CampbellOBS3.cpp`:
- Around line 100-113: The voltage range guard for OBS3 uses ADS bounds
(adcVoltage < 3.6 && adcVoltage > -0.3) but the OBS3 datasheet specifies 0–2.5V;
update the check around adcVoltage to enforce 0.0 <= adcVoltage <= 2.5 so
calibration (calibResult computed from _x2_coeff_A, _x1_coeff_B, _x0_coeff_C)
and result submission via verifyAndAddMeasurementResult(OBS3_TURB_VAR_NUM, ...)
and verifyAndAddMeasurementResult(OBS3_VOLTAGE_VAR_NUM, ...) only occur for
valid OBS3 voltages, and optionally add a debug/log when a reading is out of
range before returning bumpMeasurementAttemptCount(success).

In `@src/sensors/MaximDS18.cpp`:
- Around line 183-209: In MaximDS18::addSingleMeasurementResult replace the
magic literals for invalid temps with named constants: use DallasTemperature's
DEVICE_DISCONNECTED_C instead of -127 and introduce a named constant (e.g.
DS18_POWER_ON_RESET_C or DS18_POR_C) for the 85 POR sentinel, update the
comparison to check result != DS18_POR_C && result != DEVICE_DISCONNECTED_C, add
a short comment clarifying that 85 is the POR/no-conversion value, and change
the non-ASCII log suffix F("°C") to an ASCII-safe string such as F(" degC") or
F(" C") in MS_DBG calls so the code is portable in Arduino environments.

In `@src/sensors/SensirionSHT4x.h`:
- Line 277: Add a brief copydoc comment above the overridden declaration bool
addSingleMeasurementResult(void) override; so it matches the class's style
(e.g., add "@copydoc Sensor::addSingleMeasurementResult()" similar to the
existing documentation on sleep()). This keeps override documentation consistent
and ensures the base method docs are clearly referenced for
addSingleMeasurementResult.

In `@src/sensors/TIINA219.h`:
- Around line 452-453: The cSpell directive is currently placed after the header
guard's closing `#endif`; move the comment line `// cSpell:ignore TIINA219` so
it sits inside the header guard scope (just before the `#endif` that closes the
guard in TIINA219.h) to follow conventional header style while keeping the same
directive text.

In `@src/sensors/TurnerCyclops.cpp`:
- Around line 105-115: The magic numbers for voltage bounds in the TurnerCyclops
sensor (the if condition using 3.6 and -0.3 with adcVoltage) should be replaced
with named constants declared in the TurnerCyclops header; define e.g.
kCyclopsMaxVoltage and kCyclopsMinVoltage (or similarly descriptive names) and
use them in the check around adcVoltage, keeping the existing calibration logic
that computes calibResult from _conc_std/_volt_std/_volt_blank and calls
verifyAndAddMeasurementResult(CYCLOPS_VAR_NUM, calibResult) and
verifyAndAddMeasurementResult(CYCLOPS_VOLTAGE_VAR_NUM, adcVoltage); update any
comments/TODO to reference the constants for easier future adjustment and
verification.

…5 analog

Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
…senseCO2 and TurnerTurbidityPlus

Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
…ngleMeasurementResult with checks and value resets

Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
This reverts commit 2fdc98d.

Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
… override

Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Signed-off-by: Sara Damiano <sdamiano@stroudcenter.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant