Jelajahi Sumber

Added RAK12035 Soil Moisture and Temperature Sensor (#2223)

KPrivitt 4 bulan lalu
induk
melakukan
27f732653b

+ 47 - 2
src/helpers/sensors/EnvironmentSensorManager.cpp

@@ -101,6 +101,12 @@ static Adafruit_MLX90614 MLX90614;
 static Adafruit_VL53L0X VL53L0X;
 #endif
 
+#if ENV_INCLUDE_RAK12035
+#define TELEM_RAK12035_ADDRESS 0x20      // RAK12035 Soil Moisture sensor I2C address
+#include "RAK12035_SoilMoisture.h"
+static RAK12035_SoilMoisture RAK12035;
+#endif
+
 #if ENV_INCLUDE_GPS && defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG)
 #define RAK_WISBLOCK_GPS
 #endif
@@ -331,6 +337,17 @@ bool EnvironmentSensorManager::begin() {
   }
   #endif
 
+  #if ENV_INCLUDE_RAK12035
+    RAK12035.setup(*TELEM_WIRE);
+  if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
+    MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS);
+    RAK12035_initialized = true;
+  } else {
+    RAK12035_initialized = false;
+    MESH_DEBUG_PRINTLN("RAK12035 was not found at I2C address %02X", TELEM_RAK12035_ADDRESS);
+  }
+  #endif
+
   return true;
 }
 
@@ -483,8 +500,36 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
     }
     #endif
 
-  }
+    #if ENV_INCLUDE_RAK12035
+      if (RAK12035_initialized) {
+
+        // RAK12035 Telemetry is Channel 2
+        telemetry.addTemperature(2, RAK12035.get_sensor_temperature());
+        telemetry.addPercentage(2, RAK12035.get_sensor_moisture());
+
+        // RAK12035 CALIBRATION Telemetry is Channel 3, if enabled
 
+      #ifdef ENABLE_RAK12035_CALIBRATION 
+        // Calibration Data Screen is Channel 3
+        float cap = RAK12035.get_sensor_capacitance();
+        float _wet = RAK12035.get_humidity_full();
+        float _dry = RAK12035.get_humidity_zero();
+
+        telemetry.addFrequency(3, cap);
+        telemetry.addTemperature(3, _wet);
+        telemetry.addPower(3, _dry);
+      
+        if(cap > _dry){
+        RAK12035.set_humidity_zero(cap);
+        }
+
+        if(cap < _wet){
+        RAK12035.set_humidity_full(cap);
+        }
+        #endif
+      }
+    #endif
+  }
   return true;
 }
 
@@ -665,7 +710,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
     gps_detected = true;
     return true;
   }
-
+  
   pinMode(ioPin, INPUT);
   MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
   return false;

+ 1 - 0
src/helpers/sensors/EnvironmentSensorManager.h

@@ -22,6 +22,7 @@ protected:
   bool SHT4X_initialized = false;
   bool BME680_initialized = false;
   bool BMP085_initialized = false;
+  bool RAK12035_initialized = false;
 
   bool gps_detected = false;
   bool gps_active = false;

+ 554 - 0
src/helpers/sensors/RAK12035_SoilMoisture.cpp

@@ -0,0 +1,554 @@
+/*----------------------------------------------------------------------*
+ * RAK12035_SoilMoistureSensor.cpp - Arduino library for the Sensor     *
+ * version of I2C Soil Moisture Sensor version from Chrirp              *
+ * (https://github.com/Miceuz/i2c-moisture-sensor).                     *
+ *                                                                      *
+ * Ingo Fischer 11Nov2015                                               *
+ * https://github.com/Apollon77/I2CSoilMoistureSensor                   *
+ *                                                                      *    
+ * Ken Privitt 8Feb2026                                                 *
+ * Adapted for MeshCore Firmware Stack                                  *
+ *                                                                      *                        
+ * MIT license                                                          *
+ *                                                                      *
+ * This file contains a collection of routines to access the            *
+ * RAK12035 Soil Moisture Sensor via I2C. The sensor provides           *
+ * Soil Temperature and capacitance-based Soil Moisture Readings.       *
+ *                                                                      *
+ *----------------------------------------------------------------------*/
+
+#include "RAK12035_SoilMoisture.h"
+#include "MeshCore.h"
+#include <Wire.h>
+
+/*----------------------------------------------------------------------*
+ * Constructor.                                                         *
+ *----------------------------------------------------------------------*/
+ // RAK12035_SoilMoisture(uint8_t addr)
+ //
+ // Accepts the I2C Address to look for the RAK12035
+ // Initializes the I2C to null (will be setup later in Wire.Begin()
+ //
+ // No hardware is touched in the constructor.
+ // I2C communication is deferred until begin() is called. 
+ //------------------------------------------------------------------------------
+
+RAK12035_SoilMoisture::RAK12035_SoilMoisture(uint8_t addr)
+{
+    _addr = addr;   // Save the sensor's I2C address
+    _i2c = nullptr; // Bus not assigned yet; must be set in begin()
+}
+
+//------------------------------------------------------------------------------ 
+// setup() 
+//------------------------------------------------------------------------------ 
+// setup(TwoWire &i2c) 
+// 
+// Assigns the I2C bus that this driver instance will use. This allows the 
+// application to choose between Wire, Wire1, or any other TwoWire instance 
+// supported by the platform. 
+// 
+// No I2C communication occurs here; setup() simply stores the pointer so that 
+// begin() and all register‑level operations know which bus to use. 
+//------------------------------------------------------------------------------
+void RAK12035_SoilMoisture::setup(TwoWire &i2c)
+
+{
+    _i2c = &i2c;                  // assigns the bus pointer
+    _i2c->begin();                // Initialize the bus to Wire or Wire1
+}
+
+//------------------------------------------------------------------------------ 
+// RAK12035 Soil Moisture begin() 
+//------------------------------------------------------------------------------ 
+// 
+// Performs initialization of the RAK12035 soil‑moisture sensor. This 
+// routine assumes that the application has already selected the I2C bus via 
+// setup() and that the bus has been initialized externally (Wire.begin()).
+// It uses the passed in I2C Address (default 0x20)
+//
+// ***  This code does not supprt three sensors ***
+//      The RAK12023 has three connectors, but each of the sensors attached must
+//      all have a different I2C addresses.
+//      This code has a function to set the I2C adress of a sensor
+//      and currently only supports one address 0x20 (the default).
+//      To support three sensors, EnvironmentSensorManager would need to be modified
+//      to support multiple instances of the RAK12035_SoilMoisture class,
+//      each with a different address. (0x20, 0x21, 0x22) 
+//      The begin() function would need to be modified to loop through the three addresses
+//
+// DEBUG STATEMENTS: Can be enabled by uncommenting or adding:
+//      File:    varients/rak4631 platformio.ini
+//      Section example: [env:RAK_4631_companion_radio_ble] 
+//      Enable Debug statements: -D MESH_DEBUG=1
+// 
+//------------------------------------------------------------------------------
+bool RAK12035_SoilMoisture::begin(uint8_t addr)
+{
+//    MESH_DEBUG_PRINTLN("begin() - Start of RAK12035 initialization");
+//    MESH_DEBUG_PRINTLN("begin() - RAK12035 passed in Address %02X", addr);
+
+// 1. Ensure setup() was called
+    if (_i2c == nullptr) {
+        MESH_DEBUG_PRINTLN("RAK12035 ERROR: I2C bus not set!");
+        return false;
+}
+
+    uint16_t _dry_cal = 200;
+    uint16_t _wet_cal = 600;
+    uint8_t _version = 0;
+    uint8_t _addr;                   // The I2C address to be used (passed in parameter)
+
+/*------------------------------------------------------------------------------------------
+ * Set Calibration values -  This is done with custom a firmware version
+ *
+ *      USE the Build Flag: -D ENABLE_RAK12035_CALIBRATION = 1
+ * OR
+ *      Change the value to 1 in the RAK12035_SoilMoisture.h file
+ * 
+ * Calibration Procedure:
+ * 1) Flash the the Calibration version of the firmware.
+ * 2) Leave the sensor dry, power up the device.
+ * 3) After detecting the RAK12035 this firmware will display calibration data on Channel 3
+ * 
+ *      Frequency = Current Capacitance Value
+ *      Temperature = Current Wet calibration value
+ *      Power = Current Dry calibration value
+ *  
+ *  4) Click refresh several times.  This will take a capacitance reading and if it is
+ *     greater than the current Dry value it will store it in the sensor
+ *     The value will bounce a little as you click refresh, but it eventually settles down (a few clicks)
+ *     the stored value will stabalize at it's Maximum value.
+ * 
+ * 5) Put the sensor in water.  
+ * 
+ * 6) Click refresh several times.  This will take a capacitance reading and if it is
+ *     less than the current Wet value it will store it in the sensor
+ *     The value will bounce a little as you click refresh, but it eventually settles down (a few clicks)
+ *     the stored value will stabalize at it's Minimum value.
+ * 
+ * 7) The Sensor is now calibrated, turn off the device.
+ * 
+ * 8) Reflash the device with the non-Calibration Firmware, Data will be shown on Channel 2
+ *                             
+ *------------------------------------------------------------------------------------------
+*/ 
+
+#if ENABLE_RAK12035_CALIBRATION 
+    uint16_t _wet = 2000;         // A high value the should be out of the normal Wet range
+     set_humidity_full(_wet);
+
+   uint16_t _dry = 50;            // A low value the should be out of the normal Dry range
+   set_humidity_zero(_dry);
+#endif
+
+ /*--------------------------------------------------------------------------------
+  * 
+  *   Check if a sensor is present and return true if found, false if not present
+  *
+  *--------------------------------------------------------------------------------
+  */
+       if (query_sensor()) {
+             MESH_DEBUG_PRINTLN("begin() - Sensor responded with valid version");
+             return true;
+    }
+    else {
+        MESH_DEBUG_PRINTLN("begin() - Sensor version FAIL");
+        return false;
+    }
+}
+
+/*---------------------------------------------------------------------------------
+ *
+ * Below are all the routines to execute the various I2C commands supported
+ * by the RAK12035 sensor
+ * 
+ *--------------------------------------------------------------------------------*/
+
+uint16_t RAK12035_SoilMoisture::get_sensor_capacitance()            //Command 01 - (r) 2 byte
+{
+    uint8_t buf[2] = {0};
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_CAPACITANCE, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function 1: get_capacitance() FAIL: Bad data returned = %02X %02X", buf[0], buf[1]);
+        return (buf[0] << 8) | buf[1];   // return raw for debugging
+    }
+    uint16_t cap = (buf[0] << 8) | buf[1];
+    MESH_DEBUG_PRINTLN("Function 1: get_capacitance() SUCCESS: Capacitance = %d", cap);
+    return cap;
+}
+
+
+uint8_t RAK12035_SoilMoisture::get_I2C_address()                     //Command 02 - (r) 1 byte
+{
+    uint8_t addr = 0;
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_I2C_ADDR, &addr, 1)) {
+        MESH_DEBUG_PRINTLN("Function 2: get_I2C_address() FAIL: Bad data returned = %02X", addr);
+        return addr;   // return raw for debugging
+    }
+    MESH_DEBUG_PRINTLN("Function 2: get_I2C_address() SUCCESS: I2C Address = %02X", addr);
+    return addr;
+}
+
+
+bool RAK12035_SoilMoisture::set_sensor_addr(uint8_t addr)           //Command 03 - (w) 1 byte
+{
+    if (!write_rak12035(SOILMOISTURESENSOR_SET_I2C_ADDR, &addr, 1)) {
+        MESH_DEBUG_PRINTLN("Function 3: set_I2C_address() FAIL: Could not set new address %02X", addr);
+        return false;
+    }
+    MESH_DEBUG_PRINTLN("Function 3: set_I2C_address() SUCCESS: New address = %02X", addr);
+    return true;
+}
+
+
+uint8_t RAK12035_SoilMoisture::get_sensor_version()                 // Command 04 - 1 byte
+{
+    uint8_t v = 0;
+
+    read_rak12035(SOILMOISTURESENSOR_GET_VERSION, &v, 1);
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_VERSION, &v, 1)) {
+    MESH_DEBUG_PRINTLN("Function 4: get_sensor_version() FAIL: Bad data returned = %02X", v);
+        return v;
+    }
+    MESH_DEBUG_PRINTLN("Function 4: get_sensor_version() SUCCESS: Version = %02X", v);
+    return v;
+}
+
+
+float RAK12035_SoilMoisture::get_sensor_temperature()               //Command 05 - (r) 2 bytes
+{
+    uint8_t buf[2] = {0};
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_TEMPERATURE, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function 5: get_temperature() FAIL: Bad data returned = %02X %02X", buf[0], buf[1]);
+        return (buf[0] << 8) | buf[1];   // raw data returned for debugging 0XFFFF is error
+    }
+    // Sensor returns a 16-bit signed integer (°C * 10)
+    int16_t raw = (buf[0] << 8) | buf[1];
+    float tempC = raw / 10.0f;
+    MESH_DEBUG_PRINTLN("Function 5: get_temperature() SUCCESS: Raw=%04X  Temp=%.1f C", raw, tempC);
+    return tempC;
+}
+
+
+bool RAK12035_SoilMoisture::sensor_sleep()                          //Command 06 - (w) 1 byte
+{
+    uint8_t tmp = 0;
+    if (!write_rak12035(SOILMOISTURESENSOR_SET_SLEEP, &tmp, 1)) {
+        MESH_DEBUG_PRINTLN("Function 6: sensor_sleep() FAIL: Could not send sleep command");
+        return false;
+    }
+    MESH_DEBUG_PRINTLN("Function 6: sensor_sleep() SUCCESS: Sensor acknowledged sleep command");
+
+// Optional: turn off sensor power AFTER successful sleep command
+
+// This has been commented out due to a pin name conflict with the Heltec v3
+// This will need to be resolved if this funstion is to be utilized in the future
+/*
+    digitalWrite(WB_IO2, LOW);
+*/
+    return true;
+}
+
+
+bool RAK12035_SoilMoisture::set_humidity_full(uint16_t full)           //Command 07 - (w) 2 bytes
+{
+    uint8_t buf[2];
+    buf[0] = (full >> 8) & 0xFF;   // High byte
+    buf[1] = full & 0xFF;          // Low byte
+
+    if (!write_rak12035(SOILMOISTURESENSOR_SET_WET_CAL, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function 7: set_humidity_full() FAIL: Could not set wet calibration value"
+        );
+        return false;
+    }
+    MESH_DEBUG_PRINTLN("Function 7: set_humidity_full() SUCCESS: New Full = %04X", full);
+    return true;
+}
+
+
+bool RAK12035_SoilMoisture::set_humidity_zero(uint16_t zero)          //Command 08 - (w) 2 bytes
+{
+    uint8_t buf[2];
+    buf[0] = (zero >> 8) & 0xFF;   // High byte
+    buf[1] = zero & 0xFF;          // Low byte
+
+    if (!write_rak12035(SOILMOISTURESENSOR_SET_DRY_CAL, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function 8: set_humidity_zero() FAIL: Could not set dry calibration value");
+        return false;
+    }
+    MESH_DEBUG_PRINTLN("Function 8: set_humidity_zero() SUCCESS: New Zero = %04X", zero);
+    return true;
+}
+
+
+uint8_t RAK12035_SoilMoisture::get_sensor_moisture()                    //Command 09 - (r) 1 byte
+{
+// Load calibration values from sensor 
+    _wet_cal = get_humidity_full();
+    _dry_cal = get_humidity_zero();
+
+    MESH_DEBUG_PRINTLN("Function 9: get_moisture() - Read from sensor or calculate from capacitance");
+
+    // Read sensor version
+    uint8_t v = get_sensor_version();
+
+    // If version > 2, read moisture directly from the sensor
+    if (v > 2) {
+        MESH_DEBUG_PRINTLN("Version > 02 - Reading moisture directly from sensor");
+        uint8_t moisture = get_sensor_humid();
+        MESH_DEBUG_PRINTLN("get_moisture() Direct Read = %d%%", moisture);
+        return moisture;
+    }
+    // Otherwise calculate moisture from capacitance
+    MESH_DEBUG_PRINTLN("Calculating moisture from capacitance");
+
+    uint16_t cap = get_sensor_capacitance();
+
+    // Clamp capacitance between calibration points
+    if (_dry_cal < _wet_cal) {
+        if (cap <= _dry_cal) cap = _dry_cal;
+        if (cap >= _wet_cal) cap = _wet_cal;
+
+        float pct = (_wet_cal - cap) * 100.0f / (_wet_cal - _dry_cal);
+        if (pct > 100.0f) pct = 100.0f;
+
+        MESH_DEBUG_PRINTLN("get_moisture Case 1() Calculated = %d%%", (uint8_t)pct);
+        return (uint8_t)pct;
+    } else {
+        if (cap >= _dry_cal) cap = _dry_cal;
+        if (cap <= _wet_cal) cap = _wet_cal;
+
+        float pct = (_dry_cal - cap) * 100.0f / (_dry_cal - _wet_cal);
+        if (pct > 100.0f) pct = 100.0f;
+
+        MESH_DEBUG_PRINTLN("get_moisture Case 2() Calculated = %d%%", (uint8_t)pct);
+        return (uint8_t)pct;
+    }
+}
+
+
+uint8_t RAK12035_SoilMoisture::get_sensor_humid()                   //Command 09 - (r) 1 byte
+{
+    uint8_t moisture = 0;
+
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_MOISTURE, &moisture, 1)) {
+        MESH_DEBUG_PRINTLN("Function 9: get_sensor_humid() FAIL: Bad data returned = %02X", moisture);
+        return moisture; // raw fallback
+    }
+    MESH_DEBUG_PRINTLN("Function 9: get_sensor_humid() SUCCESS: Moisture = %d%%",moisture);
+    return moisture;
+}
+
+
+uint16_t RAK12035_SoilMoisture::get_humidity_full()                 //Command 0A - (r) 2 bytes
+{
+    uint8_t buf[2] = {0};
+
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_WET_CAL, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function A: get_humidity_full() FAIL: Bad data returned = %02X%02X", buf[0], buf[1]);
+        return 0xFFFF;   // error indicator
+    }
+
+    uint16_t full = (buf[0] << 8) | buf[1];
+
+    MESH_DEBUG_PRINTLN("Function A: get_humidity_full() SUCCESS: Full = %04X = %d", full, full);
+    return full;
+}
+
+
+uint16_t RAK12035_SoilMoisture::get_humidity_zero()                 //Command 0B - 2 bytes
+{
+    uint8_t buf[2] = {0};
+
+    if (!read_rak12035(SOILMOISTURESENSOR_GET_DRY_CAL, buf, 2)) {
+        MESH_DEBUG_PRINTLN("Function B: get_humidity_zero() FAIL: Bad data returned = %02X%02X", buf[0], buf[1]);
+        return 0xFFFF;   // error indicator
+    }
+
+    uint16_t zero = (buf[0] << 8) | buf[1];
+
+    MESH_DEBUG_PRINTLN("Function B: get_humidity_zero() SUCCESS: Zero = %04X = %d", zero, zero);
+    return zero;
+}
+
+
+/*------------------------------------------------------------------------------------------*
+ * getEvent() - High-level function to read both moisture and temperature in one call.      *
+ *------------------------------------------------------------------------------------------*
+ * This function reads the moisture percentage and temperature from the sensor and returns  *
+ * them via output parameters. This may be used for the telemerty delivery in the MeshCore  *
+ * firmware, with a single function to get all sensor data.                                 *
+ *                                                                                          *
+ * The function returns true if both readings were successfully obtained, or false if any   *
+ * error occurred during I2C communication.                                                 *
+ *                                                                                          *
+ * This function is currently not used                                                      *
+ *------------------------------------------------------------------------------------------*/
+bool RAK12035_SoilMoisture::getEvent(uint8_t *humidity, uint16_t *temp)
+{
+    // Read moisture (0-100%)
+    uint8_t moist = get_sensor_moisture();
+    if (moist == 0xFF)      //error indicator
+        return false;
+    MESH_DEBUG_PRINTLN("getEvent() - Humidity = %d", moist);  
+    *humidity = moist;
+
+    //Read temperature (degrees C)
+    uint16_t t = get_sensor_temperature();
+    if (t == 0XFFFF) // error indicator
+    return false;
+    
+    *temp = t; 
+    MESH_DEBUG_PRINTLN("getEvent() - Temperature = %d", t);   
+    return true;
+}
+
+/*------------------------------------------------------------------------------------------*
+ * Sensor Power Management and Reset Routines
+ * 
+ * These routines manage the power and reset state of the sensor. The sensor_on() routine is 
+ * designed to power on the sensor and wait for it to become responsive, while the reset() 
+ * routine toggles the reset pin and waits for the sensor to respond with a valid version.
+ * 
+ * They are for a future sensor power management function.
+ *------------------------------------------------------------------------------------------*/
+
+bool RAK12035_SoilMoisture::sensor_on()
+{
+    uint8_t data;
+ // This has been commented out due to a pin name conflict with the Heltec v3
+ // This will need to be resolved if this funstion is to be utilized in the future
+
+/*
+    pinMode(WB_IO2, OUTPUT);
+	digitalWrite(WB_IO2, HIGH);     //Turn on Sensor Power
+
+    pinMode(WB_IO4, OUTPUT);        //Set IO4 Pin to Output (connected to *reset on sensor)
+	digitalWrite(WB_IO4, LOW);      //*reset - Reset the Sensor
+	delay(1);                       //Wait for the minimum *reset, 1mS is longer than required minimum
+    digitalWrite(WB_IO4, HIGH);     //Deassert Reset
+
+    delay(10);                     // Wait for the sensor code to complete initialization
+*/
+	uint8_t v = 0;
+    time_t timeout = millis();
+	while ((!query_sensor()))                    //Wait for sensor to respond to I2C commands, 
+	{                                            //indicating it is ready
+		if ((millis() - timeout) > 50){          //0.5 second timeout for sensor to respond
+            MESH_DEBUG_PRINTLN("reset() - Timeout, no response from I2C commands");
+            return false;
+        }
+        else {
+			delay(10);                           //delay 10mS
+        }
+    }
+}
+
+bool RAK12035_SoilMoisture::reset()
+{
+// This function is for a future Sensor Power Management function.
+// When power is reapplied this will reset the sensor and wait for it to respond
+// with a valid version.
+//
+// The Atmel 8495 Microcoltroller: Reset input. A low level on this pin for longer than
+// the minimum pulse length will generate a reset, even if the clock is not
+// running and provided the reset pin has not been disabled. The minimum pulse length is 
+// given in Table 25-5 on page 240. 2000ns = .002mS
+// Shorter pulses are not guaranteed to generate a reset.
+//
+//  Power is never removed so the Sensor reset was removed and is not needed, 
+//  But might be needed if power is ever switched off.  Here is tested code.
+
+// This has been commented out due to a pin name conflict with the Heltec v3
+// This will need to be resolved if this funstion is to be utilized in the future
+
+/*
+    pinMode(WB_IO4, OUTPUT);        //Set IO4 Pin to Output (connected to *reset on sensor)
+    MESH_DEBUG_PRINTLN("Assert *reset (Low) for 1 mS");
+    digitalWrite(WB_IO4, LOW);      //Reset the Sensor
+    delay(1);                       //Wait for the minimum *reset, 1mS is longer than required minimum
+    MESH_DEBUG_PRINTLN("reset() - De-assert *reset (High)");
+    digitalWrite(WB_IO4, HIGH);     // Deassert Reset
+*/
+
+    MESH_DEBUG_PRINTLN("reset() - Begin poling in 100mS intervals for a non-zero version");
+    uint32_t start_time = millis();
+    MESH_DEBUG_PRINTLN("reset() - Timeout, Start Time: %d milliseconds", start_time);
+
+    const uint32_t timeout_ms = 500;    // Wait for 0.5 seconds
+    uint32_t start = millis();
+    
+    while (true) {
+        if (query_sensor()) {
+            MESH_DEBUG_PRINTLN("reset() - First Pass, Sensor responded with valid version");
+            uint32_t stop_time = millis();
+            MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time);    
+            MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time));
+
+             return true;
+        }
+            if (millis() - start > timeout_ms) {
+                MESH_DEBUG_PRINTLN("reset() - Timeout waiting for valid sensor version");
+                uint32_t stop_time = millis();
+                MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time);    
+                MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time));
+                return false;
+         }
+            delay(100);
+    }
+}
+
+ bool RAK12035_SoilMoisture::query_sensor()
+{
+    uint8_t v = 0;
+    v = get_sensor_version();
+
+   // Treat 0x00 and 0xFF as invalid / bootloader / garbage
+    if (v == 0x00 || v == 0xFF) {
+        MESH_DEBUG_PRINTLN("query_sensor() FAIL: Version value invalid: %02X", v);
+        return false;
+    }
+        MESH_DEBUG_PRINTLN("query_sensor() SUCCESS: Sensor Present, Version = %02X", v);
+        return true;
+}
+
+
+/*------------------------------------------------------------------------------------------*
+ * Below are the low-level I2C read and write functions. These handle the actual
+ * communication with the sensor registers. The higher-level functions call these
+ * to perform specific tasks.
+ *------------------------------------------------------------------------------------------*/
+
+bool RAK12035_SoilMoisture::read_rak12035(uint8_t cmd, uint8_t *data, uint8_t length)
+{
+    _i2c->beginTransmission(_addr);
+    _i2c->write(cmd);                     // <-- COMMAND, not register index
+    if (_i2c->endTransmission() != 0)
+        return false;
+
+    delay(20);
+
+    int received = _i2c->requestFrom(_addr, length);
+    if (received != length)
+        return false;
+
+    for (int i = 0; i < length; i++)
+        data[i] = _i2c->read();
+
+    return true;
+}
+
+bool RAK12035_SoilMoisture::write_rak12035(uint8_t cmd, uint8_t *data, uint8_t length)
+{
+    _i2c->beginTransmission(_addr);
+    _i2c->write(cmd);          // <-- COMMAND, not register index
+
+    for (uint8_t i = 0; i < length; i++)
+        _i2c->write(data[i]);
+
+    if (_i2c->endTransmission() != 0)
+        return false;
+
+    delay(20);
+    return true;
+}

+ 88 - 0
src/helpers/sensors/RAK12035_SoilMoisture.h

@@ -0,0 +1,88 @@
+/**
+ * @file RAK12035_SoilMoisture.h
+ * @author Bernd Giesecke (bernd.giesecke@rakwireless.com)
+ * @brief Header file for Class RAK12035
+ * @version 0.1
+ * @date 2021-11-20
+ * 
+ * Updates for MeshCore integration
+ * Ken Privitt
+ * 2/26/2026
+ * 
+ * @copyright Copyright (c) 2021
+ * 
+ */
+#ifndef RAK12035_SOILMOISTURE_H
+#define RAK12035_SOILMOISTURE_H
+#endif
+
+#ifndef ENABLE_RAK12025_CALIBRATION
+#define ENABLE_RAK12025_CALIBRATION = 0  // Used to generate Calibration Version of Firmware
+
+#include <Arduino.h>
+#include <Wire.h>
+
+#define RAK12035_I2C_ADDR_DEFAULT   0x20
+#define RAK12035_0_ADDR             0x20
+#define RAK12035_1_ADDR             0x21
+#define RAK12035_2_ADDR             0x22
+
+// Command codes used by the RAK12035 firmware
+#define SOILMOISTURESENSOR_GET_CAPACITANCE   0x01   // (r)   2 bytes
+#define SOILMOISTURESENSOR_GET_I2C_ADDR      0x02   // (r)   1 bytes
+#define SOILMOISTURESENSOR_SET_I2C_ADDR      0x03   // (w)   1 bytes
+#define SOILMOISTURESENSOR_GET_VERSION       0x04   // (r)   1 bytes
+#define SOILMOISTURESENSOR_GET_TEMPERATURE   0x05   // (r)   2 bytes
+#define SOILMOISTURESENSOR_SET_SLEEP         0x06   // (w)   1 bytes
+#define SOILMOISTURESENSOR_SET_WET_CAL       0x07   // (w)   2 bytes
+#define SOILMOISTURESENSOR_SET_DRY_CAL       0x08   // (w)   2 bytes
+#define SOILMOISTURESENSOR_GET_MOISTURE      0x09   // (r)   1 bytes
+#define SOILMOISTURESENSOR_GET_WET_CAL       0x0A   // (r)   2 bytes
+#define SOILMOISTURESENSOR_GET_DRY_CAL       0x0B   // (r)   2 bytes
+
+class RAK12035_SoilMoisture
+{
+public:
+    RAK12035_SoilMoisture(uint8_t addr = RAK12035_I2C_ADDR_DEFAULT);
+
+    void setup(TwoWire& i2c);
+    bool begin(uint8_t addr);
+    bool getEvent(uint8_t *humidity, uint16_t *temperature);
+   
+    uint16_t get_sensor_capacitance();                      //Command 01 - (r) 2 byte
+    uint8_t get_I2C_address();                              //Command 02 - (r) 1 byte
+    bool set_sensor_addr(uint8_t addr);                     //Command 03 - (w) 1 byte
+    uint8_t get_sensor_version();                           //Command 04 - (r) 1 byte
+    float get_sensor_temperature();                         //Command 05 - (r) 2 bytes
+    bool sensor_sleep();                                    //Command 06 - (w) 1 byte
+    bool set_humidity_full(uint16_t hundred_val);           //Command 07 - (w) 2 bytes
+    bool set_humidity_zero(uint16_t zero_val);              //Command 08 - (w) 2 bytes
+    uint8_t get_sensor_moisture();                          //Command 09 - (r) 1 byte
+    uint8_t get_sensor_humid();                             //Command 09 - (r) 1 byte
+    uint16_t get_humidity_full();                           //Command 0A - (r) 2 bytes
+    uint16_t get_humidity_zero();                           //Command 0B - (r) 2 bytes
+
+    bool read_rak12035(uint8_t cmd, uint8_t *data, uint8_t length);
+    bool write_rak12035(uint8_t cmd, uint8_t *data, uint8_t length);
+
+    bool query_sensor(); 
+    bool sensor_on();
+    bool reset();
+
+    uint16_t _dry_cal;
+    uint16_t _wet_cal;
+
+private:
+    bool read_reg(uint8_t reg, uint8_t *data, uint8_t len);
+    bool write_reg(uint8_t reg, uint8_t *data, uint8_t len);
+
+    TwoWire *_i2c = &Wire;
+    uint8_t _addr;
+
+    uint16_t default_dry_cal = 2000;
+    uint16_t default_wet_cal = 50;
+    uint8_t _capacitance = 0;
+    uint16_t _temperature = 0;
+    uint8_t _moisture = 0;
+};
+#endif

+ 1 - 0
variants/rak4631/platformio.ini

@@ -20,6 +20,7 @@ build_flags = ${nrf52_base.build_flags}
   -D LORA_TX_POWER=22
   -D SX126X_CURRENT_LIMIT=140
   -D SX126X_RX_BOOSTED_GAIN=1
+  -D ENV_INCLUDE_RAK12035=1
 build_src_filter = ${nrf52_base.build_src_filter}
   +<../variants/rak4631>
   +<helpers/sensors>