Просмотр исходного кода

Merge branch 'meshcore-dev:dev' into dev

Snayler 4 месяцев назад
Родитель
Сommit
d360931aa7
33 измененных файлов с 2118 добавлено и 23 удалено
  1. 1 0
      .gitignore
  2. 1 1
      docs/cli_commands.md
  3. 2 0
      docs/nrf52_power_management.md
  4. 20 0
      docs/number_allocations.md
  5. 17 1
      docs/payloads.md
  6. 1 2
      examples/simple_repeater/MyMesh.h
  7. 1 1
      library.json
  8. 2 1
      platformio.ini
  9. 30 5
      src/helpers/AutoDiscoverRTCClock.cpp
  10. 197 0
      src/helpers/RTC_RX8130CE.cpp
  11. 33 0
      src/helpers/RTC_RX8130CE.h
  12. 12 3
      src/helpers/esp32/ESPNOWRadio.h
  13. 49 4
      src/helpers/sensors/EnvironmentSensorManager.cpp
  14. 1 0
      src/helpers/sensors/EnvironmentSensorManager.h
  15. 554 0
      src/helpers/sensors/RAK12035_SoilMoisture.cpp
  16. 88 0
      src/helpers/sensors/RAK12035_SoilMoisture.h
  17. 46 0
      variants/gat562_mesh_watch13/GAT56MeshWatch13Board.cpp
  18. 44 0
      variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h
  19. 89 0
      variants/gat562_mesh_watch13/platformio.ini
  20. 50 0
      variants/gat562_mesh_watch13/target.cpp
  21. 31 0
      variants/gat562_mesh_watch13/target.h
  22. 44 0
      variants/gat562_mesh_watch13/variant.cpp
  23. 201 0
      variants/gat562_mesh_watch13/variant.h
  24. 1 1
      variants/generic_espnow/platformio.ini
  25. 66 0
      variants/muziworks_r1_neo/R1NeoBoard.cpp
  26. 56 0
      variants/muziworks_r1_neo/R1NeoBoard.h
  27. 132 0
      variants/muziworks_r1_neo/platformio.ini
  28. 47 0
      variants/muziworks_r1_neo/target.cpp
  29. 22 0
      variants/muziworks_r1_neo/target.h
  30. 92 0
      variants/muziworks_r1_neo/variant.cpp
  31. 183 0
      variants/muziworks_r1_neo/variant.h
  32. 1 0
      variants/rak4631/platformio.ini
  33. 4 4
      variants/thinknode_m5/platformio.ini

+ 1 - 0
.gitignore

@@ -16,3 +16,4 @@ cmake-*
 compile_commands.json
 .venv/
 venv/
+platformio.local.ini

+ 1 - 1
docs/cli_commands.md

@@ -557,7 +557,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
 - `set agc.reset.interval <value>`
 
 **Parameters:**
-- `value`: Interval in seconds rounded down to a multiple of 4 (17 becomes 16)
+- `value`: Interval in seconds rounded down to a multiple of 4 (17 becomes 16). 0 to disable.
 
 **Default:** `0.0`
 

+ 2 - 0
docs/nrf52_power_management.md

@@ -33,11 +33,13 @@ Shutdown reason codes (stored in GPREGRET2):
 
 ## Supported Boards
 
+
 | Board | Implemented | LPCOMP wake | VBUS wake |
 |-------|-------------|-------------|-----------|
 | Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes |
 | RAK4631 (`rak4631`) | Yes | Yes | Yes |
 | Heltec T114 (`heltec_t114`) | Yes | Yes | Yes |
+| GAT562 Mesh Watch13 | Yes | Yes | Yes |
 | Promicro nRF52840 | No | No | No |
 | RAK WisMesh Tag | No | No | No |
 | Heltec Mesh Solar | No | No | No |

+ 20 - 0
docs/number_allocations.md

@@ -0,0 +1,20 @@
+# Number Allocations
+
+This document lists unique numbers/identifiers used in various MeshCore protcol payloads.
+
+# Group Data Types
+
+The `PAYLOAD_TYPE_GRP_DATA` payloads have a 16-bit data-type field, which identifies which application the packet is for.
+
+To make sure multiple applications can function without interfering with each other, the table below is for reserving various ranges of data-type values. Just modify this table, adding a row, then submit a PR to have it authorised/merged.
+
+NOTE: the range FF00 - FFFF is for use while you're developing, doing POC, and for these you don't need to request to use/allocate.
+
+Once you have a working app/project, you need to be able to demonstrate it exists/works, and THEN request type IDs. So, just use the testing/dev range while developing, then request IDs before you transition to publishing your project.
+
+| Data-Type range | App name                    | Contact                                              |
+|-----------------|-----------------------------|------------------------------------------------------|
+| 0000 - 00FF     | -reserved for internal use- |  |
+| FF00 - FFFF     | -reserved for testing/dev-  |  |
+
+(add rows, inside the range 0100 - FEFF for custom apps)

+ 17 - 1
docs/payloads.md

@@ -226,7 +226,7 @@ txt_type
 | reply path     | (variable)      | reply path                                                       |
 
 
-# Group text message / datagram
+# Group text message
 
 | Field        | Size (bytes)    | Description                                |
 |--------------|-----------------|--------------------------------------------|
@@ -236,6 +236,22 @@ txt_type
 
 The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `<sender name>: <message body>` (eg., `user123: I'm on my way`).
 
+# Group datagram
+
+| Field        | Size (bytes)    | Description                                |
+|--------------|-----------------|--------------------------------------------|
+| channel hash | 1               | first byte of SHA256 of channel's shared key  |
+| cipher MAC   | 2               | MAC for encrypted data in next field       |
+| ciphertext   | rest of payload | encrypted data, see below for details   |
+
+The data contained in the ciphertext uses the format below:
+
+| Field        | Size (bytes)    | Description                                |
+|--------------|-----------------|--------------------------------------------|
+| data type    | 2               | Identifier for type of data. (See number_allocations.md)  |
+| data len     | 1               | byte length of data         |
+| data         | rest of payload | (depends on data type)     |
+
 
 # Control data
 

+ 1 - 2
examples/simple_repeater/MyMesh.h

@@ -119,7 +119,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
 #endif
 
   void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
-  void sendNodeDiscoverReq();
   uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
   uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
   uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
@@ -177,7 +176,7 @@ public:
   MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
 
   void begin(FILESYSTEM* fs);
-
+  void sendNodeDiscoverReq();
   const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
   const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
   const char* getRole() override { return FIRMWARE_ROLE; }

+ 1 - 1
library.json

@@ -4,7 +4,7 @@
     "dependencies": {
         "SPI": "*",
         "Wire": "*",
-        "jgromes/RadioLib": "^7.3.0",
+        "jgromes/RadioLib": "^7.6.0",
         "rweather/Crypto": "^0.4.0",
         "adafruit/RTClib": "^2.1.3",
         "melopero/Melopero RV3028": "^1.1.0",

+ 2 - 1
platformio.ini

@@ -11,6 +11,7 @@
 [platformio]
 extra_configs =
 	variants/*/platformio.ini
+	platformio.local.ini
 
 [arduino_base]
 framework = arduino
@@ -18,7 +19,7 @@ monitor_speed = 115200
 lib_deps =
   SPI
   Wire
-  jgromes/RadioLib @ ^7.3.0
+  jgromes/RadioLib @ ^7.6.0
   rweather/Crypto @ ^0.4.0
   adafruit/RTClib @ ^2.1.3
   melopero/Melopero RV3028 @ ^1.1.0

+ 30 - 5
src/helpers/AutoDiscoverRTCClock.cpp

@@ -1,6 +1,7 @@
 #include "AutoDiscoverRTCClock.h"
 #include "RTClib.h"
 #include <Melopero_RV3028.h>
+#include "RTC_RX8130CE.h"
 
 static RTC_DS3231 rtc_3231;
 static bool ds3231_success = false;
@@ -11,9 +12,13 @@ static bool rv3028_success = false;
 static RTC_PCF8563 rtc_8563;
 static bool rtc_8563_success = false;
 
+static RTC_RX8130CE rtc_8130;
+static bool rtc_8130_success = false;
+
 #define DS3231_ADDRESS   0x68
 #define RV3028_ADDRESS   0x52
 #define PCF8563_ADDRESS  0x51
+#define RX8130CE_ADDRESS 0x32
 
 bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
   wire.beginTransmission(addr);
@@ -25,22 +30,32 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
   if (i2c_probe(wire, DS3231_ADDRESS)) {
     ds3231_success = rtc_3231.begin(&wire);
   }
+
   if (i2c_probe(wire, RV3028_ADDRESS)) {
     rtc_rv3028.initI2C(wire);
-	rtc_rv3028.writeToRegister(0x35, 0x00);
-	rtc_rv3028.writeToRegister(0x37, 0xB4); // Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP
-	rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
+    rtc_rv3028.writeToRegister(0x35, 0x00);
+    rtc_rv3028.writeToRegister(0x37, 0xB4); // Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP
+    rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
     rv3028_success = true;
   }
-  if(i2c_probe(wire,PCF8563_ADDRESS)){
+
+  if (i2c_probe(wire, PCF8563_ADDRESS)) {
     rtc_8563_success = rtc_8563.begin(&wire);
   }
+
+  if (i2c_probe(wire, RX8130CE_ADDRESS)) {
+    MESH_DEBUG_PRINTLN("RX8130CE: Found");
+    rtc_8130.begin(&wire);
+    rtc_8130_success = true;
+    MESH_DEBUG_PRINTLN("RX8130CE: Initialized");
+  }
 }
 
 uint32_t AutoDiscoverRTCClock::getCurrentTime() {
   if (ds3231_success) {
     return rtc_3231.now().unixtime();
   }
+
   if (rv3028_success) {
     return DateTime(
         rtc_rv3028.getYear(),
@@ -51,9 +66,16 @@ uint32_t AutoDiscoverRTCClock::getCurrentTime() {
         rtc_rv3028.getSecond()
     ).unixtime();
   }
-  if(rtc_8563_success){
+
+  if (rtc_8563_success) {
     return rtc_8563.now().unixtime();
   }
+
+  if (rtc_8130_success) {
+    MESH_DEBUG_PRINTLN("RX8130CE: Reading time");
+    return rtc_8130.now().unixtime();
+  }
+
   return _fallback->getCurrentTime();
 }
 
@@ -66,6 +88,9 @@ void AutoDiscoverRTCClock::setCurrentTime(uint32_t time) {
     rtc_rv3028.setTime(dt.year(), dt.month(), weekday, dt.day(), dt.hour(), dt.minute(), dt.second());
   } else if (rtc_8563_success) {
     rtc_8563.adjust(DateTime(time));
+  } else if (rtc_8130_success) {
+    MESH_DEBUG_PRINTLN("RX8130CE: Setting time");
+    rtc_8130.adjust(DateTime(time));
   } else {
     _fallback->setCurrentTime(time);
   }

+ 197 - 0
src/helpers/RTC_RX8130CE.cpp

@@ -0,0 +1,197 @@
+#include "RTC_RX8130CE.h"
+#include "RTClib.h"
+
+
+bool RTC_RX8130CE::stop(bool stop) {
+    write_register(0x1E, stop ? 0x040 : 0x00);
+    return true;
+}
+
+bool RTC_RX8130CE::begin(TwoWire *wire) {
+    if (i2c_dev) {
+       delete i2c_dev;
+    }
+
+    i2c_dev = new Adafruit_I2CDevice(this->_addr, wire);
+    if (!i2c_dev->begin()) {
+       return false;
+    }
+
+    /* 
+     * Digital offset register:
+     *   [7]   DET: 0 ->  disabled
+     *   [6:0] L7-L1: 0 -> no offset
+     */
+    write_register(0x30, 0x00);
+
+    /* 
+     * Extension Register register:
+     *   [7:6]   FSEL: 0 ->  0
+     *   [5]     USEL: 0 -> 0
+     *   [4]     TE: 0 ->
+     *   [3]     WADA: 0 -> 0
+     *   [2-0]   TSEL: 0 -> 0
+     */
+    write_register(0x1C, 0x00);
+
+    /* 
+     * Flag Register register:
+     *   [7]     VBLF: 0 ->  0
+     *   [6]     0: 0 -> 
+     *   [5]     UF: 0 -> 
+     *   [4]     TF: 0 ->
+     *   [3]     AF: 0 -> 0
+     *   [2]     RSF: 0 -> 0
+     *   [1]     VLF: 0 -> 0
+     *   [0]     VBFF: 0 -> 0
+     */
+    write_register(0x1D, 0x00);
+
+    /* 
+     * Control Register0 register:
+     *   [7]     TEST: 0 ->  0
+     *   [6]     STOP: 0 -> 
+     *   [5]     UIE: 0 -> 
+     *   [4]     TIE: 0 ->
+     *   [3]     AIE: 0 -> 0
+     *   [2]     TSTP: 0 -> 0
+     *   [1]     TBKON: 0 -> 0
+     *   [0]     TBKE: 0 -> 0
+     */
+    write_register(0x1E, 0x00);
+
+    /* 
+     * Control Register1 register:
+     *   [7-6]   SMPTSEL: 0 ->  0
+     *   [5]     CHGEN: 0 -> 
+     *   [4]     INIEN: 0 -> 
+     *   [3]     0: 0 ->
+     *   [2]     RSVSEL: 0 -> 0
+     *   [1-0]   BFVSEL: 0 -> 0
+     */
+    write_register(0x1F, 0x00);
+
+    this->stop(false); // clear STOP bit
+
+    /* 
+     * Function register:
+     *   [7]   100TH: 0 ->  disabled
+     *   [6:5] Periodic interrupt: 0 -> no periodic interrupt
+     *   [4]   RTCM: 0 -> real-time clock mode
+     *   [3]   STOPM: 0 -> RTC stop is controlled by STOP bit only
+     *   [2:0] Clock output frequency: 000 (Default value)
+     */
+    write_register(0x28, 0x00); 
+    
+    // Battery switch register
+    write_register(0x26, 0x00); // enable battery switch feature
+
+    return true;
+}
+
+bool RTC_RX8130CE::setTime(struct tm *t) {
+    uint8_t buf[8];
+    buf[0] = 0x10;
+    buf[1] = bin2bcd(t->tm_sec) & 0x7F;
+    buf[2] = bin2bcd(t->tm_min) & 0x7F;
+    buf[3] = bin2bcd(t->tm_hour) & 0x3F;
+    buf[4] = bin2bcd(t->tm_wday) & 0x07;
+    buf[5] = bin2bcd(t->tm_mday) & 0x3F;
+    buf[6] = bin2bcd(t->tm_mon + 1) & 0x1F;
+    buf[7] = bin2bcd((t->tm_year - 100));
+
+    this->stop(true);
+    i2c_dev->write(buf, sizeof(buf));
+    this->stop(false);
+
+    return true;
+}
+
+void RTC_RX8130CE::adjust(DateTime dt) {
+   struct tm *atv;
+   time_t utime;
+
+   utime = (time_t)dt.unixtime();
+   atv = gmtime(&utime);
+
+   this->setTime(atv);
+}
+
+DateTime RTC_RX8130CE::now() {
+   struct tm atv;
+   this->getTime(&atv);
+
+   return DateTime((uint32_t)mktime(&atv));
+}
+
+uint32_t RTC_RX8130CE::unixtime() {
+   struct tm atv;
+   this->getTime(&atv);
+
+   return (uint32_t)mktime(&atv);
+}
+
+bool RTC_RX8130CE::getTime(struct tm *t) {
+    uint8_t buff[7];
+
+    buff[0] = 0x10;
+
+    i2c_dev->write_then_read(buff, 1, buff, 7);
+
+    t->tm_sec =  bcd2bin(buff[0] & 0x7F);
+    t->tm_min =  bcd2bin(buff[1] & 0x7F);
+    t->tm_hour = bcd2bin(buff[2] & 0x3F);
+    t->tm_wday = bcd2bin(buff[3] & 0x07);
+    t->tm_mday = bcd2bin(buff[4] & 0x3F);
+    t->tm_mon =  bcd2bin(buff[5] & 0x1F) - 1;
+    t->tm_year = bcd2bin(buff[6]) + 100;
+
+    return true;
+}
+
+bool RTC_RX8130CE::writeRAM(uint8_t address, uint8_t value) {
+    return this->writeRAM(address, &value, 1);
+}
+
+size_t RTC_RX8130CE::writeRAM(uint8_t address, uint8_t *value, size_t len) {
+    uint8_t buf[len + 1];
+    
+    if (address > 3) {
+        return 0;
+    }
+
+    if ((address + len) > 3) {
+        len = 3 - address;
+    }
+
+    buf[0] = 0x20 + address;
+
+    for (int i = 1; i <= len + 1; i++) {
+       buf[i] = value[i - 1];
+    }
+
+    i2c_dev->write(buf, len + 1);
+
+    return len;
+}
+
+bool RTC_RX8130CE::readRAM(uint8_t address, uint8_t *value, size_t len) {
+    uint8_t real_address = 0x20 + address;
+
+    if (address > 3) { // Oversize of 64-bytes RAM
+        return false;
+    }
+
+    if ((address + len) > 3) { // Data size over RAM size
+        len = 3 - address;
+    }
+
+    i2c_dev->write_then_read(&real_address, 1, value, len);
+    return true;
+}
+
+uint8_t RTC_RX8130CE::readRAM(uint8_t address) {
+    uint8_t value = 0xFF;
+    this->readRAM(address, &value, 1);
+    return value;
+}

+ 33 - 0
src/helpers/RTC_RX8130CE.h

@@ -0,0 +1,33 @@
+#ifndef __RTC_RX8130CE_H__
+#define __RTC_RX8130CE_H__
+
+#include <Arduino.h>
+#include <Wire.h>
+#include <time.h>
+#include "RTClib.h"
+
+class RTC_RX8130CE : RTC_I2C {
+    private:
+        const uint8_t _addr = 0x32;
+
+        bool stop(bool stop);
+
+    protected:
+
+    public:
+        bool begin(TwoWire *wire);
+        bool setTime(struct tm *t);
+        bool getTime(struct tm *t);
+        void adjust(DateTime t);
+        
+        DateTime now();
+        uint32_t unixtime();
+
+        bool writeRAM(uint8_t address, uint8_t value);
+        size_t writeRAM(uint8_t address, uint8_t *value, size_t len);
+        bool readRAM(uint8_t address, uint8_t *value, size_t len);
+        uint8_t readRAM(uint8_t address);
+
+};
+
+#endif

+ 12 - 3
src/helpers/esp32/ESPNOWRadio.h

@@ -4,10 +4,10 @@
 
 class ESPNOWRadio : public mesh::Radio {
 protected:
-  uint32_t n_recv, n_sent;
+  uint32_t n_recv, n_sent, n_recv_errors;
 
 public:
-  ESPNOWRadio() { n_recv = n_sent = 0; }
+  ESPNOWRadio() { n_recv = n_sent = n_recv_errors = 0; }
 
   void init();
   int recvRaw(uint8_t* bytes, int sz) override;
@@ -19,12 +19,21 @@ public:
 
   uint32_t getPacketsRecv() const { return n_recv; }
   uint32_t getPacketsSent() const { return n_sent; }
-  void resetStats() { n_recv = n_sent = 0; }
+  uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
+  void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
 
   virtual float getLastRSSI() const override;
   virtual float getLastSNR() const override;
 
   float packetScore(float snr, int packet_len) override { return 0; }
+
+  /**
+   * These two functions do nothing for ESP-NOW, but are needed for the
+   * Radio interface.
+   */
+  virtual void setRxBoostedGainMode(bool) { }
+  virtual bool getRxBoostedGainMode() const { return false; }
+
   uint32_t intID();
   void setTxPower(uint8_t dbm);
 };

+ 49 - 4
src/helpers/sensors/EnvironmentSensorManager.cpp

@@ -12,7 +12,7 @@
 #endif
 #define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
 #include <Adafruit_BME680.h>
-static Adafruit_BME680 BME680;
+static Adafruit_BME680 BME680(TELEM_WIRE);
 #endif
 
 #ifdef ENV_INCLUDE_BMP085
@@ -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
@@ -180,7 +186,7 @@ bool EnvironmentSensorManager::begin() {
   #endif
 
   #if ENV_INCLUDE_BME680
-  if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
+  if (BME680.begin(TELEM_BME680_ADDRESS)) {
     MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
     BME680_initialized = true;
   } else {
@@ -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

+ 46 - 0
variants/gat562_mesh_watch13/GAT56MeshWatch13Board.cpp

@@ -0,0 +1,46 @@
+#include <Arduino.h>
+#include <Wire.h>
+
+#include "GAT56MeshWatch13Board.h"
+
+
+#ifdef NRF52_POWER_MANAGEMENT
+// Static configuration for power management
+// Values set in variant.h defines
+const PowerMgtConfig power_config = {
+  .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
+  .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
+  .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
+};
+
+
+void GAT56MeshWatch13Board::initiateShutdown(uint8_t reason) {
+  if (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
+      reason == SHUTDOWN_REASON_BOOT_PROTECT) {
+    configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
+  }
+  enterSystemOff(reason);
+}
+#endif // NRF52_POWER_MANAGEMENT
+
+
+void GAT56MeshWatch13Board::begin() {
+  NRF52BoardDCDC::begin();
+  pinMode(PIN_VBAT_READ, INPUT);
+
+
+#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
+  Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
+#endif
+
+  Wire.begin();
+
+  pinMode(SX126X_POWER_EN, OUTPUT);
+#ifdef NRF52_POWER_MANAGEMENT
+  // Boot voltage protection check (may not return if voltage too low)
+  // We need to call this after we configure SX126X_POWER_EN as output but before we pull high
+  checkBootVoltage(&power_config);
+#endif
+  digitalWrite(SX126X_POWER_EN, HIGH);
+  delay(10);   // give sx1262 some time to power up
+}

+ 44 - 0
variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <MeshCore.h>
+#include <Arduino.h>
+#include <helpers/NRF52Board.h>
+
+
+class GAT56MeshWatch13Board : public NRF52BoardDCDC {
+protected:
+#ifdef NRF52_POWER_MANAGEMENT
+  void initiateShutdown(uint8_t reason) override;
+#endif
+
+public:
+  GAT56MeshWatch13Board() : NRF52Board("GAT562_OTA") {}
+  void begin();
+
+  #define BATTERY_SAMPLES 8
+
+  uint16_t getBattMilliVolts() override {
+    analogReadResolution(12);
+
+    uint32_t raw = 0;
+    for (int i = 0; i < BATTERY_SAMPLES; i++) {
+      raw += analogRead(PIN_VBAT_READ);
+    }
+    raw = raw / BATTERY_SAMPLES;
+
+    return (ADC_MULTIPLIER * raw) / 4096;
+  }
+
+  const char* getManufacturerName() const override {
+    return "GAT562 Mesh Watch 13";
+  }
+
+
+  void powerOff() override {
+    uint32_t button_pin = PIN_BUTTON1;
+    nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
+    nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
+    sd_power_system_off(); 
+  }
+
+};

+ 89 - 0
variants/gat562_mesh_watch13/platformio.ini

@@ -0,0 +1,89 @@
+[GAT562_Mesh_Watch13]
+extends = nrf52_base
+board = rak4631
+board_check = true
+build_flags = ${nrf52_base.build_flags}
+  ${sensor_base.build_flags}
+  -UENV_INCLUDE_GPS
+  -I variants/gat562_mesh_watch13
+  -D RAK_4631
+  -D RAK_BOARD
+  -D NRF52_POWER_MANAGEMENT
+  -D PIN_BOARD_SCL=14
+  -D PIN_BOARD_SDA=13
+  -D PIN_OLED_RESET=-1
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=19
+  -D SX126X_CURRENT_LIMIT=140
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D QSPIFLASH=1
+build_src_filter = ${nrf52_base.build_src_filter}
+  +<../variants/gat562_mesh_watch13>
+  +<helpers/ui/SSD1306Display.cpp>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/sensors>
+lib_deps =
+  ${nrf52_base.lib_deps}
+  ${sensor_base.lib_deps}
+  adafruit/Adafruit SSD1306 @ ^2.5.13
+
+
+;[env:GAT562_Mesh_Watch13_repeater]
+;extends = GAT562_Mesh_Watch13
+;build_flags =
+;  ${GAT562_Mesh_Watch13.build_flags}
+;  -D DISPLAY_CLASS=SSD1306Display
+;  -D ADVERT_NAME='"GAT562 Repeater"'
+;  -D ADVERT_LAT=0.0
+;  -D ADVERT_LON=0.0
+;  -D ADMIN_PASSWORD='"password"'
+;  -D MAX_NEIGHBOURS=50
+;;  -D MESH_PACKET_LOGGING=1
+;;  -D MESH_DEBUG=1
+;build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter}
+;  +<helpers/ui/SSD1306Display.cpp>
+;  +<../examples/simple_repeater>
+
+;[env:GAT562_Mesh_Watch13_room_server]
+;extends = GAT562_Mesh_Watch13
+;build_flags =
+;  ${GAT562_Mesh_Watch13.build_flags}
+;  -D DISPLAY_CLASS=SSD1306Display
+;  -D ADVERT_NAME='"GAT562 Room"'
+;  -D ADVERT_LAT=0.0
+;  -D ADVERT_LON=0.0
+;  -D ADMIN_PASSWORD='"password"'
+;  -D ROOM_PASSWORD='"hello"'
+;;  -D MESH_PACKET_LOGGING=1
+;;  -D MESH_DEBUG=1
+;build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter}
+;  +<helpers/ui/SSD1306Display.cpp>
+;  +<../examples/simple_room_server>
+
+[env:GAT562_Mesh_Watch13_companion_radio_ble]
+extends = GAT562_Mesh_Watch13
+board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
+board_upload.maximum_size = 712704
+build_flags =
+  ${GAT562_Mesh_Watch13.build_flags}
+  -I examples/companion_radio/ui-new
+  -D DISPLAY_CLASS=SSD1306Display
+  -D MAX_CONTACTS=350
+  -D MAX_GROUP_CHANNELS=40
+  -D BLE_PIN_CODE=123456
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+;  -D PIN_VIBRATION=36
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter}
+  +<helpers/nrf52/SerialBLEInterface.cpp>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/ui/GenericVibration.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${GAT562_Mesh_Watch13.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+

+ 50 - 0
variants/gat562_mesh_watch13/target.cpp

@@ -0,0 +1,50 @@
+#include <Arduino.h>
+#include "target.h"
+#include <helpers/ArduinoHelpers.h>
+
+GAT56MeshWatch13Board board;
+
+#ifndef PIN_USER_BTN
+  #define PIN_USER_BTN (-1)
+#endif
+
+
+#ifdef DISPLAY_CLASS
+  DISPLAY_CLASS display;
+  MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, true);
+#endif
+
+
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+VolatileRTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+EnvironmentSensorManager sensors;
+
+
+bool radio_init() {
+  rtc_clock.begin(Wire);
+  return radio.std_init(&SPI);
+}
+
+uint32_t radio_get_rng_seed() {
+  return radio.random(0x7FFFFFFF);
+}
+
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
+  radio.setFrequency(freq);
+  radio.setSpreadingFactor(sf);
+  radio.setBandwidth(bw);
+  radio.setCodingRate(cr);
+}
+
+void radio_set_tx_power(int8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);  // create new random identity
+}

+ 31 - 0
variants/gat562_mesh_watch13/target.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/radiolib/RadioLibWrappers.h>
+#include <GAT56MeshWatch13Board.h>
+#include <helpers/radiolib/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/sensors/EnvironmentSensorManager.h>
+
+#ifdef DISPLAY_CLASS
+  #include <helpers/ui/SSD1306Display.h>
+  extern DISPLAY_CLASS display;
+  #include <helpers/ui/MomentaryButton.h>
+  extern MomentaryButton user_btn;
+#endif
+
+#ifdef PIN_VIBRATION
+  #include <helpers/ui/GenericVibration.h>
+#endif
+
+extern GAT56MeshWatch13Board board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
+
+bool radio_init();
+uint32_t radio_get_rng_seed();
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
+void radio_set_tx_power(int8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 44 - 0
variants/gat562_mesh_watch13/variant.cpp

@@ -0,0 +1,44 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "variant.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+#include "nrf.h"
+
+const uint32_t g_ADigitalPinMap[] =
+{
+  // P0
+  0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,
+  8 , 9 , 10, 11, 12, 13, 14, 15,
+  16, 17, 18, 19, 20, 21, 22, 23,
+  24, 25, 26, 27, 28, 29, 30, 31,
+
+  // P1
+  32, 33, 34, 35, 36, 37, 38, 39,
+  40, 41, 42, 43, 44, 45, 46, 47
+};
+
+
+void initVariant()
+{
+
+}
+

+ 201 - 0
variants/gat562_mesh_watch13/variant.h

@@ -0,0 +1,201 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _VARIANT_RAK4630_
+#define _VARIANT_RAK4630_
+
+#define RAK4630
+
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO // Board uses 32khz crystal for LF
+// define USE_LFRC    // Board uses RC for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif // __cplusplus
+
+	/*
+ * WisBlock Base GPIO definitions
+ */
+	static const uint8_t WB_IO1 = 17;	   // SLOT_A SLOT_B
+	static const uint8_t WB_IO2 = 34;	   // SLOT_A SLOT_B
+	static const uint8_t WB_IO3 = 21;	   // SLOT_C
+	static const uint8_t WB_IO4 = 4;	   // SLOT_C
+	static const uint8_t WB_IO5 = 9;	   // SLOT_D
+	static const uint8_t WB_IO6 = 10;	   // SLOT_D
+	static const uint8_t WB_SW1 = 33;	   // IO_SLOT
+	static const uint8_t WB_A0 = 5;		   // IO_SLOT
+	static const uint8_t WB_A1 = 31;	   // IO_SLOT
+	static const uint8_t WB_I2C1_SDA = 13; // SENSOR_SLOT IO_SLOT
+	static const uint8_t WB_I2C1_SCL = 14; // SENSOR_SLOT IO_SLOT
+	static const uint8_t WB_I2C2_SDA = 24; // IO_SLOT
+	static const uint8_t WB_I2C2_SCL = 25; // IO_SLOT
+	static const uint8_t WB_SPI_CS = 26;   // IO_SLOT
+	static const uint8_t WB_SPI_CLK = 3;   // IO_SLOT
+	static const uint8_t WB_SPI_MISO = 29; // IO_SLOT
+	static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT
+
+// Number of pins defined in PinDescription array
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (6)
+#define NUM_ANALOG_OUTPUTS (0)
+
+// LEDs
+#define PIN_LED (-1)
+#define LED_BUILTIN PIN_LED
+#define LED_CONN    PIN_LED
+#define LED_GREEN   PIN_LED
+#define LED_BLUE    PIN_LED
+#define LED_STATE_ON 1 // State when LED is litted
+
+
+/*
+ * Buttons
+ */
+#define PIN_BUTTON1             (9) 
+#define PIN_BUTTON2             (10) 
+#define PIN_USER_BTN            PIN_BUTTON1
+#define PIN_BACK_BTN            PIN_BUTTON2
+
+
+// Analog pins
+#define  PIN_VBAT_READ          (5)
+#define  ADC_MULTIPLIER         (3 * 1.75 * 1.187 * 1000)
+
+
+/*
+ * Analog pins
+ */
+#define PIN_A0 (5)	//(3)
+#define PIN_A1 (31) //(4)
+#define PIN_A2 (28)
+#define PIN_A3 (29)
+#define PIN_A4 (30)
+#define PIN_A5 (31)
+#define PIN_A6 (0xff)
+#define PIN_A7 (0xff)
+
+	static const uint8_t A0 = PIN_A0;
+	static const uint8_t A1 = PIN_A1;
+	static const uint8_t A2 = PIN_A2;
+	static const uint8_t A3 = PIN_A3;
+	static const uint8_t A4 = PIN_A4;
+	static const uint8_t A5 = PIN_A5;
+	static const uint8_t A6 = PIN_A6;
+	static const uint8_t A7 = PIN_A7;
+#define ADC_RESOLUTION 14
+
+// Power management boot protection threshold (millivolts)
+// Set to 0 to disable boot protection
+#define PWRMGT_VOLTAGE_BOOTLOCK 3300   // Won't boot below this voltage (mV)
+// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
+// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
+#define PWRMGT_LPCOMP_AIN 3
+#define PWRMGT_LPCOMP_REFSEL 4  // 5/8 VDD (~3.13-3.44V)
+
+// Other pins
+#define PIN_AREF (2)
+#define PIN_NFC1 (9)
+#define PIN_NFC2 (10)
+
+	static const uint8_t AREF = PIN_AREF;
+
+/*
+ * Serial interfaces
+ */
+// TXD1 RXD1 on Base Board
+#define PIN_SERIAL1_RX (15)
+#define PIN_SERIAL1_TX (16)
+
+// TXD0 RXD0 on Base Board
+#define PIN_SERIAL2_RX (19)
+#define PIN_SERIAL2_TX (20)
+
+/*
+ * SPI Interfaces
+ */
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO (29)
+#define PIN_SPI_MOSI (30)
+#define PIN_SPI_SCK   (3)
+
+	static const uint8_t SS = 26;
+	static const uint8_t MOSI = PIN_SPI_MOSI;
+	static const uint8_t MISO = PIN_SPI_MISO;
+	static const uint8_t SCK = PIN_SPI_SCK;
+
+// LoRa radio module pins for RAK4631
+
+#define  SX126X_POWER_EN (37)
+#define  P_LORA_RESET    (38)
+#define  P_LORA_NSS      (42)
+#define  P_LORA_SCLK     (43)
+#define  P_LORA_MOSI     (44)
+#define  P_LORA_MISO     (45)
+#define  P_LORA_BUSY     (46)
+#define  P_LORA_DIO_1    (47)
+
+#define SX126X_DIO2_AS_RF_SWITCH  true
+#define SX126X_DIO3_TCXO_VOLTAGE   1.8
+
+/*
+ * Wire Interfaces
+ */
+#define WIRE_INTERFACES_COUNT 2
+
+#define PIN_WIRE_SDA (13)
+#define PIN_WIRE_SCL (14)
+
+#define PIN_WIRE1_SDA (24)
+#define PIN_WIRE1_SCL (25)
+
+// QSPI Pins
+// QSPI occupied by GPIO's
+#define PIN_QSPI_SCK 3	// 19
+#define PIN_QSPI_CS  26	// 17
+#define PIN_QSPI_IO0 30 // 20
+#define PIN_QSPI_IO1 29 // 21
+#define PIN_QSPI_IO2 28 // 22
+#define PIN_QSPI_IO3 2	// 23
+
+// On-board QSPI Flash
+#define EXTERNAL_FLASH_DEVICES W25Q16JV_IQ
+#define EXTERNAL_FLASH_USE_QSPI
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif

+ 1 - 1
variants/generic_espnow/platformio.ini

@@ -69,7 +69,7 @@ lib_deps =
 extends = Generic_ESPNOW
 build_flags =
   ${Generic_ESPNOW.build_flags}
-  -D ADVERT_NAME='"Heltec Room"'
+  -D ADVERT_NAME='"Generic ESPNow Room"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADMIN_PASSWORD='"password"'

+ 66 - 0
variants/muziworks_r1_neo/R1NeoBoard.cpp

@@ -0,0 +1,66 @@
+#include <Arduino.h>
+#include <Wire.h>
+
+#include "R1NeoBoard.h"
+
+#ifdef NRF52_POWER_MANAGEMENT
+// Static configuration for power management
+// Values set in variant.h defines
+const PowerMgtConfig power_config = {
+  .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
+  .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
+  .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
+};
+
+void R1NeoBoard::initiateShutdown(uint8_t reason) {
+  // Disable LoRa module power before shutdown
+  MESH_DEBUG_PRINTLN("R1Neo: shutting down");
+  digitalWrite(SX126X_POWER_EN, LOW);
+
+  if (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
+      reason == SHUTDOWN_REASON_BOOT_PROTECT) {
+    configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
+  }
+
+  enterSystemOff(reason);
+}
+#endif // NRF52_POWER_MANAGEMENT
+
+void R1NeoBoard::begin() {
+  // R1 Neo peculiarity: tell DCDC converter to stay powered.
+  // Must be done as soon as practical during boot.
+
+  pinMode(PIN_DCDC_EN_MCU_HOLD, OUTPUT);
+  digitalWrite(PIN_DCDC_EN_MCU_HOLD, HIGH);
+
+  // R1 Neo peculiarity: Tell I/O Controller device is on
+  // Enables passthrough of buttons and LEDs
+
+  pinMode(PIN_SOFT_SHUTDOWN, OUTPUT);
+  digitalWrite(PIN_SOFT_SHUTDOWN, HIGH);
+
+  NRF52BoardDCDC::begin();
+
+  // button is active high and passed through from I/O controller
+  pinMode(PIN_USER_BTN, INPUT);
+
+  pinMode(PIN_BUZZER, OUTPUT);
+  digitalWrite(PIN_BUZZER, LOW);
+
+  // battery pins
+  pinMode(PIN_BAT_CHG, INPUT);
+  pinMode(PIN_VBAT_READ, INPUT);
+
+  Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
+
+  Wire.begin();
+
+  pinMode(SX126X_POWER_EN, OUTPUT);
+#ifdef NRF52_POWER_MANAGEMENT
+  // Boot voltage protection check (may not return if voltage too low)
+  // We need to call this after we configure SX126X_POWER_EN as output but before we pull high
+  checkBootVoltage(&power_config);
+#endif
+  digitalWrite(SX126X_POWER_EN, HIGH);
+  delay(10);   // give sx1262 some time to power up
+}

+ 56 - 0
variants/muziworks_r1_neo/R1NeoBoard.h

@@ -0,0 +1,56 @@
+#pragma once
+
+#include <MeshCore.h>
+#include <Arduino.h>
+#include <helpers/NRF52Board.h>
+#include "NullDisplayDriver.h"
+#include "MomentaryButton.h"
+
+#define DISPLAY_CLASS NullDisplayDriver
+
+class R1NeoBoard : public NRF52BoardDCDC {
+protected:
+#ifdef NRF52_POWER_MANAGEMENT
+  void initiateShutdown(uint8_t reason) override;
+#endif
+
+public:
+  R1NeoBoard() : NRF52Board("R1NEO_OTA") {}
+  void begin();
+
+#if defined(P_LORA_TX_LED)
+  void onBeforeTransmit() override {
+    digitalWrite(P_LORA_TX_LED, HIGH);   // turn TX LED on
+    #if defined(LED_BLUE)
+       // turn off that annoying blue LED before transmitting
+       digitalWrite(LED_BLUE, LOW);
+    #endif
+  }
+  void onAfterTransmit() override {
+    digitalWrite(P_LORA_TX_LED, LOW);   // turn TX LED off
+    #if defined(LED_BLUE)
+       // do it after transmitting too, just in case
+       digitalWrite(LED_BLUE, LOW);
+    #endif
+  }
+#endif
+
+  #define BATTERY_SAMPLES 8
+
+  uint16_t getBattMilliVolts() override {
+    MESH_DEBUG_PRINTLN("R1Neo: Sampling battery");
+    analogReadResolution(12);
+
+    uint32_t raw = 0;
+    for (int i = 0; i < BATTERY_SAMPLES; i++) {
+      raw += analogRead(PIN_VBAT_READ);
+    }
+    raw = raw / BATTERY_SAMPLES;
+
+    return (ADC_MULTIPLIER * raw) / 4096;
+  }
+
+  const char* getManufacturerName() const override {
+    return "muzi works R1 Neo";
+  }
+};

+ 132 - 0
variants/muziworks_r1_neo/platformio.ini

@@ -0,0 +1,132 @@
+[R1Neo]
+extends = nrf52_base
+board = rak4631
+board_check = true
+build_flags = ${nrf52_base.build_flags}
+  ${sensor_base.build_flags}
+  -I variants/muziworks_r1_neo
+  -I src/helpers/ui
+  -D R1Neo
+  -D NRF52_POWER_MANAGEMENT
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D SX126X_CURRENT_LIMIT=140
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D PIN_BUZZER=3
+  -D PIN_USER_BTN=26
+  -D USER_BTN_PRESSED=HIGH
+  -D PIN_GPS_TX=25
+  -D PIN_GPS_RX=24
+  -D PIN_GPS_EN=33
+build_src_filter = ${nrf52_base.build_src_filter}
+  +<../variants/muziworks_r1_neo>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/ui/NullDisplayDriver.cpp>
+  +<helpers/sensors>
+lib_deps =
+  ${nrf52_base.lib_deps}
+  ${sensor_base.lib_deps}
+  sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27
+
+[env:R1Neo_repeater]
+extends = R1Neo
+build_flags =
+  ${R1Neo.build_flags}
+  -D ADVERT_NAME='"R1 Neo Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=50
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<../examples/simple_repeater>
+
+[env:R1Neo_room_server]
+extends = R1Neo
+build_flags =
+  ${R1Neo.build_flags}
+  -D ADVERT_NAME='"R1 Neo Test Room"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<../examples/simple_room_server>
+
+[env:R1Neo_companion_radio_usb]
+extends = R1Neo
+board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
+board_upload.maximum_size = 712704
+build_flags =
+  ${R1Neo.build_flags}
+  -I examples/companion_radio/ui-orig
+  -D MAX_CONTACTS=350
+  -D MAX_GROUP_CHANNELS=40
+; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
+; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<helpers/ui/buzzer.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-orig/*.cpp>
+lib_deps =
+  ${R1Neo.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+  end2endzone/NonBlockingRTTTL@^1.3.0
+
+[env:R1Neo_companion_radio_ble]
+extends = R1Neo
+board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
+board_upload.maximum_size = 712704
+build_flags =
+  ${R1Neo.build_flags}
+  -I examples/companion_radio/ui-orig
+  -D ENV_INCLUDE_GPS=1
+  -D MAX_CONTACTS=350
+  -D MAX_GROUP_CHANNELS=40
+  -D BLE_PIN_CODE=123456
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<helpers/ui/buzzer.cpp>
+  +<helpers/nrf52/SerialBLEInterface.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-orig/*.cpp>
+lib_deps =
+  ${R1Neo.lib_deps}
+  ${rak4631.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+  end2endzone/NonBlockingRTTTL@^1.3.0
+
+[env:R1Neo_terminal_chat]
+extends = R1Neo
+build_flags =
+  ${R1Neo.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<../examples/simple_secure_chat/main.cpp>
+lib_deps =
+  ${R1Neo.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:R1Neo_sensor]
+extends = R1Neo
+build_flags =
+  ${R1Neo.build_flags}
+  -D DISPLAY_CLASS=SSD1306Display
+  -D ADVERT_NAME='"R1 Neo Sensor"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+;  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+build_src_filter = ${R1Neo.build_src_filter}
+  +<../examples/simple_sensor>

+ 47 - 0
variants/muziworks_r1_neo/target.cpp

@@ -0,0 +1,47 @@
+#include <Arduino.h>
+#include "target.h"
+#include <helpers/ArduinoHelpers.h>
+
+R1NeoBoard board;
+
+DISPLAY_CLASS display;
+
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+VolatileRTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+#if ENV_INCLUDE_GPS
+  #include <helpers/sensors/MicroNMEALocationProvider.h>
+  MicroNMEALocationProvider nmea    = MicroNMEALocationProvider(Serial1, &rtc_clock);
+  EnvironmentSensorManager  sensors = EnvironmentSensorManager(nmea);
+#else
+  EnvironmentSensorManager sensors;
+#endif
+
+bool radio_init() {
+  rtc_clock.begin(Wire);
+  return radio.std_init(&SPI);
+}
+
+uint32_t radio_get_rng_seed() {
+  return radio.random(0x7FFFFFFF);
+}
+
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
+  radio.setFrequency(freq);
+  radio.setSpreadingFactor(sf);
+  radio.setBandwidth(bw);
+  radio.setCodingRate(cr);
+}
+
+void radio_set_tx_power(int8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);  // create new random identity
+}

+ 22 - 0
variants/muziworks_r1_neo/target.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/radiolib/RadioLibWrappers.h>
+#include <R1NeoBoard.h>
+#include <helpers/radiolib/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/sensors/EnvironmentSensorManager.h>
+
+extern R1NeoBoard board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
+extern DISPLAY_CLASS display;
+extern MomentaryButton user_btn;
+
+bool radio_init();
+uint32_t radio_get_rng_seed();
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
+void radio_set_tx_power(int8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 92 - 0
variants/muziworks_r1_neo/variant.cpp

@@ -0,0 +1,92 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "variant.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+#include "nrf.h"
+
+const uint32_t g_ADigitalPinMap[] =
+{
+  // P0
+  0,  // P0.00 (NC) (XTAL)
+  1,  // P0.01 (NC) (XTAL)
+  2,  // P0.02 (30) GPS_PPS
+  3,  // P0.03 (29) BUZZER_DRIVE
+  4,  // P0.04 (41) NC
+  5,  // P0.05 (40) NC
+  6,  // P0.06 (NC) NOT_PRESENT
+  7,  // P0.07 (NC) (TRACECLK)
+  8,  // P0.08 (NC) NOT_PRESENT
+  9,  // P0.09 (13) NC
+  10, // P0.10 (12) NC
+  11, // P0.11 (NC) NOT_PRESENT
+  12, // P0.12 (NC) NOT_PRESENT
+  13, // P0.13 (04) DCDC_EN_MCU_HOLD
+  14, // P0.14 (05) NC
+  15, // P0.15 (06) NC
+  16, // P0.16 (07) NC
+  17, // P0.17 (08) NC
+  18, // P0.18 (17) !RESET
+  19, // P0.19 (09) RTC_SDA
+  20, // P0.20 (10) RTC_SCL
+  21, // P0.21 (11) NC
+  22, // P0.22 (NC) NOT_PRESENT
+  23, // P0.23 (NC) NOT_PRESENT
+  24, // P0.24 (23) UART_GPS_RX
+  25, // P0.25 (24) UART_GPS_TX
+  26, // P0.26 (26) BTN_OK/USR_BTN_PROCESSED
+  27, // P0.27 (NC) NOT_PRESENT
+  28, // P0.28 (31) BLU_LED_RAK
+  29, // P0.29 (32) SOFT_SHUTDOWN_SIGNAL
+  30, // P0.30 (33) MCU_SIGNAL
+  31, // P0.31 (39) ADC_VBAT
+
+  // P1
+  32, // P1.00 (NC) NOT_PRESENT
+  33, // P1.01 (25) GPS_EN
+  34, // P1.02 (26) BAT_CHG_STATUS
+  35, // P1.03 (27) NC
+  36, // P1.04 (28) GRN_LED_RAK
+  37, // P1.05 (SX) SX126X_POWER_EN
+  38, // P1.06 (SX) P_LORA_RESET
+  39, // P1.07 (NC) NOT_PRESENT
+  40, // P1.08 (NC) NOT_PRESENT
+  41, // P1.09 (NC) NOT_PRESENT
+  42, // P1.10 (SX) P_LORA_NSS
+  43, // P1.11 (SX) P_LORA_SCLK
+  44, // P1.12 (SX) P_LORA_MOSI
+  45, // P1.13 (SX) P_LORA_MISO
+  46, // P1.14 (SX) P_LORA_BUSY
+  47  // P1.15 (SX) P_LORA_DIO_1
+};
+
+
+void initVariant()
+{
+  // Red & Green LEDs - enable & turn off
+  pinMode(LED_GREEN, OUTPUT);
+  ledOff(LED_GREEN);
+
+  pinMode(LED_BLUE, OUTPUT);
+  ledOff(LED_BLUE);
+
+  pinMode(PIN_GPS_EN, OUTPUT);
+}

+ 183 - 0
variants/muziworks_r1_neo/variant.h

@@ -0,0 +1,183 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _VARIANT_R1NEO_
+#define _VARIANT_R1NEO_
+
+#define RAK4630
+
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO // Board uses 32khz crystal for LF
+// define USE_LFRC    // Board uses RC for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif // __cplusplus
+
+/* Number of pins defined in PinDescription array */
+#define PINS_COUNT         (48)
+#define NUM_DIGITAL_PINS   (48)
+#define NUM_ANALOG_INPUTS   (8)
+#define NUM_ANALOG_OUTPUTS  (0)
+
+/* R1Neo peculiarities */
+#define PIN_DCDC_EN_MCU_HOLD (13) // P0.13 (04) DCDC_EN_MCU_HOLD
+#define PIN_SOFT_SHUTDOWN    (29) // P0.29 (32) SOFT_SHUTDOWN_SIGNAL
+#define PIN_MCU_SIGNAL       (30) // P0.30 (33) MCU_SIGNAL
+
+/* R1Neo LoRa Radio */
+// RAK4630/4631 pins
+
+#define  P_LORA_DIO_1    (47)        // P1.15 (SX)
+#define  P_LORA_NSS      (42)        // P1.10 (SX)
+#define  P_LORA_RESET    RADIOLIB_NC // P1.06 (SX) -- 38
+#define  P_LORA_BUSY     (46)        // P1.14 (SX)
+#define  P_LORA_SCLK     (43)        // P1.11 (SX)
+#define  P_LORA_MISO     (45)        // P1.13 (SX)
+#define  P_LORA_MOSI     (44)        // P1.12 (SX)
+#define  SX126X_POWER_EN (37)        // P1.05 (SX)
+
+#define SX126X_DIO2_AS_RF_SWITCH  true
+#define SX126X_DIO3_TCXO_VOLTAGE   1.8
+
+/* R1Neo peripherals */
+/* GPS */
+#define GPS_RX      (24) // P0.24 (23) UART_GPS_RX
+#define GPS_TX      (25) // P0.25 (24) UART_GPS_TX
+#define GPS_EN      (33) // P1.01 (25) GPS_EN
+#define GPS_PPS      (2) // P0.02 (30) GPS_PPS
+
+#define PIN_GPS_1PPS  GPS_PPS
+#define GPS_BAUD_RATE 9600
+
+/* RTC */
+#define RTC_SDA     (19) // P0.19  (9) RTC_SDA
+#define RTC_SCL     (20) // P0.20 (10) RTC_SCL
+
+/* LEDs */
+#define LED_GREEN   (36) // P1.04 (28) GRN_LED_RAK
+#define LED_BLUE    (28) // P0.28 (31) BLU_LED_RAK
+
+#define LED_BUILTIN (0xFF)
+
+#ifndef P_LORA_TX_LED
+   #define P_LORA_TX_LED LED_GREEN
+#endif
+
+#define LED_STATE_ON 1 // State when LED is lit
+
+/* Buttons */
+#define PIN_USER_BTN (26)
+
+/* Buzzer */
+#define PIN_BUZZER    (3)
+
+/* Analog pins */
+// Arduino makes me angry
+#define PIN_A0 (0xFF)  // NOT_PRESENT
+#define PIN_A1 (0xFF)  // NOT_PRESENT
+#define PIN_A2    (4)  // P0.04 (41) NC
+#define PIN_A3    (5)  // P0.05 (40) NC
+#define PIN_A4 (0xFF)  // NOT_PRESENT
+#define PIN_A5 (0xFF)  // NOT_PRESENT
+#define PIN_A6 (0xFF)  // NOT_PRESENT
+#define PIN_A7   (31)  // P0.31 (39) ADC_VBAT
+
+   static const uint8_t A0 = PIN_A0;
+   static const uint8_t A1 = PIN_A1;
+   static const uint8_t A2 = PIN_A2;
+   static const uint8_t A3 = PIN_A3;
+   static const uint8_t A4 = PIN_A4;
+   static const uint8_t A5 = PIN_A5;
+   static const uint8_t A6 = PIN_A6;
+   static const uint8_t A7 = PIN_A7;
+#define ADC_RESOLUTION 14
+
+// Other pins
+#define PIN_AREF (0xFF) // No analog reference
+
+   static const uint8_t AREF = PIN_AREF;
+
+/* Serial interfaces */
+#define PIN_GPS_TX (GPS_TX)
+#define PIN_GPS_RX (GPS_RX)
+#define PIN_GPS_EN (GPS_EN)
+
+#define PIN_SERIAL1_TX (PIN_GPS_TX)
+#define PIN_SERIAL1_RX (PIN_GPS_RX)
+
+/* SPI Interfaces */
+// unused pins - define anyways
+#define SPI_INTERFACES_COUNT 1
+#define PIN_SPI_MOSI  (9) // P0.09 (13) NC  
+#define PIN_SPI_MISO (10) // P0.10 (12) NC
+#define PIN_SPI_SCK  (21) // P0.21 (11) NC
+
+/* I2C Interfaces */
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_WIRE_SDA (RTC_SDA)
+#define PIN_WIRE_SCL (RTC_SCL)
+
+/* QSPI Pins */
+// interface occupied by peripherals, define anyways
+#define PIN_QSPI_SCK  (3) // P0.03 (29) BUZZER
+#define PIN_QSPI_CS  (26) // P0.26 (34) USER_BUTTON
+#define PIN_QSPI_IO0 (30) // P0.30 (33) MCU_SIGNAL
+#define PIN_QSPI_IO1 (29) // P0.29 (32) SOFT_SHUTDOWN
+#define PIN_QSPI_IO2 (28) // P0.28 (31) BLU_LED_RAK
+#define PIN_QSPI_IO3  (2) // P0.02 (30) GPS_PPS
+
+/* On-board QSPI Flash */
+// No QSPI (define anyways)
+#define EXTERNAL_FLASH_DEVICES IS25LP080D
+#define EXTERNAL_FLASH_USE_QSPI
+
+/* Battery */
+#define PIN_VBAT_READ (31) // P0.31 (39) ADC_VBAT
+#define PIN_BAT_CHG   (34) // P1.02 (26) BAT_CHG_STATUS
+
+#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
+
+// Power management boot protection threshold (millivolts)
+// Set to 0 to disable boot protection
+// disabled for now until I can figure this out
+#define PWRMGT_VOLTAGE_BOOTLOCK 0   // Won't boot below this voltage (mV)
+// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
+// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
+#define PWRMGT_LPCOMP_AIN    5
+#define PWRMGT_LPCOMP_REFSEL 4  // 5/8 VDD (~3.13-3.44V)
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#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>

+ 4 - 4
variants/thinknode_m5/platformio.ini

@@ -58,7 +58,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter}
   +<../examples/simple_repeater/*.cpp>
 build_flags =
   ${ThinkNode_M5.build_flags}
-  -D ADVERT_NAME='"Thinknode M2 Repeater"'
+  -D ADVERT_NAME='"Thinknode M5 Repeater"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADMIN_PASSWORD='"password"'
@@ -116,7 +116,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter}
   +<../examples/simple_room_server>
 build_flags =
   ${ThinkNode_M5.build_flags}
-  -D ADVERT_NAME='"Thinknode M2 Room Server"'
+  -D ADVERT_NAME='"Thinknode M5 Room Server"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADMIN_PASSWORD='"password"'
@@ -193,8 +193,8 @@ build_flags =
   -D MAX_GROUP_CHANNELS=40
   -D OFFLINE_QUEUE_SIZE=256
   -D WIFI_DEBUG_LOGGING=1
-  -D WIFI_SSID='"Livebox-633C"'
-  -D WIFI_PWD='"vvQUHGSxsWd7fKMYSr"'
+  -D WIFI_SSID='"myssid"'
+  -D WIFI_PWD='"mypwd"'
 build_src_filter = ${ThinkNode_M5.build_src_filter}
   +<helpers/esp32/*.cpp>
   +<helpers/ui/MomentaryButton.cpp>