فهرست منبع

Merge branch 'dev' into double-acks

Scott Powell 1 سال پیش
والد
کامیت
7bec45b3dd
61فایلهای تغییر یافته به همراه2683 افزوده شده و 176 حذف شده
  1. 59 0
      boards/minewsemi_me25ls01.json
  2. 60 0
      boards/seeed_sensecap_solar.json
  3. 11 7
      examples/companion_radio/MyMesh.cpp
  4. 2 2
      examples/companion_radio/MyMesh.h
  5. 2 2
      examples/simple_repeater/main.cpp
  6. 2 2
      examples/simple_room_server/main.cpp
  7. 14 0
      platformio.ini
  8. 1 1
      src/Mesh.cpp
  9. 53 0
      src/helpers/CustomLR1110.h
  10. 1 1
      src/helpers/CustomLR1110Wrapper.h
  11. 4 2
      src/helpers/HeltecV3Board.h
  12. 81 0
      src/helpers/MeshadventurerBoard.h
  13. 91 0
      src/helpers/nrf52/MinewsemiME25LS01Board.cpp
  14. 88 0
      src/helpers/nrf52/MinewsemiME25LS01Board.h
  15. 61 46
      src/helpers/nrf52/SerialBLEInterface.cpp
  16. 6 7
      src/helpers/nrf52/SerialBLEInterface.h
  17. 30 0
      src/helpers/rp2040/XiaoRP2040Board.cpp
  18. 75 0
      src/helpers/rp2040/XiaoRP2040Board.h
  19. 61 16
      src/helpers/sensors/EnvironmentSensorManager.cpp
  20. 2 0
      src/helpers/sensors/EnvironmentSensorManager.h
  21. 116 0
      src/helpers/ui/E213Display.cpp
  22. 37 0
      src/helpers/ui/E213Display.h
  23. 3 30
      variants/heltec_tracker/target.cpp
  24. 1 1
      variants/heltec_v2/platformio.ini
  25. 4 4
      variants/heltec_v3/platformio.ini
  26. 84 0
      variants/heltec_wireless_paper/platformio.ini
  27. 45 0
      variants/heltec_wireless_paper/target.cpp
  28. 27 0
      variants/heltec_wireless_paper/target.h
  29. 3 36
      variants/lilygo_t3s3/target.cpp
  30. 18 1
      variants/lilygo_tbeam_SX1262/platformio.ini
  31. 234 0
      variants/meshadventurer/platformio.ini
  32. 119 0
      variants/meshadventurer/target.cpp
  33. 46 0
      variants/meshadventurer/target.h
  34. 44 0
      variants/meshadventurer/variant.h
  35. 24 0
      variants/minewsemi_me25ls01/NullDisplayDriver.h
  36. 165 0
      variants/minewsemi_me25ls01/platformio.ini
  37. 98 0
      variants/minewsemi_me25ls01/target.cpp
  38. 29 0
      variants/minewsemi_me25ls01/target.h
  39. 70 0
      variants/minewsemi_me25ls01/variant.cpp
  40. 94 0
      variants/minewsemi_me25ls01/variant.h
  41. 1 1
      variants/nano_g2_ultra/platformio.ini
  42. 1 1
      variants/rak3x72/target.cpp
  43. 8 2
      variants/rak4631/platformio.ini
  44. 81 0
      variants/sensecap_solar/SenseCapSolarBoard.cpp
  45. 42 0
      variants/sensecap_solar/SenseCapSolarBoard.h
  46. 75 0
      variants/sensecap_solar/platformio.ini
  47. 39 0
      variants/sensecap_solar/target.cpp
  48. 21 0
      variants/sensecap_solar/target.h
  49. 69 0
      variants/sensecap_solar/variant.cpp
  50. 85 0
      variants/sensecap_solar/variant.h
  51. 3 2
      variants/t1000-e/target.cpp
  52. 1 1
      variants/wio-e5-dev/target.cpp
  53. 1 1
      variants/wio-e5-mini/target.cpp
  54. 90 3
      variants/xiao_c3/platformio.ini
  55. 0 0
      variants/xiao_nrf52/XiaoNrf52Board.cpp
  56. 3 4
      variants/xiao_nrf52/XiaoNrf52Board.h
  57. 1 2
      variants/xiao_nrf52/platformio.ini
  58. 1 1
      variants/xiao_nrf52/target.h
  59. 104 0
      variants/xiao_rp2040/platformio.ini
  60. 71 0
      variants/xiao_rp2040/target.cpp
  61. 21 0
      variants/xiao_rp2040/target.h

+ 59 - 0
boards/minewsemi_me25ls01.json

@@ -0,0 +1,59 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "nrf52840_s140_v7.ld"
+    },
+    "core": "nRF5",
+    "cpu": "cortex-m4",
+    "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+    "f_cpu": "64000000L",
+    "hwids": [
+      ["0x239A", "0x8029"],
+      ["0x239A", "0x0029"],
+      ["0x239A", "0x002A"],
+      ["0x239A", "0x802A"]
+    ],
+    "usb_product": "me25ls01-BOOT",
+    "mcu": "nrf52840",
+    "variant": "minewsemi_me25ls01",
+    "bsp": {
+      "name": "adafruit"
+    },
+    "softdevice": {
+      "sd_flags": "-DS140",
+      "sd_name": "s140",
+      "sd_version": "7.3.0",
+      "sd_fwid": "0x0123"
+    },
+    "bootloader": {
+      "settings_addr": "0xFF000"
+    }
+  },
+  "connectivity": ["bluetooth"],
+  "debug": {
+    "jlink_device": "nRF52840_xxAA",
+    "svd_path": "nrf52840.svd",
+    "openocd_target": "nrf52.cfg"
+  },
+  "frameworks": ["arduino"],
+  "name": "Minewsemi ME25LS01",
+  "upload": {
+    "maximum_ram_size": 248832,
+    "maximum_size": 815104,
+    "speed": 115200,
+    "protocol": "nrfutil",
+    "protocols": [
+      "jlink",
+      "nrfjprog",
+      "nrfutil",
+      "stlink",
+      "cmsis-dap",
+      "blackmagic"
+    ],
+    "use_1200bps_touch": true,
+    "require_upload_port": true,
+    "wait_for_upload_port": true
+  },
+  "url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01",
+  "vendor": "MINEWSEMI"
+}

+ 60 - 0
boards/seeed_sensecap_solar.json

@@ -0,0 +1,60 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "nrf52840_s140_v7.ld"
+    },
+    "core": "nRF5",
+    "cpu": "cortex-m4",
+    "extra_flags": "-DARDUINO_Seeed_XIAO_nRF52840 -DNRF52840_XXAA -DSEEED_XIAO_NRF52840 ",
+    "f_cpu": "64000000L",
+    "hwids": [
+      [ "0x2886", "0x0059" ]
+    ],
+    "mcu": "nrf52840",
+    "variant": "Seeed_XIAO_nRF52840",
+    "softdevice": {
+      "sd_flags": "-DS140",
+      "sd_name": "s140",
+      "sd_version": "7.3.0",
+      "sd_fwid": "0x0123"
+    },
+    "bsp": {
+      "name": "adafruit"
+    },
+    "bootloader": {
+      "settings_addr": "0xFF000"
+    },
+    "usb_product": "XIAO nRF52840"
+  },
+  "connectivity": [
+    "bluetooth"
+  ],
+  "debug": {
+    "jlink_device": "nRF52840_xxAA",
+    "openocd_target": "nrf52.cfg",
+    "svd_path": "nrf52840.svd"
+  },
+  "frameworks": [
+    "arduino"
+  ],
+  "name": "Seeed Studio XIAO nRF52840",
+  "upload": {
+    "maximum_ram_size": 248832,
+    "maximum_size": 815104,
+    "protocol": "nrfutil",
+    "speed": 115200,
+    "protocols": [
+      "jlink",
+      "nrfjprog",
+      "nrfutil",
+      "cmsis-dap",
+      "sam-ba",
+      "blackmagic"
+    ],
+    "use_1200bps_touch": true,
+    "require_upload_port": true,
+    "wait_for_upload_port": true
+  },
+  "url": "https://wiki.seeedstudio.com/meshtastic_solar_node/",
+  "vendor": "Seeed Studio"
+}

+ 11 - 7
examples/companion_radio/MyMesh.cpp

@@ -319,13 +319,17 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
     uint8_t frame[1];
     frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
     _serial->writeFrame(frame, 1);
-  } else {
-#ifdef DISPLAY_CLASS
-    ui_task.soundBuzzer(UIEventType::contactMessage);
-#endif
   }
+
 #ifdef DISPLAY_CLASS
-  ui_task.newMsg(path_len, from.name, text, offline_queue_len);
+  // we only want to show text messages on display, not cli data
+  bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
+  if (should_display) {
+    ui_task.newMsg(path_len, from.name, text, offline_queue_len);
+    if (!_serial->isConnected()) {
+      ui_task.soundBuzzer(UIEventType::contactMessage);
+    }
+  }
 #endif
 }
 
@@ -613,10 +617,10 @@ void MyMesh::begin(bool has_display) {
   _prefs.cr = constrain(_prefs.cr, 5, 8);
   _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
 
-#ifdef BLE_PIN_CODE
+#ifdef BLE_PIN_CODE // 123456 by default
   if (_prefs.ble_pin == 0) {
 #ifdef DISPLAY_CLASS
-    if (has_display) {
+    if (has_display && BLE_PIN_CODE == 123456) {
       StdRNG rng;
       _active_ble_pin = rng.nextInt(100000, 999999); // random pin each session
     } else {

+ 2 - 2
examples/companion_radio/MyMesh.h

@@ -10,11 +10,11 @@
 #define FIRMWARE_VER_CODE 6
 
 #ifndef FIRMWARE_BUILD_DATE
-#define FIRMWARE_BUILD_DATE "29 Jun 2025"
+#define FIRMWARE_BUILD_DATE "2 Jul 2025"
 #endif
 
 #ifndef FIRMWARE_VERSION
-#define FIRMWARE_VERSION "v1.7.1"
+#define FIRMWARE_VERSION "v1.7.2"
 #endif
 
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

+ 2 - 2
examples/simple_repeater/main.cpp

@@ -22,11 +22,11 @@
 /* ------------------------------ Config -------------------------------- */
 
 #ifndef FIRMWARE_BUILD_DATE
-  #define FIRMWARE_BUILD_DATE   "29 Jun 2025"
+  #define FIRMWARE_BUILD_DATE   "2 Jul 2025"
 #endif
 
 #ifndef FIRMWARE_VERSION
-  #define FIRMWARE_VERSION   "v1.7.1"
+  #define FIRMWARE_VERSION   "v1.7.2"
 #endif
 
 #ifndef LORA_FREQ

+ 2 - 2
examples/simple_room_server/main.cpp

@@ -22,11 +22,11 @@
 /* ------------------------------ Config -------------------------------- */
 
 #ifndef FIRMWARE_BUILD_DATE
-  #define FIRMWARE_BUILD_DATE   "29 Jun 2025"
+  #define FIRMWARE_BUILD_DATE   "2 Jul 2025"
 #endif
 
 #ifndef FIRMWARE_VERSION
-  #define FIRMWARE_VERSION   "v1.7.1"
+  #define FIRMWARE_VERSION   "v1.7.2"
 #endif
 
 #ifndef LORA_FREQ

+ 14 - 0
platformio.ini

@@ -29,6 +29,20 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
   -D LORA_SF=11
   -D ENABLE_PRIVATE_KEY_IMPORT=1   ; NOTE: comment these out for more secure firmware
   -D ENABLE_PRIVATE_KEY_EXPORT=1
+  -D RADIOLIB_EXCLUDE_CC1101=1
+  -D RADIOLIB_EXCLUDE_RF69=1
+  -D RADIOLIB_EXCLUDE_SX1231=1
+  -D RADIOLIB_EXCLUDE_SI443X=1
+  -D RADIOLIB_EXCLUDE_RFM2X=1
+  -D RADIOLIB_EXCLUDE_SX128X=1
+  -D RADIOLIB_EXCLUDE_AFSK=1
+  -D RADIOLIB_EXCLUDE_AX25=1
+  -D RADIOLIB_EXCLUDE_HELLSCHREIBER=1
+  -D RADIOLIB_EXCLUDE_MORSE=1
+  -D RADIOLIB_EXCLUDE_APRS=1
+  -D RADIOLIB_EXCLUDE_BELL=1
+  -D RADIOLIB_EXCLUDE_RTTY=1
+  -D RADIOLIB_EXCLUDE_SSTV=1
 build_src_filter =
   +<*.cpp>
   +<helpers/*.cpp>

+ 1 - 1
src/Mesh.cpp

@@ -136,7 +136,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
                 int k = 0;
                 uint8_t path_len = data[k++];
                 uint8_t* path = &data[k]; k += path_len;
-                uint8_t extra_type = data[k++];
+                uint8_t extra_type = data[k++] & 0x0F;   // upper 4 bits reserved for future use
                 uint8_t* extra = &data[k];
                 uint8_t extra_len = len - k;   // remainder of packet (may be padded with zeroes!)
                 if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) {

+ 53 - 0
src/helpers/CustomLR1110.h

@@ -9,6 +9,59 @@ class CustomLR1110 : public LR1110 {
   public:
     CustomLR1110(Module *mod) : LR1110(mod) { }
 
+    RadioLibTime_t getTimeOnAir(size_t len) override {
+  // calculate number of symbols
+  float N_symbol = 0;
+  if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) {
+    // legacy coding rate - nice and simple
+    // get SF coefficients
+    float coeff1 = 0;
+    int16_t coeff2 = 0;
+    int16_t coeff3 = 0;
+    if(this->spreadingFactor < 7) {
+      // SF5, SF6
+      coeff1 = 6.25;
+      coeff2 = 4*this->spreadingFactor;
+      coeff3 = 4*this->spreadingFactor;
+    } else if(this->spreadingFactor < 11) {
+      // SF7. SF8, SF9, SF10
+      coeff1 = 4.25;
+      coeff2 = 4*this->spreadingFactor + 8;
+      coeff3 = 4*this->spreadingFactor;
+    } else {
+      // SF11, SF12
+      coeff1 = 4.25;
+      coeff2 = 4*this->spreadingFactor + 8;
+      coeff3 = 4*(this->spreadingFactor - 2);
+    }
+
+    // get CRC length
+    int16_t N_bitCRC = 16;
+    if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) {
+      N_bitCRC = 0;
+    }
+
+    // get header length
+    int16_t N_symbolHeader = 20;
+    if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) {
+      N_symbolHeader = 0;
+    }
+
+    // calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols
+    // uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
+
+    // calculate the number of symbols - nope
+    // N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
+    // calculate the number of symbols - using only preamblelora because it's already in symbols
+    N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
+  } else {
+    // long interleaving - not needed for this modem
+  }
+
+  // get time-on-air in us
+  return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f);
+}
+
     bool isReceiving() {
       uint16_t irq = getIrqStatus();
       bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE));

+ 1 - 1
src/helpers/CustomLR1110Wrapper.h

@@ -17,7 +17,7 @@ public:
 
   void onSendFinished() override {
     RadioLibWrapper::onSendFinished();
-    _radio->setPreambleLength(8); // overcomes weird issues with small and big pkts
+    _radio->setPreambleLength(16); // overcomes weird issues with small and big pkts
   }
 
   float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }

+ 4 - 2
src/helpers/HeltecV3Board.h

@@ -4,7 +4,7 @@
 #include <helpers/RefCountedDigitalPin.h>
 
 // LoRa radio module pins for Heltec V3
-// Also for Heltec Wireless Tracker
+// Also for Heltec Wireless Tracker/Paper
 #define  P_LORA_DIO_1   14
 #define  P_LORA_NSS      8
 #define  P_LORA_RESET   RADIOLIB_NC
@@ -14,7 +14,9 @@
 #define  P_LORA_MOSI    10
 
 // built-ins
-#define  PIN_VBAT_READ    1
+#ifndef PIN_VBAT_READ              // set in platformio.ini for boards like Heltec Wireless Paper (20)
+  #define  PIN_VBAT_READ    1
+#endif
 #ifndef PIN_ADC_CTRL              // set in platformio.ini for Heltec Wireless Tracker (2)
   #define  PIN_ADC_CTRL    37
 #endif

+ 81 - 0
src/helpers/MeshadventurerBoard.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include <Arduino.h>
+
+// LoRa radio module pins for Meshadventurer
+#define  P_LORA_DIO_1   33
+#define  P_LORA_NSS     18
+#define  P_LORA_RESET   23
+#define  P_LORA_BUSY    32
+#define  P_LORA_SCLK     5
+#define  P_LORA_MISO    19
+#define  P_LORA_MOSI    27
+
+#define  PIN_VBAT_READ    35
+
+#include "ESP32Board.h"
+
+#include <driver/rtc_io.h>
+
+class MeshadventurerBoard : public ESP32Board {
+
+public:
+  void begin() {
+    ESP32Board::begin();
+
+    esp_reset_reason_t reason = esp_reset_reason();
+    if (reason == ESP_RST_DEEPSLEEP) {
+      long wakeup_source = esp_sleep_get_ext1_wakeup_status();
+      if (wakeup_source & (1 << P_LORA_DIO_1)) {  // received a LoRa packet (while in deep sleep)
+        startup_reason = BD_STARTUP_RX_PACKET;
+      }
+
+      rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
+      rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
+    }
+  }
+
+  void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
+    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+
+    // Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep 
+    rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
+    rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
+
+    rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
+
+    if (pin_wake_btn < 0) {
+      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet
+    } else {
+      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet OR wake btn
+    }
+
+    if (secs > 0) {
+      esp_sleep_enable_timer_wakeup(secs * 1000000);
+    }
+
+    // Finally set ESP32 into sleep
+    esp_deep_sleep_start();   // CPU halts here and never returns!
+  }
+
+  void powerOff() override {
+    // TODO: re-enable this when there is a definite wake-up source pin:
+    //  enterDeepSleep(0);
+  }
+
+  uint16_t getBattMilliVolts() override {
+    analogReadResolution(12);
+
+    uint32_t raw = 0;
+    for (int i = 0; i < 4; i++) {
+      raw += analogReadMilliVolts(PIN_VBAT_READ);
+    }
+    raw = raw / 4;
+
+    return (2 * raw);
+  }
+
+  const char* getManufacturerName() const override {
+    return "Meshadventurer";
+  }
+};

+ 91 - 0
src/helpers/nrf52/MinewsemiME25LS01Board.cpp

@@ -0,0 +1,91 @@
+#include <Arduino.h>
+#include "MinewsemiME25LS01Board.h"
+#include <Wire.h>
+
+#include <bluefruit.h>
+
+void MinewsemiME25LS01Board::begin() {
+  // for future use, sub-classes SHOULD call this from their begin()
+  startup_reason = BD_STARTUP_NORMAL;
+  btn_prev_state = HIGH;
+  
+  pinMode(PIN_VBAT_READ, INPUT);
+
+  sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
+
+#ifdef BUTTON_PIN
+  pinMode(BUTTON_PIN, INPUT);
+  pinMode(LED_PIN, OUTPUT);
+#endif
+
+#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
+  Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
+#endif
+
+  Wire.begin();
+  
+#ifdef P_LORA_TX_LED
+  pinMode(P_LORA_TX_LED, OUTPUT);
+  digitalWrite(P_LORA_TX_LED, LOW);
+#endif
+
+  delay(10);   // give sx1262 some time to power up
+}
+
+static BLEDfu bledfu;
+
+static void connect_callback(uint16_t conn_handle) {
+  (void)conn_handle;
+  MESH_DEBUG_PRINTLN("BLE client connected");
+}
+
+static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
+  (void)conn_handle;
+  (void)reason;
+
+  MESH_DEBUG_PRINTLN("BLE client disconnected");
+}
+
+
+bool MinewsemiME25LS01Board::startOTAUpdate(const char* id, char reply[]) {
+  // Config the peripheral connection with maximum bandwidth
+  // more SRAM required by SoftDevice
+  // Note: All config***() function must be called before begin()
+  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
+  Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
+
+  Bluefruit.begin(1, 0);
+  // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
+  Bluefruit.setTxPower(4);
+  // Set the BLE device name
+  Bluefruit.setName("Minewsemi_OTA");
+
+  Bluefruit.Periph.setConnectCallback(connect_callback);
+  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
+
+  // To be consistent OTA DFU should be added first if it exists
+  bledfu.begin();
+
+  // Set up and start advertising
+  // Advertising packet
+  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
+  Bluefruit.Advertising.addTxPower();
+  Bluefruit.Advertising.addName();
+
+  /* Start Advertising
+    - Enable auto advertising if disconnected
+    - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
+    - Timeout for fast mode is 30 seconds
+    - Start(timeout) with timeout = 0 will advertise forever (until connected)
+
+    For recommended advertising interval
+    https://developer.apple.com/library/content/qa/qa1931/_index.html
+  */
+  Bluefruit.Advertising.restartOnDisconnect(true);
+  Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
+  Bluefruit.Advertising.setFastTimeout(30);   // number of seconds in fast mode
+  Bluefruit.Advertising.start(0);             // 0 = Don't stop advertising after n seconds
+
+  strcpy(reply, "OK - started");
+  return true;
+}

+ 88 - 0
src/helpers/nrf52/MinewsemiME25LS01Board.h

@@ -0,0 +1,88 @@
+#pragma once
+
+#include <MeshCore.h>
+#include <Arduino.h>
+
+// LoRa and SPI pins
+
+#define  P_LORA_DIO_1   (32 + 12)  // P1.12
+#define  P_LORA_NSS     (32 + 13)  // P1.13
+#define  P_LORA_RESET   (32 + 11)  // P1.11
+#define  P_LORA_BUSY    (32 + 10)  // P1.10
+#define  P_LORA_SCLK    (32 + 15)  // P1.15
+#define  P_LORA_MISO    (0 + 29)   // P0.29
+#define  P_LORA_MOSI    (0 + 2)    // P0.2
+ 
+#define LR11X0_DIO_AS_RF_SWITCH  true
+#define LR11X0_DIO3_TCXO_VOLTAGE   1.6
+
+#define  PIN_VBAT_READ BATTERY_PIN
+#define  ADC_MULTIPLIER   (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
+
+
+class MinewsemiME25LS01Board : public mesh::MainBoard {
+protected:
+  uint8_t startup_reason;
+  uint8_t btn_prev_state;
+
+public:
+  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);
+  }
+
+  uint8_t getStartupReason() const override { return startup_reason; }
+
+  const char* getManufacturerName() const override {
+    return "Minewsemi";
+  }
+
+  void powerOff() override {
+    #ifdef HAS_GPS
+        digitalWrite(GPS_VRTC_EN, LOW);
+        digitalWrite(GPS_RESET, LOW);
+        digitalWrite(GPS_SLEEP_INT, LOW);
+        digitalWrite(GPS_RTC_INT, LOW);
+        pinMode(GPS_RESETB, OUTPUT);
+        digitalWrite(GPS_RESETB, LOW);
+    #endif
+    
+    #ifdef BUZZER_EN
+        digitalWrite(BUZZER_EN, LOW);
+    #endif
+    
+    #ifdef LED_PIN
+    digitalWrite(LED_PIN, LOW);
+    #endif
+    #ifdef BUTTON_PIN
+    nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_HIGH);
+    #endif
+    sd_power_system_off();
+  }
+
+  #if defined(P_LORA_TX_LED)
+  void onBeforeTransmit() override {
+    digitalWrite(P_LORA_TX_LED, HIGH);// turn TX LED on
+  }
+  void onAfterTransmit() override {
+    digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
+  }
+#endif
+
+
+  void reboot() override {
+    NVIC_SystemReset();
+  }
+
+  bool startOTAUpdate(const char* id, char reply[]) override;
+};

+ 61 - 46
src/helpers/nrf52/SerialBLEInterface.cpp

@@ -1,6 +1,27 @@
 #include "SerialBLEInterface.h"
 
+static SerialBLEInterface* instance;
+
+void SerialBLEInterface::onConnect(uint16_t connection_handle) {
+  BLE_DEBUG_PRINTLN("SerialBLEInterface: connected");
+  if(instance){
+    instance->_isDeviceConnected = true;
+    // no need to stop advertising on connect, as the ble stack does this automatically
+  }
+}
+
+void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) {
+  BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason);
+  if(instance){
+    instance->_isDeviceConnected = false;
+    instance->startAdv();
+  }
+}
+
 void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
+
+  instance = this;
+
   char charpin[20];
   sprintf(charpin, "%d", pin_code);
 
@@ -13,11 +34,31 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
   Bluefruit.Security.setMITM(true);
   Bluefruit.Security.setPIN(charpin);
 
+  Bluefruit.Periph.setConnectCallback(onConnect);
+  Bluefruit.Periph.setDisconnectCallback(onDisconnect);
+
   // To be consistent OTA DFU should be added first if it exists
   //bledfu.begin();
+
+  // Configure and start the BLE Uart service
+  bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
+  bleuart.begin();
+  
 }
 
 void SerialBLEInterface::startAdv() {
+
+  BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising");
+  
+  // clean restart if already advertising
+  if(Bluefruit.Advertising.isRunning()){
+    BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart");
+    Bluefruit.Advertising.stop();
+  }
+
+  Bluefruit.Advertising.clearData(); // clear advertising data
+  Bluefruit.ScanResponse.clearData(); // clear scan response data
+  
   // Advertising packet
   Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
   Bluefruit.Advertising.addTxPower();
@@ -38,10 +79,25 @@ void SerialBLEInterface::startAdv() {
    * For recommended advertising interval
    * https://developer.apple.com/library/content/qa/qa1931/_index.html   
    */
-  Bluefruit.Advertising.restartOnDisconnect(true);
+  Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect
   Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
   Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
-  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds 
+  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds
+
+}
+
+void SerialBLEInterface::stopAdv() {
+
+  BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising");
+  
+  // we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack
+  if(!Bluefruit.Advertising.isRunning()){
+    return;
+  }
+
+  // stop advertising
+  Bluefruit.Advertising.stop();
+
 }
 
 // ---------- public methods
@@ -52,25 +108,14 @@ void SerialBLEInterface::enable() {
   _isEnabled = true;
   clearBuffers();
 
-  // Configure and start the BLE Uart service
-  bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
-  bleuart.begin();
-
   // Start advertising
   startAdv();
-
-  checkAdvRestart = false;
 }
 
 void SerialBLEInterface::disable() {
   _isEnabled = false;
-
   BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");
-
-  Bluefruit.Advertising.stop();
-
-  oldDeviceConnected = deviceConnected = false;
-  checkAdvRestart = false;
+  stopAdv();
 }
 
 size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
@@ -79,7 +124,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
     return 0;
   }
 
-  if (deviceConnected && len > 0) {
+  if (_isDeviceConnected && len > 0) {
     if (send_queue_len >= FRAME_QUEUE_SIZE) {
       BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
       return 0;
@@ -115,44 +160,14 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
   } else {
     int len = bleuart.available();
     if (len > 0) {
-      deviceConnected = true; // should probably use the callback to monitor cx 
       bleuart.readBytes(dest, len);
       BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]);
       return len;
     }
   }
-
-  if (Bluefruit.connected() == 0)  deviceConnected = false;
-
-  if (deviceConnected != oldDeviceConnected) {
-    if (!deviceConnected) {    // disconnecting
-      clearBuffers();
-
-      BLE_DEBUG_PRINTLN("SerialBLEInterface -> disconnecting...");
-      delay(500); // give the bluetooth stack the chance to get things ready
-
-      checkAdvRestart = true;
-    } else {
-      BLE_DEBUG_PRINTLN("SerialBLEInterface -> stopping advertising");
-      BLE_DEBUG_PRINTLN("SerialBLEInterface -> connecting...");
-      // connecting
-      // do stuff here on connecting
-      Bluefruit.Advertising.stop();
-      checkAdvRestart = false;
-    }
-    oldDeviceConnected = deviceConnected;
-  }
-
-  if (checkAdvRestart) {
-    if (Bluefruit.connected() == 0) {
-      BLE_DEBUG_PRINTLN("SerialBLEInterface -> re-starting advertising");
-      startAdv();
-    }
-    checkAdvRestart = false;
-  }
   return 0;
 }
 
 bool SerialBLEInterface::isConnected() const {
-  return deviceConnected;  //pServer != NULL && pServer->getConnectedCount() > 0;
+  return _isDeviceConnected;
 }

+ 6 - 7
src/helpers/nrf52/SerialBLEInterface.h

@@ -5,10 +5,8 @@
 
 class SerialBLEInterface : public BaseSerialInterface {
   BLEUart bleuart;
-  bool deviceConnected;
-  bool oldDeviceConnected;
-  bool checkAdvRestart;
   bool _isEnabled;
+  bool _isDeviceConnected;
   unsigned long _last_write;
 
   struct Frame {
@@ -21,18 +19,19 @@ class SerialBLEInterface : public BaseSerialInterface {
   Frame send_queue[FRAME_QUEUE_SIZE];
 
   void clearBuffers() { send_queue_len = 0; }
-  void startAdv();
+  static void onConnect(uint16_t connection_handle);
+  static void onDisconnect(uint16_t connection_handle, uint8_t reason);
 
 public:
   SerialBLEInterface() {
-    deviceConnected = false;
-    oldDeviceConnected = false;
-    checkAdvRestart = false;
     _isEnabled = false;
+    _isDeviceConnected = false;
     _last_write = 0;
     send_queue_len = 0;
   }
 
+  void startAdv();
+  void stopAdv();
   void begin(const char* device_name, uint32_t pin_code);
 
   // BaseSerialInterface methods

+ 30 - 0
src/helpers/rp2040/XiaoRP2040Board.cpp

@@ -0,0 +1,30 @@
+#include "XiaoRP2040Board.h"
+
+#include <Arduino.h>
+#include <Wire.h>
+
+void XiaoRP2040Board::begin() {
+  // for future use, sub-classes SHOULD call this from their begin()
+  startup_reason = BD_STARTUP_NORMAL;
+
+#ifdef P_LORA_TX_LED
+  pinMode(P_LORA_TX_LED, OUTPUT);
+#endif
+
+#ifdef PIN_VBAT_READ
+  pinMode(PIN_VBAT_READ, INPUT);
+#endif
+
+#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
+  Wire.setSDA(PIN_BOARD_SDA);
+  Wire.setSCL(PIN_BOARD_SCL);
+#endif
+
+  Wire.begin();
+
+  delay(10); // give sx1262 some time to power up
+}
+
+bool XiaoRP2040Board::startOTAUpdate(const char *id, char reply[]) {
+  return false;
+}

+ 75 - 0
src/helpers/rp2040/XiaoRP2040Board.h

@@ -0,0 +1,75 @@
+#pragma once
+
+#include <Arduino.h>
+#include <MeshCore.h>
+
+// LoRa radio module pins for the Xiao RP2040
+// https://wiki.seeedstudio.com/XIAO-RP2040/
+
+#define P_LORA_DIO_1      27 // D1
+#define P_LORA_NSS        6  // D4
+#define P_LORA_RESET      28 // D2
+#define P_LORA_BUSY       29 // D3
+#define P_LORA_TX_LED     17
+
+#define SX126X_RXEN       7  // D5
+#define SX126X_TXEN       -1
+
+#define SX126X_DIO2_AS_RF_SWITCH true
+#define SX126X_DIO3_TCXO_VOLTAGE 1.8
+
+/*
+ * This board has no built-in way to read battery voltage.
+ * Nevertheless it's very easy to make it work, you only require two 1% resistors.
+ * If your using the WIO SX1262 Addon for xaio, make sure you dont connect D0!
+ *
+ *    BAT+ -----+
+ *              |
+ *       VSYS --+ -/\/\/\/\- --+
+ *                   200k      |
+ *                             +-- D0
+ *                             |
+ *        GND --+ -/\/\/\/\- --+
+ *              |    100k
+ *    BAT- -----+
+ */
+#define PIN_VBAT_READ     26 // D0
+#define BATTERY_SAMPLES   8
+#define ADC_MULTIPLIER    (3.0f * 3.3f * 1000)
+
+class XiaoRP2040Board : public mesh::MainBoard {
+protected:
+  uint8_t startup_reason;
+
+public:
+  void begin();
+  uint8_t getStartupReason() const override { return startup_reason; }
+
+#ifdef P_LORA_TX_LED
+  void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); }
+  void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
+#endif
+
+
+  uint16_t getBattMilliVolts() override {
+#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER)
+    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;
+#else
+    return 0;
+#endif
+  }
+
+  const char *getManufacturerName() const override { return "Xiao RP2040"; }
+
+  void reboot() override { rp2040.reboot(); }
+
+  bool startOTAUpdate(const char *id, char reply[]) override;
+};

+ 61 - 16
src/helpers/sensors/EnvironmentSensorManager.cpp

@@ -24,6 +24,14 @@ static Adafruit_BME280 BME280;
 static Adafruit_BMP280 BMP280;
 #endif
 
+#if ENV_INCLUDE_SHTC3
+#include <Adafruit_SHTC3.h>
+static Adafruit_SHTC3 SHTC3;
+#endif
+
+#if ENV_INCLUDE_LPS22HB
+#include <Arduino_LPS22HB.h>
+#endif
 
 #if ENV_INCLUDE_INA3221
 #define TELEM_INA3221_ADDRESS   0x42      // INA3221 3 channel current sensor I2C address
@@ -76,28 +84,48 @@ bool EnvironmentSensorManager::begin() {
   }
   #endif
 
+  #if ENV_INCLUDE_SHTC3
+  if (SHTC3.begin()) {
+    MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
+    SHTC3_initialized = true;
+  } else {
+    SHTC3_initialized = false;
+    MESH_DEBUG_PRINTLN("SHTC3 was not found at I2C address %02X", 0x70);
+  }
+  #endif
+
+  #if ENV_INCLUDE_LPS22HB
+  if (BARO.begin()) {
+    MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
+    LPS22HB_initialized = true;
+  } else {
+    LPS22HB_initialized = false;
+    MESH_DEBUG_PRINTLN("LPS22HB was not found at I2C address %02X", 0x5C);
+  }
+  #endif
+
   #if ENV_INCLUDE_INA3221
   if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) {
-      MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
-      MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
+    MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
+    MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
 
-      for(int i = 0; i < 3; i++) {
-        INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
-      }
-      INA3221_initialized = true;
+    for(int i = 0; i < 3; i++) {
+      INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
+    }
+    INA3221_initialized = true;
   } else {
-      INA3221_initialized = false;
-      MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
+    INA3221_initialized = false;
+    MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
   }
   #endif
 
   #if ENV_INCLUDE_INA219
   if (INA219.begin(&Wire)) {
-      MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
-      INA219_initialized = true;
+    MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
+    INA219_initialized = true;
   } else {
-      INA219_initialized = false;
-      MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
+    INA219_initialized = false;
+    MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
   }
   #endif
 
@@ -139,6 +167,23 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
     }
     #endif
 
+    #if ENV_INCLUDE_SHTC3
+    if (SHTC3_initialized) {
+      sensors_event_t humidity, temp;
+      SHTC3.getEvent(&humidity, &temp);
+
+      telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
+      telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
+    }
+    #endif
+
+    #if ENV_INCLUDE_LPS22HB
+    if (LPS22HB_initialized) {
+      telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature());
+      telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure());
+    }
+    #endif
+
     #if ENV_INCLUDE_INA3221
     if (INA3221_initialized) {
       for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
@@ -157,10 +202,10 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
 
     #if ENV_INCLUDE_INA219
     if (INA219_initialized) {
-        telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
-        telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
-        telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
-        next_available_channel++;
+      telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
+      telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
+      telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
+      next_available_channel++;
     }
     #endif
 

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

@@ -13,6 +13,8 @@ protected:
   bool BMP280_initialized = false;
   bool INA3221_initialized = false;
   bool INA219_initialized = false;
+  bool SHTC3_initialized = false;
+  bool LPS22HB_initialized = false;
 
   bool gps_detected = false;
   bool gps_active = false;

+ 116 - 0
src/helpers/ui/E213Display.cpp

@@ -0,0 +1,116 @@
+#include "E213Display.h"
+
+#include "../../MeshCore.h"
+
+bool E213Display::begin() {
+  if (_init) return true;
+
+  powerOn();
+  display.begin();
+
+  // Set to landscape mode rotated 180 degrees
+  display.setRotation(3);
+
+  _init = true;
+  _isOn = true;
+
+  clear();
+  display.fastmodeOn(); // Enable fast mode for quicker (partial) updates
+
+  return true;
+}
+
+void E213Display::powerOn() {
+#ifdef PIN_VEXT_EN
+  pinMode(PIN_VEXT_EN, OUTPUT);
+  digitalWrite(PIN_VEXT_EN, LOW); // Active low
+  delay(50);                      // Allow power to stabilize
+#endif
+}
+
+void E213Display::powerOff() {
+#ifdef PIN_VEXT_EN
+  digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power
+#endif
+}
+
+void E213Display::turnOn() {
+  if (!_init) begin();
+  powerOn();
+  _isOn = true;
+}
+
+void E213Display::turnOff() {
+  powerOff();
+  _isOn = false;
+}
+
+void E213Display::clear() {
+  display.clear();
+}
+
+void E213Display::startFrame(Color bkg) {
+  // Fill screen with white first to ensure clean background
+  display.fillRect(0, 0, width(), height(), WHITE);
+  if (bkg == LIGHT) {
+    // Fill with black if light background requested (inverted for e-ink)
+    display.fillRect(0, 0, width(), height(), BLACK);
+  }
+}
+
+void E213Display::setTextSize(int sz) {
+  // The library handles text size internally
+  display.setTextSize(sz);
+}
+
+void E213Display::setColor(Color c) {
+  // implemented in individual display methods
+}
+
+void E213Display::setCursor(int x, int y) {
+  display.setCursor(x, y);
+}
+
+void E213Display::print(const char *str) {
+  display.print(str);
+}
+
+void E213Display::fillRect(int x, int y, int w, int h) {
+  display.fillRect(x, y, w, h, BLACK);
+}
+
+void E213Display::drawRect(int x, int y, int w, int h) {
+  display.drawRect(x, y, w, h, BLACK);
+}
+
+void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
+  // Width in bytes for bitmap processing
+  uint16_t widthInBytes = (w + 7) / 8;
+
+  // Process the bitmap row by row
+  for (int by = 0; by < h; by++) {
+    // Scan across the row bit by bit
+    for (int bx = 0; bx < w; bx++) {
+      // Get the current bit using MSB ordering (like GxEPDDisplay)
+      uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
+      uint8_t bitMask = 0x80 >> (bx & 7);
+      bool bitSet = bits[byteOffset] & bitMask;
+
+      // If the bit is set, draw the pixel
+      if (bitSet) {
+        display.drawPixel(x + bx, y + by, BLACK);
+      }
+    }
+  }
+}
+
+uint16_t E213Display::getTextWidth(const char *str) {
+  int16_t x1, y1;
+  uint16_t w, h;
+  display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
+  return w;
+}
+
+void E213Display::endFrame() {
+  display.update();
+}

+ 37 - 0
src/helpers/ui/E213Display.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include "DisplayDriver.h"
+
+#include <SPI.h>
+#include <Wire.h>
+#include <heltec-eink-modules.h>
+
+// Display driver for E213 e-ink display
+class E213Display : public DisplayDriver {
+  EInkDisplay_VisionMasterE213 display;
+  bool _init = false;
+  bool _isOn = false;
+
+public:
+  E213Display() : DisplayDriver(250, 122) {}
+
+  bool begin();
+  bool isOn() override { return _isOn; }
+  void turnOn() override;
+  void turnOff() override;
+  void clear() override;
+  void startFrame(Color bkg = DARK) override;
+  void setTextSize(int sz) override;
+  void setColor(Color c) override;
+  void setCursor(int x, int y) override;
+  void print(const char *str) override;
+  void fillRect(int x, int y, int w, int h) override;
+  void drawRect(int x, int y, int w, int h) override;
+  void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override;
+  uint16_t getTextWidth(const char *str) override;
+  void endFrame() override;
+
+private:
+  void powerOn();
+  void powerOff();
+};

+ 3 - 30
variants/heltec_tracker/target.cpp

@@ -23,43 +23,16 @@ HWTSensorManager sensors = HWTSensorManager(nmea);
   DISPLAY_CLASS display(&board.periph_power);   // peripheral power pin is shared
 #endif
 
-#ifndef LORA_CR
-  #define LORA_CR      5
-#endif
-
 bool radio_init() {
   fallback_clock.begin();
   rtc_clock.begin(Wire);
   
-#ifdef SX126X_DIO3_TCXO_VOLTAGE
-  float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
-#else
-  float tcxo = 1.6f;
-#endif
-
 #if defined(P_LORA_SCLK)
-  spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
-#endif
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
-  if (status != RADIOLIB_ERR_NONE) {
-    Serial.print("ERROR: radio init failed: ");
-    Serial.println(status);
-    return false;  // fail
-  }
-  
-  radio.setCRC(1);
-  
-#ifdef SX126X_CURRENT_LIMIT
-  radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
-#endif
-#ifdef SX126X_DIO2_AS_RF_SWITCH
-  radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
-#endif
-#ifdef SX126X_RX_BOOSTED_GAIN
-  radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
+  return radio.std_init(&spi);
+#else
+  return radio.std_init();
 #endif
 
-  return true;  // success
 }
 
 uint32_t radio_get_rng_seed() {

+ 1 - 1
variants/heltec_v2/platformio.ini

@@ -96,7 +96,7 @@ build_flags =
   -D DISPLAY_CLASS=SSD1306Display
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
-  -D BLE_PIN_CODE=0
+  -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D OFFLINE_QUEUE_SIZE=256
 ;  -D MESH_PACKET_LOGGING=1

+ 4 - 4
variants/heltec_v3/platformio.ini

@@ -23,9 +23,9 @@ build_flags =
   -D ENV_INCLUDE_INA3221=1
   -D ENV_INCLUDE_INA219=1
   -D ENV_INCLUDE_GPS=1
-  -D PIN_GPS_RX=45
-  -D PIN_GPS_TX=46
-  -D PIN_GPS_EN=-1
+  -D PIN_GPS_RX=47
+  -D PIN_GPS_TX=48
+  -D PIN_GPS_EN=26
 build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/heltec_v3>
   +<helpers/sensors>
@@ -114,7 +114,7 @@ build_flags =
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=SSD1306Display
-  -D BLE_PIN_CODE=0   ; dynamic, random PIN
+  -D BLE_PIN_CODE=123456   ; dynamic, random PIN
   -D BLE_DEBUG_LOGGING=1
   -D OFFLINE_QUEUE_SIZE=256
 ;  -D MESH_PACKET_LOGGING=1

+ 84 - 0
variants/heltec_wireless_paper/platformio.ini

@@ -0,0 +1,84 @@
+[Heltec_Wireless_Paper_base]
+extends = esp32_base
+board = esp32-s3-devkitc-1
+build_flags =
+  ${esp32_base.build_flags}
+  -I variants/heltec_wireless_paper
+  -D HELTEC_WIRELESS_PAPER
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D P_LORA_TX_LED=18
+  ; -D PIN_BOARD_SDA=17
+  ; -D PIN_BOARD_SCL=18
+  -D PIN_USER_BTN=0
+  -D PIN_VEXT_EN=45
+  -D PIN_VBAT_READ=20
+  -D PIN_ADC_CTRL=19
+  -D SX126X_DIO2_AS_RF_SWITCH=true
+  -D SX126X_DIO3_TCXO_VOLTAGE=1.8
+  -D SX126X_CURRENT_LIMIT=140
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D DISP_CS=4
+  -D DISP_BUSY=7
+  -D DISP_DC=5
+  -D DISP_RST=6
+  -D DISP_SCLK=3
+  -D DISP_MOSI=2
+  -D ARDUINO_heltec_wifi_lora_32_V3
+  -D WIRELESS_PAPER
+build_src_filter = ${esp32_base.build_src_filter}
+  +<../variants/heltec_wireless_paper>    
+lib_deps =
+  ${esp32_base.lib_deps}
+  todd-herbert/heltec-eink-modules @ 4.5.0
+
+[env:Heltec_Wireless_Paper_companion_radio_ble]
+extends = Heltec_Wireless_Paper_base
+build_flags =
+  ${Heltec_Wireless_Paper_base.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D DISPLAY_CLASS=E213Display
+  -D BLE_PIN_CODE=123456   ; dynamic, random PIN
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
+  +<helpers/ui/E213Display.cpp>
+  +<helpers/esp32/*.cpp>
+  +<../examples/companion_radio>
+lib_deps =
+  ${Heltec_Wireless_Paper_base.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Heltec_Wireless_Paper_repeater]
+extends = Heltec_Wireless_Paper_base
+build_flags =
+  ${Heltec_Wireless_Paper_base.build_flags}
+  -D DISPLAY_CLASS=E213Display
+  -D ADVERT_NAME='"Heltec WP Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
+  +<helpers/ui/E213Display.cpp>
+  +<../examples/simple_repeater>
+lib_deps =
+  ${Heltec_Wireless_Paper_base.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Heltec_Wireless_Paper_room_server]
+extends = Heltec_Wireless_Paper_base
+build_flags =
+  ${Heltec_Wireless_Paper_base.build_flags}
+  -D DISPLAY_CLASS=E213Display
+  -D ADVERT_NAME='"Heltec WP Room"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
+  +<helpers/ui/E213Display.cpp>
+  +<../examples/simple_room_server>
+lib_deps =
+  ${Heltec_Wireless_Paper_base.lib_deps}
+  ${esp32_ota.lib_deps}

+ 45 - 0
variants/heltec_wireless_paper/target.cpp

@@ -0,0 +1,45 @@
+#include "target.h"
+
+#include <Arduino.h>
+
+HeltecV3Board board;
+
+static SPIClass spi;
+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);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+SensorManager sensors;
+
+#ifdef DISPLAY_CLASS
+DISPLAY_CLASS display;
+#endif
+
+bool radio_init() {
+  fallback_clock.begin();
+  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(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng); // create new random identity
+}

+ 27 - 0
variants/heltec_wireless_paper/target.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/RadioLibWrappers.h>
+#include <helpers/HeltecV3Board.h>
+#include <helpers/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/SensorManager.h>
+#ifdef DISPLAY_CLASS
+#include <helpers/ui/E213Display.h>
+#endif
+
+extern HeltecV3Board board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern SensorManager sensors;
+
+#ifdef DISPLAY_CLASS
+extern DISPLAY_CLASS display;
+#endif
+
+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(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 3 - 36
variants/lilygo_t3s3/target.cpp

@@ -3,13 +3,8 @@
 
 ESP32Board board;
 
-#if defined(P_LORA_SCLK)
-  static SPIClass spi;
-  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
-#else
-  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
-#endif
-
+static SPIClass spi;
+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);
 
 ESP32RTCClock fallback_clock;
@@ -28,35 +23,7 @@ bool radio_init() {
   fallback_clock.begin();
   rtc_clock.begin(Wire);
   
-#ifdef SX126X_DIO3_TCXO_VOLTAGE
-  float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
-#else
-  float tcxo = 1.6f;
-#endif
-
-#if defined(P_LORA_SCLK)
-  spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
-#endif
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
-  if (status != RADIOLIB_ERR_NONE) {
-    Serial.print("ERROR: radio init failed: ");
-    Serial.println(status);
-    return false;  // fail
-  }
-  
-  radio.setCRC(1);
-  
-#ifdef SX126X_CURRENT_LIMIT
-  radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
-#endif
-#ifdef SX126X_DIO2_AS_RF_SWITCH
-  radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
-#endif
-#ifdef SX126X_RX_BOOSTED_GAIN
-  radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
-#endif
-
-  return true;  // success
+  return radio.std_init(&spi);
 }
 
 uint32_t radio_get_rng_seed() {

+ 18 - 1
variants/lilygo_tbeam_SX1262/platformio.ini

@@ -69,4 +69,21 @@ build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
   +<../examples/simple_repeater>
 lib_deps =
   ${LilyGo_TBeam_SX1262.lib_deps}
-  ${esp32_ota.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Tbeam_SX1262_room_server]
+extends = LilyGo_TBeam_SX1262
+build_flags =
+  ${LilyGo_TBeam_SX1262.build_flags}
+  -D ADVERT_NAME='"Tbeam SX1262 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 = ${LilyGo_TBeam_SX1262.build_src_filter}
+  +<../examples/simple_room_server>
+lib_deps =
+  ${LilyGo_TBeam_SX1262.lib_deps}
+  ${esp32_ota.lib_deps}

+ 234 - 0
variants/meshadventurer/platformio.ini

@@ -0,0 +1,234 @@
+[Meshadventurer]
+extends = esp32_base
+board = esp32doit-devkit-v1
+board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
+build_flags =
+  ${esp32_base.build_flags}
+  -I variants/meshadventurer
+  -D MESHADVENTURER
+  -D P_LORA_TX_LED=2
+  -D PIN_VBAT_READ=35
+  -D PIN_USER_BTN_ANA=39
+  -D P_LORA_DIO_1=33
+  -D P_LORA_NSS=18
+  -D P_LORA_RESET=23
+  -D P_LORA_BUSY=32
+  -D P_LORA_SCLK=5
+  -D P_LORA_MOSI=27
+  -D P_LORA_MISO=19
+  -D SX126X_TXEN=13
+  -D SX126X_RXEN=14
+  -D PIN_BOARD_SDA=21
+  -D PIN_BOARD_SCL=22
+  -D SX126X_DIO2_AS_RF_SWITCH=false
+  -D SX126X_DIO3_TCXO_VOLTAGE=1.8
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D PIN_GPS_RX=12
+  -D PIN_GPS_TX=15
+  -D DISPLAY_CLASS=SSD1306Display
+build_src_filter = ${esp32_base.build_src_filter}
+  +<../variants/meshadventurer>
+lib_deps =
+  ${esp32_base.lib_deps}
+  stevemarple/MicroNMEA @ ^2.0.6
+  adafruit/Adafruit SSD1306 @ ^2.5.13
+
+[env:Meshadventurer_sx1262_repeater]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_repeater>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D ADVERT_NAME='"Meshadventurer Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Meshadventurer_sx1268_repeater]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_repeater>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1268
+  -D WRAPPER_CLASS=CustomSX1268Wrapper
+  -D LORA_TX_POWER=22
+  -D ADVERT_NAME='"Meshadventurer Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Meshadventurer_sx1262_companion_radio_usb]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1262_companion_radio_ble]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/esp32/*.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1262_terminal_chat]
+extends = Meshadventurer
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_secure_chat/main.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1262_room_server]
+extends = Meshadventurer
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D ADVERT_NAME='"Meshadventurer 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 = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_room_server>
+  +<helpers/ui/SSD1306Display.cpp>
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Meshadventurer_sx1268_companion_radio_usb]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1268
+  -D WRAPPER_CLASS=CustomSX1268Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1268_companion_radio_ble]
+extends = Meshadventurer
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/esp32/*.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1268
+  -D WRAPPER_CLASS=CustomSX1268Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1268_terminal_chat]
+extends = Meshadventurer
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1268
+  -D WRAPPER_CLASS=CustomSX1268Wrapper
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_secure_chat/main.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Meshadventurer_sx1268_room_server]
+extends = Meshadventurer
+build_flags =
+  ${Meshadventurer.build_flags}
+  -D RADIO_CLASS=CustomSX1268
+  -D WRAPPER_CLASS=CustomSX1268Wrapper
+  -D LORA_TX_POWER=22
+  -D ADVERT_NAME='"Meshadventurer 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 = ${Meshadventurer.build_src_filter}
+  +<../examples/simple_room_server>
+  +<helpers/ui/SSD1306Display.cpp>
+lib_deps =
+  ${Meshadventurer.lib_deps}
+  ${esp32_ota.lib_deps}

+ 119 - 0
variants/meshadventurer/target.cpp

@@ -0,0 +1,119 @@
+#include <Arduino.h>
+#include "target.h"
+
+#include <helpers/sensors/MicroNMEALocationProvider.h>
+
+MeshadventurerBoard board;
+
+static SPIClass spi;
+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);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
+MASensorManager sensors = MASensorManager(nmea);
+
+#ifdef DISPLAY_CLASS
+  DISPLAY_CLASS display;
+#endif
+
+#ifndef LORA_CR
+  #define LORA_CR      5
+#endif
+
+bool radio_init() {
+  fallback_clock.begin();
+  rtc_clock.begin(Wire);
+
+#if defined(P_LORA_SCLK)
+  return radio.std_init(&spi);
+#else
+  return radio.std_init();
+#endif
+}
+
+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(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);  // create new random identity
+}
+
+void MASensorManager::start_gps() {
+  if(!gps_active) {
+    MESH_DEBUG_PRINTLN("starting GPS");
+    gps_active = true;
+  }
+}
+
+void MASensorManager::stop_gps() {
+  if(gps_active) {
+    MESH_DEBUG_PRINTLN("stopping GPS");
+    gps_active = false;
+  }
+}
+
+bool MASensorManager::begin() {
+  Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX);
+  Serial1.begin(9600);
+  delay(500);
+  return true;
+}
+
+bool MASensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
+  if(requester_permissions & TELEM_PERM_LOCATION) {   // does requester have permission?
+    telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude);
+  }
+  return true;
+}
+
+void MASensorManager::loop() {
+  static long next_gps_update = 0;
+  _location->loop();
+  if(millis() > next_gps_update && gps_active) {
+    if(_location->isValid()) {
+      node_lat = ((double)_location->getLatitude()) / 1000000.;
+      node_lon = ((double)_location->getLongitude()) / 1000000.;
+      node_altitude = ((double)_location->getAltitude()) / 1000.0;
+      MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
+    }
+    next_gps_update = millis() + 1000;
+  }
+}
+
+int MASensorManager::getNumSettings() const { return 1; }  // just one supported: "gps" (power switch)
+
+const char* MASensorManager::getSettingName(int i) const {
+  return i == 0 ? "gps" : NULL;
+}
+const char* MASensorManager::getSettingValue(int i) const {
+  if(i == 0) {
+    return gps_active ? "1" : "0";
+  }
+  return NULL;
+}
+bool MASensorManager::setSettingValue(const char* name, const char* value) {
+  if(strcmp(name, "gps") == 0) {
+    if(strcmp(value, "0") == 0) {
+      stop_gps();
+    } else {
+      start_gps();
+    }
+    return true;
+  }
+  return false;  // not supported
+}

+ 46 - 0
variants/meshadventurer/target.h

@@ -0,0 +1,46 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/RadioLibWrappers.h>
+#include <helpers/MeshadventurerBoard.h>
+#include <helpers/CustomSX1262Wrapper.h>
+#include <helpers/CustomSX1268Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/SensorManager.h>
+#include <helpers/sensors/LocationProvider.h>
+#ifdef DISPLAY_CLASS
+  #include <helpers/ui/SSD1306Display.h>
+#endif
+
+class MASensorManager : public SensorManager {
+  bool gps_active = false;
+  LocationProvider * _location;
+
+  void start_gps();
+  void stop_gps();
+public:
+  MASensorManager(LocationProvider &location): _location(&location) { }
+  bool begin() override;
+  bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
+  void loop() override;
+  int getNumSettings() const override;
+  const char* getSettingName(int i) const override;
+  const char* getSettingValue(int i) const override;
+  bool setSettingValue(const char* name, const char* value) override;
+};
+
+extern MeshadventurerBoard board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern MASensorManager sensors;
+
+#ifdef DISPLAY_CLASS
+  extern DISPLAY_CLASS display;
+#endif
+
+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(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 44 - 0
variants/meshadventurer/variant.h

@@ -0,0 +1,44 @@
+// For OLED LCD
+#define I2C_SDA 21
+#define I2C_SCL 22
+
+// For GPS, 'undef's not needed
+#define GPS_TX_PIN 15
+#define GPS_RX_PIN 12
+#define PIN_GPS_EN 4
+#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here
+
+#define BUTTON_PIN 39  // The middle button GPIO on the T-Beam
+#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+#define ADC_CHANNEL ADC1_GPIO35_CHANNEL
+#define ADC_MULTIPLIER 2
+#define EXT_PWR_DETECT 4    // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards
+#define EXT_NOTIFY_OUT 12   // Overridden default pin to use for Ext Notify Module (#975).
+#define LED_PIN 2           // add status LED (compatible with core-pcb and DIY targets)
+
+// Radio
+#define USE_SX1262 // E22-900M30S uses SX1262
+#define USE_SX1268 // E22-400M30S uses SX1268
+#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA)
+#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V
+
+#define SX126X_CS 18    // EBYTE module's NSS pin
+#define SX126X_SCK 5    // EBYTE module's SCK pin
+#define SX126X_MOSI 27  // EBYTE module's MOSI pin
+#define SX126X_MISO 19  // EBYTE module's MISO pin
+#define SX126X_RESET 23 // EBYTE module's NRST pin
+#define SX126X_BUSY 32  // EBYTE module's BUSY pin
+#define SX126X_DIO1 33  // EBYTE module's DIO1 pin
+
+// The E22's TXEN pin is connected to MCU pin, E22's RXEN pin is connected to MCU pin (allows for ramping up PA before transmission
+// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent
+// a short if they are both connected at the same time and there's a slight non-neglibible delay and/or voltage difference between
+// DIO2 and TXEN).
+#define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU
+#define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU
+
+#define LORA_CS SX126X_CS     // Compatibility with variant file configuration structure
+#define LORA_SCK SX126X_SCK   // Compatibility with variant file configuration structure
+#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure
+#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure
+#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure

+ 24 - 0
variants/minewsemi_me25ls01/NullDisplayDriver.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <helpers/ui/DisplayDriver.h>
+
+class NullDisplayDriver : public DisplayDriver {
+public:
+  NullDisplayDriver() : DisplayDriver(128, 64) { }
+  bool begin() { return false; }   // not present
+
+  bool isOn() override { return false; }
+  void turnOn() override { }
+  void turnOff() override { }
+  void clear() override { }
+  void startFrame(Color bkg = DARK) override { }
+  void setTextSize(int sz) override { }
+  void setColor(Color c) override { }
+  void setCursor(int x, int y) override { }
+  void print(const char* str) override { }
+  void fillRect(int x, int y, int w, int h) override { }
+  void drawRect(int x, int y, int w, int h) override { }
+  void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { }
+  uint16_t getTextWidth(const char* str) override { return 0; }
+  void endFrame() { }
+};

+ 165 - 0
variants/minewsemi_me25ls01/platformio.ini

@@ -0,0 +1,165 @@
+; ----------------- NRF52 me25ls01---------------------
+[nrf52840_me25ls01]
+extends = nrf52_base
+platform_packages = framework-arduinoadafruitnrf52
+build_flags = ${nrf52_base.build_flags}
+  -I src/helpers/nrf52
+  -I lib/nrf52/s140_nrf52_7.3.0_API/include
+  -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
+lib_ignore =
+  BluetoothOTA
+  lib5b4
+lib_deps =
+  ${nrf52_base.lib_deps}
+  rweather/Crypto @ ^0.4.0
+
+[me25ls01]
+extends = nrf52840_me25ls01
+board = minewsemi_me25ls01
+board_build.ldscript = boards/nrf52840_s140_v7.ld
+build_flags = ${nrf52840_me25ls01.build_flags}
+  -I variants/minewsemi_me25ls01
+  -D me25ls01
+  -D PIN_USER_BTN=27
+  -D USER_BTN_PRESSED=HIGH
+  -D PIN_STATUS_LED=39
+  -D P_LORA_TX_LED=22
+  -D RADIO_CLASS=CustomLR1110
+  -D WRAPPER_CLASS=CustomLR1110Wrapper
+  -D LORA_TX_POWER=22
+  -D ENV_INCLUDE_GPS=0
+  -D ENV_INCLUDE_AHTX0=1
+  -D ENV_INCLUDE_BME280=1
+  -D ENV_INCLUDE_INA3221=1
+  -D ENV_INCLUDE_INA219=1
+build_src_filter = ${nrf52840_me25ls01.build_src_filter}
+  +<helpers/*.cpp>
+  +<helpers/nrf52/MinewsemiME25LS01Board.cpp>  
+  +<../variants/minewsemi_me25ls01>
+  +<helpers/sensors>
+debug_tool = jlink
+upload_protocol = nrfutil
+lib_deps = ${nrf52840_me25ls01.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+  stevemarple/MicroNMEA @ ^2.0.6
+  end2endzone/NonBlockingRTTTL@^1.3.0
+  adafruit/Adafruit SSD1306 @ ^2.5.13
+  adafruit/Adafruit INA3221 Library @ ^1.0.1
+  adafruit/Adafruit INA219 @ ^1.2.3
+  adafruit/Adafruit AHTX0 @ ^2.0.5
+  adafruit/Adafruit BME280 Library @ ^2.3.0  
+
+[env:Minewsemi_me25ls01_companion_radio_ble]
+extends = me25ls01
+build_flags = ${me25ls01.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+;  -D BLE_DEBUG_LOGGING=1
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+  -D DISPLAY_CLASS=NullDisplayDriver
+  ;-D PIN_BUZZER=25
+  ;-D PIN_BUZZER_EN=37      
+build_src_filter = ${me25ls01.build_src_filter}
+  +<helpers/nrf52/SerialBLEInterface.cpp>
+  +<../examples/companion_radio/*.cpp>
+lib_deps = ${me25ls01.lib_deps}
+  adafruit/RTClib @ ^2.1.3
+ 
+
+[env:Minewsemi_me25ls01_repeater]
+extends = me25ls01
+build_flags = ${me25ls01.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+;  -D BLE_DEBUG_LOGGING=1
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+  -D ADVERT_NAME='"ME25LS01 Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+  -D DISPLAY_CLASS=NullDisplayDriver
+build_src_filter = ${me25ls01.build_src_filter}
+  +<../examples/simple_repeater>
+lib_deps = ${me25ls01.lib_deps}
+  adafruit/RTClib @ ^2.1.3
+
+
+
+[env:Minewsemi_me25ls01_room_server]
+extends = me25ls01
+build_flags = ${me25ls01.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+;  -D BLE_PIN_CODE=123456
+;  -D BLE_DEBUG_LOGGING=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+  -D ADVERT_NAME='"ME25LS01 Room"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+  -D MAX_NEIGHBOURS=8
+  -D DISPLAY_CLASS=NullDisplayDriver
+build_src_filter = ${me25ls01.build_src_filter}
+  +<../examples/simple_room_server>
+lib_deps = ${me25ls01.lib_deps}
+  adafruit/RTClib @ ^2.1.3
+
+[env:Minewsemi_me25ls01_terminal_chat]
+extends = me25ls01
+build_flags = ${me25ls01.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+;  -D BLE_DEBUG_LOGGING=1
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+  -D ADVERT_NAME='"ME25LS01 Chat"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+  -D MAX_NEIGHBOURS=8
+  -D DISPLAY_CLASS=NullDisplayDriver
+build_src_filter = ${me25ls01.build_src_filter}
+  +<../examples/simple_secure_chat/main.cpp>
+lib_deps = ${me25ls01.lib_deps}
+  adafruit/RTClib @ ^2.1.3
+
+[env:Minewsemi_me25ls01_companion_radio_usb]
+extends = me25ls01
+build_flags = ${me25ls01.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  ;-D BLE_PIN_CODE=123456
+;  -D BLE_DEBUG_LOGGING=1
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+  -D DISPLAY_CLASS=NullDisplayDriver
+build_src_filter = ${me25ls01.build_src_filter}
+  +<helpers/nrf52/*.cpp>
+  +<../examples/companion_radio>
+lib_deps = ${me25ls01.lib_deps}
+  adafruit/RTClib @ ^2.1.3
+

+ 98 - 0
variants/minewsemi_me25ls01/target.cpp

@@ -0,0 +1,98 @@
+#include <Arduino.h>
+#include "target.h"
+
+MinewsemiME25LS01Board board;
+
+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 rtc_clock;
+extern EnvironmentSensorManager sensors;
+#if ENV_INCLUDE_GPS
+  #include <helpers/sensors/MicroNMEALocationProvider.h>
+  MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
+  EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
+#else
+  EnvironmentSensorManager sensors;
+#endif
+
+#ifdef DISPLAY_CLASS
+  NullDisplayDriver display;
+#endif
+
+#ifndef LORA_CR
+  #define LORA_CR      5
+#endif
+
+#ifdef RF_SWITCH_TABLE
+static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
+  RADIOLIB_LR11X0_DIO5,
+  RADIOLIB_LR11X0_DIO6,
+  RADIOLIB_LR11X0_DIO7,
+  RADIOLIB_LR11X0_DIO8, 
+  RADIOLIB_NC
+};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+  // mode                 DIO5  DIO6  DIO7  DIO8
+  { LR11x0::MODE_STBY,   {LOW,  LOW,  LOW,  LOW  }},  
+  { LR11x0::MODE_RX,     {HIGH, LOW,  LOW,  HIGH }},
+  { LR11x0::MODE_TX,     {HIGH, HIGH, LOW,  HIGH }},
+  { LR11x0::MODE_TX_HP,  {LOW,  HIGH, LOW,  HIGH }},
+  { LR11x0::MODE_TX_HF,  {LOW,  LOW,  LOW,  LOW  }}, 
+  { LR11x0::MODE_GNSS,   {LOW,  LOW,  HIGH, LOW  }},
+  { LR11x0::MODE_WIFI,   {LOW,  LOW,  LOW,  LOW  }},  
+  END_OF_MODE_TABLE,
+};
+#endif
+
+bool radio_init() {
+  //rtc_clock.begin(Wire);
+  
+#ifdef LR11X0_DIO3_TCXO_VOLTAGE
+  float tcxo = LR11X0_DIO3_TCXO_VOLTAGE;
+#else
+  float tcxo = 1.6f;
+#endif
+
+  SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
+  SPI.begin();
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
+  if (status != RADIOLIB_ERR_NONE) {
+    Serial.print("ERROR: radio init failed: ");
+    Serial.println(status);
+    return false;  // fail
+  }
+  
+  radio.setCRC(1);
+
+#ifdef RF_SWITCH_TABLE
+  radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
+#endif
+#ifdef RX_BOOSTED_GAIN
+  radio.setRxBoostedGainMode(RX_BOOSTED_GAIN);
+#endif
+
+  return true;  // success
+}
+
+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(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);  // create new random identity
+}

+ 29 - 0
variants/minewsemi_me25ls01/target.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/RadioLibWrappers.h>
+#include <helpers/nrf52/MinewsemiME25LS01Board.h>
+#include <helpers/CustomLR1110Wrapper.h>
+#include <helpers/ArduinoHelpers.h>
+#include <helpers/SensorManager.h>
+#include <helpers/sensors/LocationProvider.h>
+#include <helpers/sensors/EnvironmentSensorManager.h>
+#ifdef DISPLAY_CLASS
+  #include "NullDisplayDriver.h"
+#endif
+
+#ifdef DISPLAY_CLASS
+  extern NullDisplayDriver display;
+#endif
+
+extern MinewsemiME25LS01Board board;
+extern WRAPPER_CLASS radio_driver;
+extern VolatileRTCClock 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(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 70 - 0
variants/minewsemi_me25ls01/variant.cpp

@@ -0,0 +1,70 @@
+#include "variant.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] =
+{
+  0,  // P0.00
+  1,  // P0.01
+  2,  // P0.02
+  3,  // P0.03
+  4,  // P0.04
+  5,  // P0.05
+  6,  // P0.06
+  7,  // P0.07
+  8,  // P0.08
+  9,  // P0.09
+  10, // P0.10
+  11, // P0.11
+  12, // P0.12
+  13, // P0.13, PIN_SERIAL1_TX
+  14, // P0.14, PIN_SERIAL1_RX
+  15, // P0.15, PIN_SERIAL2_RX
+  16, // P0.16, PIN_WIRE_SCL
+  17, // P0.17, PIN_SERIAL2_TX
+  18, // P0.18
+  19, // P0.19
+  20, // P0.20
+  21, // P0.21, PIN_WIRE_SDA
+  22, // P0.22
+  23, // P0.23
+  24, // P0.24, 
+  25, // P0.25, 
+  26, // P0.26, 
+  27, // P0.27, 
+  28, // P0.28
+  29, // P0.29, 
+  30, // P0.30
+  31, // P0.31, BATTERY_PIN
+  32, // P1.00
+  33, // P1.01, LORA_DIO_1
+  34, // P1.02
+  35, // P1.03, 
+  36, // P1.04
+  37, // P1.05, LR1110_EN
+  38, // P1.06, 
+  39, // P1.07, 
+  40, // P1.08, PIN_SPI_MISO
+  41, // P1.09, PIN_SPI_MOSI
+  42, // P1.10, LORA_RESET
+  43, // P1.11, GPS_EN
+  44, // P1.12, GPS_SLEEP_INT
+  45, // P1.13
+  46, // P1.14, GPS_RESETB
+  47, // P1.15, PIN_GPS_RESET
+  255,  // NRFX_SPIM_PIN_NOT_USED
+};
+
+void initVariant()
+{
+  pinMode(BATTERY_PIN, INPUT);
+  pinMode(PIN_BUTTON1, INPUT);
+
+  // pinMode(PIN_3V3_EN, OUTPUT);
+  // pinMode(PIN_3V3_ACC_EN, OUTPUT);
+  pinMode(LED_PIN, OUTPUT);
+  pinMode(P_LORA_TX_LED, OUTPUT);
+
+  digitalWrite(LED_PIN, HIGH);
+  digitalWrite(P_LORA_TX_LED, LOW);
+}

+ 94 - 0
variants/minewsemi_me25ls01/variant.h

@@ -0,0 +1,94 @@
+#pragma once
+
+#include "WVariant.h"
+
+// Low frequency clock source
+#define USE_LFXO    // 32.768 kHz crystal oscillator
+#define VARIANT_MCK (64000000ul)
+// #define USE_LFRC    // 32.768 kHz RC oscillator
+
+// Power
+#define BATTERY_PIN             (31)
+#define BATTERY_IMMUTABLE
+#define ADC_MULTIPLIER          (2.0F)
+
+#define ADC_RESOLUTION          (14)
+#define BATTERY_SENSE_RES       (12)
+
+// Number of pins
+#define PINS_COUNT              (48)
+#define NUM_DIGITAL_PINS        (48)
+#define NUM_ANALOG_INPUTS       (6)
+#define NUM_ANALOG_OUTPUTS      (0)
+
+// UART pin definition
+#define PIN_SERIAL1_RX          (14)             // P0.14
+#define PIN_SERIAL1_TX          (13)             // P0.13
+
+#define PIN_SERIAL2_RX          (15)             // P0.15
+#define PIN_SERIAL2_TX          (17)             // P0.17
+
+// I2C pin definition
+#define HAS_WIRE                (1)     
+#define WIRE_INTERFACES_COUNT   (1)     
+
+#define PIN_WIRE_SDA            (21)             // P0.21
+#define PIN_WIRE_SCL            (16)             // P0.16
+#define I2C_NO_RESCAN
+
+// SPI pin definition
+#define SPI_INTERFACES_COUNT    (1)
+
+#define PIN_SPI_MISO            (0 + 29)            // P0.29
+#define PIN_SPI_MOSI            (0 + 2)             // P0.2
+#define PIN_SPI_SCK             (32 + 15)           // P1.15
+#define PIN_SPI_NSS             (32 + 13)           // P1.13
+
+// Builtin LEDs
+#define LED_BUILTIN             (-1)
+#define LED_RED                (32 + 5)            // P1.5
+#define LED_BLUE               (32 + 7)            // P1.7 
+#define LED_PIN                 LED_BLUE
+#define P_LORA_TX_LED           LED_RED
+
+#define LED_STATE_ON            HIGH
+
+// Builtin buttons
+
+#define PIN_BUTTON1             (0 + 27)             // P0.6
+#define BUTTON_PIN              PIN_BUTTON1
+
+// LR1110
+#define LORA_DIO_1              (32 + 12)           // P1.12
+#define LORA_DIO_2              (32 + 10)           // P1.10 
+#define LORA_NSS                (PIN_SPI_NSS)       // P1.13
+#define LORA_RESET              (32 + 11)           // P1.11 
+#define LORA_BUSY               (32 + 10)           // P1.10
+#define LORA_SCLK               (PIN_SPI_SCK)       // P1.15
+#define LORA_MISO               (PIN_SPI_MISO)      // P0.29
+#define LORA_MOSI               (PIN_SPI_MOSI)      // P0.2
+#define LORA_CS                 PIN_SPI_NSS         // P1.13
+ 
+#define LR11X0_DIO_AS_RF_SWITCH    true
+#define LR11X0_DIO3_TCXO_VOLTAGE   1.6
+
+#define LR1110_IRQ_PIN LORA_DIO_1
+#define LR1110_NRESET_PIN LORA_RESET
+#define LR1110_BUSY_PIN LORA_DIO_2
+#define LR1110_SPI_NSS_PIN LORA_CS
+#define LR1110_SPI_SCK_PIN LORA_SCLK
+#define LR1110_SPI_MOSI_PIN LORA_MOSI
+#define LR1110_SPI_MISO_PIN LORA_MISO
+
+// GPS
+#define HAS_GPS                 0
+#define GPS_RX_PIN              PIN_SERIAL1_RX
+#define GPS_TX_PIN              PIN_SERIAL1_TX
+
+#define GPS_EN                  (-1)            // P1.11
+#define GPS_RESET               (-1)            // P1.15
+
+#define GPS_VRTC_EN             (-1)             // P0.8
+#define GPS_SLEEP_INT           (-1)            // P1.12
+#define GPS_RTC_INT             (-1)            // P0.15
+#define GPS_RESETB              (-1)            // P1.14

+ 1 - 1
variants/nano_g2_ultra/platformio.ini

@@ -36,7 +36,7 @@ build_flags =
   -I src/helpers/ui
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
-  -D BLE_PIN_CODE=0
+  -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D OFFLINE_QUEUE_SIZE=256
   -D DISPLAY_CLASS=SH1106Display

+ 1 - 1
variants/rak3x72/target.cpp

@@ -38,7 +38,7 @@ bool radio_init() {
 
   radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
 
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, STM32WL_TCXO_VOLTAGE, 0); 
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, STM32WL_TCXO_VOLTAGE, 0); 
 
   if (status != RADIOLIB_ERR_NONE) {
     Serial.print("ERROR: radio init failed: ");

+ 8 - 2
variants/rak4631/platformio.ini

@@ -6,8 +6,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/rak4631
   -D RAK_4631
-  -D PIN_USER_BTN=9
-  -D PIN_USER_BTN_ANA=31
   -D PIN_BOARD_SCL=14
   -D PIN_BOARD_SDA=13
   -D PIN_OLED_RESET=-1
@@ -84,6 +82,8 @@ build_src_filter = ${rak4631.build_src_filter}
 extends = rak4631
 build_flags =
   ${rak4631.build_flags}
+  -D PIN_USER_BTN=9
+  -D PIN_USER_BTN_ANA=31
   -D DISPLAY_CLASS=SSD1306Display
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
@@ -100,6 +100,8 @@ lib_deps =
 extends = rak4631
 build_flags =
   ${rak4631.build_flags}
+  -D PIN_USER_BTN=9
+  -D PIN_USER_BTN_ANA=31
   -D DISPLAY_CLASS=SSD1306Display
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
@@ -120,6 +122,8 @@ lib_deps =
 extends = rak4631
 build_flags =
   ${rak4631.build_flags}
+  -D PIN_USER_BTN=9
+  -D PIN_USER_BTN_ANA=31
   -D DISPLAY_CLASS=SSD1306Display
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
@@ -145,6 +149,8 @@ lib_deps =
 extends = rak4631
 build_flags =
   ${rak4631.build_flags}
+  -D PIN_USER_BTN=9
+  -D PIN_USER_BTN_ANA=31
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=1
 ;  -D MESH_PACKET_LOGGING=1

+ 81 - 0
variants/sensecap_solar/SenseCapSolarBoard.cpp

@@ -0,0 +1,81 @@
+#include <Arduino.h>
+#include "SenseCapSolarBoard.h"
+
+#include <bluefruit.h>
+#include <Wire.h>
+
+static BLEDfu bledfu;
+
+static void connect_callback(uint16_t conn_handle) {
+  (void)conn_handle;
+  MESH_DEBUG_PRINTLN("BLE client connected");
+}
+
+static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
+  (void)conn_handle;
+  (void)reason;
+
+  MESH_DEBUG_PRINTLN("BLE client disconnected");
+}
+
+void SenseCapSolarBoard::begin() {
+  // for future use, sub-classes SHOULD call this from their begin()
+  startup_reason = BD_STARTUP_NORMAL;
+
+#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
+  Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
+#endif
+
+  Wire.begin();
+
+#ifdef P_LORA_TX_LED
+  pinMode(P_LORA_TX_LED, OUTPUT);
+  digitalWrite(P_LORA_TX_LED, LOW);
+#endif
+
+  delay(10);   // give sx1262 some time to power up
+}
+
+bool SenseCapSolarBoard::startOTAUpdate(const char* id, char reply[]) {
+  // Config the peripheral connection with maximum bandwidth
+  // more SRAM required by SoftDevice
+  // Note: All config***() function must be called before begin()
+  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
+  Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
+
+  Bluefruit.begin(1, 0);
+  // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
+  Bluefruit.setTxPower(4);
+  // Set the BLE device name
+  Bluefruit.setName("SENSECAP_SOLAR_OTA");
+
+  Bluefruit.Periph.setConnectCallback(connect_callback);
+  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
+
+  // To be consistent OTA DFU should be added first if it exists
+  bledfu.begin();
+
+  // Set up and start advertising
+  // Advertising packet
+  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
+  Bluefruit.Advertising.addTxPower();
+  Bluefruit.Advertising.addName();
+
+  /* Start Advertising
+    - Enable auto advertising if disconnected
+    - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
+    - Timeout for fast mode is 30 seconds
+    - Start(timeout) with timeout = 0 will advertise forever (until connected)
+
+    For recommended advertising interval
+    https://developer.apple.com/library/content/qa/qa1931/_index.html
+  */
+  Bluefruit.Advertising.restartOnDisconnect(true);
+  Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
+  Bluefruit.Advertising.setFastTimeout(30);   // number of seconds in fast mode
+  Bluefruit.Advertising.start(0);             // 0 = Don't stop advertising after n seconds
+
+  strcpy(reply, "OK - started");
+
+  return true;
+}

+ 42 - 0
variants/sensecap_solar/SenseCapSolarBoard.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include <MeshCore.h>
+#include <Arduino.h>
+
+class SenseCapSolarBoard : public mesh::MainBoard {
+protected:
+  uint8_t startup_reason;
+
+public:
+  void begin();
+  uint8_t getStartupReason() const override { return startup_reason; }
+
+#if defined(P_LORA_TX_LED)
+  void onBeforeTransmit() override {
+    digitalWrite(P_LORA_TX_LED, HIGH);   // turn TX LED on
+  }
+  void onAfterTransmit() override {
+    digitalWrite(P_LORA_TX_LED, LOW);   // turn TX LED off
+  }
+#endif
+
+  uint16_t getBattMilliVolts() override {
+    digitalWrite(VBAT_ENABLE, LOW);
+    int adcvalue = 0;
+    analogReadResolution(12);
+    analogReference(AR_INTERNAL_3_0);
+    delay(10);
+    adcvalue = analogRead(BATTERY_PIN);
+    return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
+  }
+
+  const char* getManufacturerName() const override {
+    return "Seeed SenseCap Solar";
+  }
+
+  void reboot() override {
+    NVIC_SystemReset();
+  }
+
+  bool startOTAUpdate(const char* id, char reply[]) override;
+};

+ 75 - 0
variants/sensecap_solar/platformio.ini

@@ -0,0 +1,75 @@
+[SenseCap_Solar]
+extends = nrf52_base
+board = seeed_sensecap_solar
+board_build.ldscript = boards/nrf52840_s140_v7.ld
+build_flags = ${nrf52_base.build_flags}
+  -I lib/nrf52/s140_nrf52_7.3.0_API/include
+  -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
+  -I variants/sensecap_solar
+  -I src/helpers/nrf52
+  -D NRF52_PLATFORM=1
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D P_LORA_TX_LED=12
+  -D P_LORA_DIO_1=1
+  -D P_LORA_RESET=2
+  -D P_LORA_BUSY=3
+  -D P_LORA_NSS=4
+  -D LORA_TX_POWER=22
+  -D SX126X_RXEN=5
+  -D SX126X_TXEN=RADIOLIB_NC
+  -D SX126X_DIO2_AS_RF_SWITCH=1
+  -D SX126X_DIO3_TCXO_VOLTAGE=1.8
+  -D SX126X_CURRENT_LIMIT=140
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D ENV_INCLUDE_AHTX0=1
+  -D ENV_INCLUDE_BME280=1
+  -D ENV_INCLUDE_BMP280=1
+  -D ENV_INCLUDE_SHTC3=1
+  -D ENV_INCLUDE_LPS22HB=1
+  -D ENV_INCLUDE_INA3221=1
+  -D ENV_INCLUDE_INA219=1
+build_src_filter = ${nrf52_base.build_src_filter}
+  +<helpers/*.cpp>
+  +<helpers/sensors>
+  +<helpers/nrf52/SenseCapSolarBoard.cpp>
+  +<../variants/SenseCap_Solar>
+debug_tool = jlink
+upload_protocol = nrfutil
+lib_deps =
+  ${nrf52_base.lib_deps}
+  rweather/Crypto @ ^0.4.0
+  adafruit/Adafruit INA3221 Library @ ^1.0.1
+  adafruit/Adafruit INA219 @ ^1.2.3
+  adafruit/Adafruit AHTX0 @ ^2.0.5
+  adafruit/Adafruit BME280 Library @ ^2.3.0
+  adafruit/Adafruit BMP280 Library @ ^2.6.8
+  adafruit/Adafruit SHTC3 Library @ ^1.0.1
+  arduino-libraries/Arduino_LPS22HB @ ^1.0.2
+
+[env:SenseCap_Solar_repeater]
+extends = SenseCap_Solar
+build_flags =
+  ${SenseCap_Solar.build_flags}
+  -D ADVERT_NAME='"SenseCap_Solar Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${SenseCap_Solar.build_src_filter}
+  +<../examples/simple_repeater/main.cpp>
+
+[env:SenseCap_Solar_room_server]
+extends = SenseCap_Solar
+build_flags =
+  ${SenseCap_Solar.build_flags}
+  -D ADVERT_NAME='"SenseCap_Solar Room"'
+  -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 = ${SenseCap_Solar.build_src_filter}
+  +<../examples/simple_room_server/main.cpp>

+ 39 - 0
variants/sensecap_solar/target.cpp

@@ -0,0 +1,39 @@
+#include <Arduino.h>
+#include "target.h"
+#include <helpers/ArduinoHelpers.h>
+
+SenseCapSolarBoard board;
+
+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(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);  // create new random identity
+}

+ 21 - 0
variants/sensecap_solar/target.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/RadioLibWrappers.h>
+#include <SenseCapSolarBoard.h>
+#include <helpers/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/ArduinoHelpers.h>
+#include <helpers/sensors/EnvironmentSensorManager.h>
+
+extern SenseCapSolarBoard 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(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 69 - 0
variants/sensecap_solar/variant.cpp

@@ -0,0 +1,69 @@
+#include "variant.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+#include "nrf.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+    // D0 .. D10 - Peripheral control pins
+    2,  // D0  P0.02 (A0)    GNSS_WAKEUP
+    3,  // D1  P0.03 (A1)    LORA_DIO1
+    28, // D2  P0.28 (A2)    LORA_RESET
+    29, // D3  P0.29 (A3)    LORA_BUSY
+    4,  // D4  P0.04 (A4/SDA) LORA_CS
+    5,  // D5  P0.05 (A5/SCL) LORA_SW
+    43, // D6  P1.11 (UART_TX) GNSS_TX
+    44, // D7  P1.12 (UART_RX) GNSS_RX
+    45, // D8  P1.13 (SPI_SCK) LORA_SCK
+    46, // D9  P1.14 (SPI_MISO) LORA_MISO
+    47, // D10 P1.15 (SPI_MOSI) LORA_MOSI
+
+    // D11-D12 - LED outputs
+    15, // D11 P0.15 User LED
+    19, // D12 P0.19 Breathing LED
+
+    // D13 - User input
+    33, // D13 P1.01 User Button
+
+    // D14-D15 - Grove/NFC interface
+    9,  // D14 P0.09 NFC1/GROVE_D1
+    10, // D15 P0.10 NFC2/GROVE_D0
+
+    // D16 - Power management
+    // 31, // D16 P0.31 VBAT_ADC (Battery voltage)
+    31, // D16 P0.31 VBAT_ADC (Battery voltage)
+    // D17 - GNSS control
+    35, // D17 P1.03 GNSS_RESET
+
+    37, // D18 P1.05 GNSS_ENABLE
+    14, // D19 P0.14 BAT_READ
+    39, // D20 P1.07 USER_BUTTON
+
+    //
+    21, // D21 P0.21 (QSPI_SCK)
+    25, // D22 P0.25 (QSPI_CSN)
+    20, // D23 P0.20 (QSPI_SIO_0 DI)
+    24, // D24 P0.24 (QSPI_SIO_1 DO)
+    22, // D25 P0.22 (QSPI_SIO_2 WP)
+    23, // D26 P0.23 (QSPI_SIO_3 HOLD)
+};
+
+void initVariant() {
+    pinMode(GPS_EN, OUTPUT);
+    digitalWrite(GPS_EN, LOW);
+
+    pinMode(BATTERY_PIN, INPUT);
+    pinMode(VBAT_ENABLE, OUTPUT);
+    digitalWrite(VBAT_ENABLE, LOW);
+
+    pinMode(PIN_QSPI_CS, OUTPUT);
+    digitalWrite(PIN_QSPI_CS, HIGH);
+
+    pinMode(LED_GREEN, OUTPUT);
+    digitalWrite(LED_GREEN, LOW);
+
+    pinMode(LED_BLUE, OUTPUT);
+    digitalWrite(LED_BLUE, LOW);
+
+    pinMode(GPS_EN, OUTPUT);
+    digitalWrite(GPS_EN, HIGH);
+}

+ 85 - 0
variants/sensecap_solar/variant.h

@@ -0,0 +1,85 @@
+#ifndef _SEEED_SENSECAP_SOLAR_H_
+#define _SEEED_SENSECAP_SOLAR_H_
+
+/** Master clock frequency */
+#define VARIANT_MCK             (64000000ul)
+
+#define USE_LFXO                // Board uses 32khz crystal for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#define PINS_COUNT              (33)
+#define NUM_DIGITAL_PINS        (33)
+#define NUM_ANALOG_INPUTS       (8)
+#define NUM_ANALOG_OUTPUTS      (0)
+
+// LEDs
+#define PIN_LED                 (12)
+#define LED_PWR                 (PINS_COUNT)
+
+#define LED_BUILTIN             (PIN_LED)
+
+#define LED_RED                 (PINS_COUNT)
+#define LED_GREEN               (12)
+#define LED_BLUE                (11)
+
+#define LED_STATE_ON            (1)     // State when LED is litted
+
+// Buttons
+#define PIN_BUTTON1             (13)
+#define PIN_BUTTON2             (20)
+
+#define VBAT_ENABLE             (19)    // Output LOW to enable reading of the BAT voltage.
+
+// Analog pins
+#define BATTERY_PIN             (16)    // Read the BAT voltage.
+#define AREF_VOLTAGE            (3.0F)
+#define ADC_MULTIPLIER          (3.0F) // 1M, 512k divider bridge
+#define ADC_RESOLUTION          (12)
+
+// Serial interfaces
+#define PIN_SERIAL1_RX          (7)
+#define PIN_SERIAL1_TX          (6)
+
+// SPI Interfaces
+#define SPI_INTERFACES_COUNT    (1)
+
+#define PIN_SPI_MISO            (9)
+#define PIN_SPI_MOSI            (10)
+#define PIN_SPI_SCK             (8)
+
+// Lora SPI is on SPI0
+#define  P_LORA_SCLK            PIN_SPI_SCK
+#define  P_LORA_MISO            PIN_SPI_MISO
+#define  P_LORA_MOSI            PIN_SPI_MOSI
+
+// Wire Interfaces
+#define WIRE_INTERFACES_COUNT   (1)
+
+#define PIN_WIRE_SDA            (14)
+#define PIN_WIRE_SCL            (15)
+
+// GPS L76KB
+#define GPS_BAUDRATE            9600
+#define GPS_THREAD_INTERVAL     50
+#define PIN_GPS_TX              PIN_SERIAL1_RX
+#define PIN_GPS_RX              PIN_SERIAL1_TX
+#define PIN_GPS_STANDBY         (0)
+#define GPS_EN                  (18)
+
+// QSPI Pins
+#define PIN_QSPI_SCK            (21)
+#define PIN_QSPI_CS             (22)
+#define PIN_QSPI_IO0            (23)
+#define PIN_QSPI_IO1            (24)
+#define PIN_QSPI_IO2            (25)
+#define PIN_QSPI_IO3            (26)
+
+#define EXTERNAL_FLASH_DEVICES P25Q16H
+#define EXTERNAL_FLASH_USE_QSPI
+
+#endif

+ 3 - 2
variants/t1000-e/target.cpp

@@ -54,14 +54,15 @@ bool radio_init() {
 
   SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
   SPI.begin();
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
   if (status != RADIOLIB_ERR_NONE) {
     Serial.print("ERROR: radio init failed: ");
     Serial.println(status);
     return false;  // fail
   }
   
-  radio.setCRC(1);
+  radio.setCRC(2);
+  radio.explicitHeader();
 
 #ifdef RF_SWITCH_TABLE
   radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);

+ 1 - 1
variants/wio-e5-dev/target.cpp

@@ -35,7 +35,7 @@ bool radio_init() {
 
   radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
 
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, 1.7, 0);
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, 1.7, 0);
 
   if (status != RADIOLIB_ERR_NONE) {
     Serial.print("ERROR: radio init failed: ");

+ 1 - 1
variants/wio-e5-mini/target.cpp

@@ -33,7 +33,7 @@ bool radio_init() {
 
   radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
 
-  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, 1.7, 0);
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, 1.7, 0);
 
   if (status != RADIOLIB_ERR_NONE) {
     Serial.print("ERROR: radio init failed: ");

+ 90 - 3
variants/xiao_c3/platformio.ini

@@ -1,6 +1,27 @@
 [Xiao_esp32_C3]
 extends = esp32_base
 board = seeed_xiao_esp32c3
+build_flags =
+  ${esp32_base.build_flags}
+  -I variants/xiao_c3
+  -D ESP32_CPU_FREQ=80
+  -D PIN_VBAT_READ=D0
+  -D P_LORA_DIO_1=D1
+  -D P_LORA_NSS=D4
+  -D P_LORA_RESET=D2
+  -D P_LORA_BUSY=D3
+  -D PIN_BOARD_SDA=D6
+  -D PIN_BOARD_SCL=D7
+  -D SX126X_RXEN=D5
+  -D SX126X_DIO2_AS_RF_SWITCH=true
+  -D SX126X_DIO3_TCXO_VOLTAGE=1.8
+  -D SX126X_CURRENT_LIMIT=140
+build_src_filter = ${esp32_base.build_src_filter}
+  +<../variants/xiao_c3>
+
+[Xiao_esp32_C3_custom]
+extends = esp32_base
+board = seeed_xiao_esp32c3
 build_flags =
   ${esp32_base.build_flags}
   -I variants/xiao_c3
@@ -30,7 +51,7 @@ build_flags =
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D SX126X_RX_BOOSTED_GAIN=1
   -D LORA_TX_POWER=22
-  -D ADVERT_NAME='"Xiao Repeater"'
+  -D ADVERT_NAME='"Xiao C3 Repeater"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADMIN_PASSWORD='"password"'
@@ -41,8 +62,74 @@ lib_deps =
   ${Xiao_esp32_C3.lib_deps}
   ${esp32_ota.lib_deps}
 
-[env:Xiao_C3_Repeater_sx1268]
+[env:Xiao_C3_companion_radio_ble]
 extends = Xiao_esp32_C3
+build_src_filter = ${Xiao_esp32_C3.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/esp32/*.cpp>
+build_flags =
+  ${Xiao_esp32_C3.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+  -D OFFLINE_QUEUE_SIZE=256
+  ; -D BLE_DEBUG_LOGGING=1
+  ; -D MESH_PACKET_LOGGING=1
+  ; -D MESH_DEBUG=1
+lib_deps =
+  ${Xiao_esp32_C3.lib_deps}
+  ${esp32_ota.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Xiao_C3_companion_radio_usb]
+extends = Xiao_esp32_C3
+build_src_filter = ${Xiao_esp32_C3.build_src_filter}
+  +<../examples/companion_radio>
+  +<helpers/esp32/*.cpp>
+build_flags =
+  ${Xiao_esp32_C3.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D LORA_TX_POWER=22
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+  -D OFFLINE_QUEUE_SIZE=256
+  ; -D BLE_DEBUG_LOGGING=1
+  ; -D MESH_PACKET_LOGGING=1
+  ; -D MESH_DEBUG=1
+lib_deps =
+  ${Xiao_esp32_C3.lib_deps}
+  ${esp32_ota.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Xiao_C3_Repeater_sx1262_custom]
+extends = Xiao_esp32_C3_custom
+build_src_filter = ${Xiao_esp32_C3.build_src_filter}
+  +<../examples/simple_repeater/main.cpp>
+build_flags =
+  ${Xiao_esp32_C3.build_flags}
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D SX126X_RX_BOOSTED_GAIN=1
+  -D LORA_TX_POWER=22
+  -D ADVERT_NAME='"Xiao Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+lib_deps =
+  ${Xiao_esp32_C3.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Xiao_C3_Repeater_sx1268_custom]
+extends = Xiao_esp32_C3_custom
 build_src_filter = ${Xiao_esp32_C3.build_src_filter}
   +<../examples/simple_repeater/main.cpp>
 build_flags =
@@ -59,4 +146,4 @@ build_flags =
  ; -D MESH_DEBUG=1
 lib_deps =
   ${Xiao_esp32_C3.lib_deps}
-  ${esp32_ota.lib_deps}
+  ${esp32_ota.lib_deps}

+ 0 - 0
src/helpers/nrf52/XiaoNrf52Board.cpp → variants/xiao_nrf52/XiaoNrf52Board.cpp


+ 3 - 4
src/helpers/nrf52/XiaoNrf52Board.h → variants/xiao_nrf52/XiaoNrf52Board.h

@@ -5,20 +5,19 @@
 
 #ifdef XIAO_NRF52
 
-// LoRa radio module pins for Seeed Xiao-nrf52
+// redefine lora pins if using the S3 variant of SX1262 board
 #ifdef SX1262_XIAO_S3_VARIANT
   #undef P_LORA_DIO_1
   #undef P_LORA_BUSY
   #undef P_LORA_RESET
   #undef P_LORA_NSS
+  #undef SX126X_RXEN
   #define  P_LORA_DIO_1       D0
   #define  P_LORA_BUSY        D1
   #define  P_LORA_RESET       D2
   #define  P_LORA_NSS         D3
+  #define  SX126X_RXEN        D4
 #endif
-//#define  SX126X_POWER_EN  37
-
-
 
 class XiaoNrf52Board : public mesh::MainBoard {
 protected:

+ 1 - 2
variants/xiao_nrf52/platformio.ini

@@ -50,8 +50,7 @@ build_flags = ${nrf52840_xiao.build_flags}
   -D ENV_INCLUDE_INA219=1
 build_src_filter = ${nrf52840_xiao.build_src_filter}
   +<helpers/*.cpp>
-  +<helpers/sensors>  
-  +<helpers/nrf52/XiaoNrf52Board.cpp>
+  +<helpers/sensors>
   +<../variants/xiao_nrf52>
 debug_tool = jlink
 upload_protocol = nrfutil

+ 1 - 1
variants/xiao_nrf52/target.h

@@ -3,7 +3,7 @@
 #define RADIOLIB_STATIC_ONLY 1
 #include <RadioLib.h>
 #include <helpers/RadioLibWrappers.h>
-#include <helpers/nrf52/XiaoNrf52Board.h>
+#include <XiaoNrf52Board.h>
 #include <helpers/CustomSX1262Wrapper.h>
 #include <helpers/AutoDiscoverRTCClock.h>
 #include <helpers/ArduinoHelpers.h>

+ 104 - 0
variants/xiao_rp2040/platformio.ini

@@ -0,0 +1,104 @@
+[Xiao_rp2040]
+extends = rp2040_base
+
+board = seeed_xiao_rp2040
+board_build.filesystem_size = 0.5m
+
+build_flags = ${rp2040_base.build_flags}
+  -I variants/xiao_rp2040
+  -D SX126X_CURRENT_LIMIT=140
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D SX126X_RX_BOOSTED_GAIN=1
+; Debug options
+  ; -D DEBUG_RP2040_WIRE=1
+  ; -D DEBUG_RP2040_SPI=1
+  ; -D DEBUG_RP2040_CORE=1
+  ; -D RADIOLIB_DEBUG_SPI=1
+  ; -D DEBUG_RP2040_PORT=Serial
+
+build_src_filter = ${rp2040_base.build_src_filter}
+  +<helpers/rp2040/XiaoRP2040Board.cpp>
+  +<../variants/xiao_rp2040>
+
+lib_deps = ${rp2040_base.lib_deps}
+
+[env:Xiao_rp2040_Repeater]
+extends = Xiao_rp2040
+build_flags = ${Xiao_rp2040.build_flags}
+  -D ADVERT_NAME='"Xiao Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=8
+  -D MESH_PACKET_LOGGING=1
+  -D MESH_DEBUG=1
+build_src_filter = ${Xiao_rp2040.build_src_filter}
+  +<../examples/simple_repeater>
+
+[env:Xiao_rp2040_room_server]
+extends = Xiao_rp2040
+build_flags = ${Xiao_rp2040.build_flags}
+  -D ADVERT_NAME='"Xiao 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 = ${Xiao_rp2040.build_src_filter}
+  +<../examples/simple_room_server>
+
+[env:Xiao_rp2040_companion_radio_usb]
+extends = Xiao_rp2040
+build_flags = ${Xiao_rp2040.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
+; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
+build_src_filter = ${Xiao_rp2040.build_src_filter}
+  +<../examples/companion_radio>
+lib_deps = ${Xiao_rp2040.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+; [env:Xiao_rp2040_companion_radio_ble]
+; extends = Xiao_rp2040
+; build_flags = ${Xiao_rp2040.build_flags}
+;   -D MAX_CONTACTS=100
+;   -D MAX_GROUP_CHANNELS=8
+;   -D BLE_PIN_CODE=123456
+;   -D BLE_DEBUG_LOGGING=1
+; ;  -D MESH_PACKET_LOGGING=1
+; ;  -D MESH_DEBUG=1
+; build_src_filter = ${Xiao_rp2040.build_src_filter}
+;   +<../examples/companion_radio>
+; lib_deps = ${Xiao_rp2040.lib_deps}
+;   densaugeo/base64 @ ~1.4.0
+
+; [env:Xiao_rp2040_companion_radio_wifi]
+; extends = Xiao_rp2040
+; build_flags = ${Xiao_rp2040.build_flags}
+;   -D MAX_CONTACTS=100
+;   -D MAX_GROUP_CHANNELS=8
+;   -D WIFI_DEBUG_LOGGING=1
+;   -D WIFI_SSID='"myssid"'
+;   -D WIFI_PWD='"mypwd"'
+; ;  -D MESH_PACKET_LOGGING=1
+; ;  -D MESH_DEBUG=1
+; build_src_filter = ${Xiao_rp2040.build_src_filter}
+;   +<../examples/companion_radio>
+; lib_deps = ${Xiao_rp2040.lib_deps}
+;   densaugeo/base64 @ ~1.4.0
+
+[env:Xiao_rp2040_terminal_chat]
+extends = Xiao_rp2040
+build_flags = ${Xiao_rp2040.build_flags}
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${Xiao_rp2040.build_src_filter}
+  +<../examples/simple_secure_chat/main.cpp>
+lib_deps = ${Xiao_rp2040.lib_deps}
+  densaugeo/base64 @ ~1.4.0

+ 71 - 0
variants/xiao_rp2040/target.cpp

@@ -0,0 +1,71 @@
+#include "target.h"
+
+#include <Arduino.h>
+#include <helpers/ArduinoHelpers.h>
+
+XiaoRP2040Board board;
+
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+VolatileRTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+SensorManager sensors;
+
+#ifndef LORA_CR
+#define LORA_CR 5
+#endif
+
+bool radio_init() {
+  rtc_clock.begin(Wire);
+
+#ifdef SX126X_DIO3_TCXO_VOLTAGE
+  float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
+#else
+  float tcxo = 1.6f;
+#endif
+  int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
+
+  if (status != RADIOLIB_ERR_NONE) {
+    Serial.print("ERROR: radio init failed: ");
+    Serial.println(status);
+    return false; // fail
+  }
+
+  radio.setCRC(1);
+
+#ifdef SX126X_CURRENT_LIMIT
+  radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
+#endif
+
+#ifdef SX126X_DIO2_AS_RF_SWITCH
+  radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
+#endif
+
+#ifdef SX126X_RX_BOOSTED_GAIN
+  radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
+#endif
+
+  return true; // success
+}
+
+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(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng); // create new random identity
+}

+ 21 - 0
variants/xiao_rp2040/target.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+
+#include <RadioLib.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/CustomSX1262Wrapper.h>
+#include <helpers/RadioLibWrappers.h>
+#include <helpers/SensorManager.h>
+#include <helpers/rp2040/XiaoRP2040Board.h>
+
+extern XiaoRP2040Board board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern SensorManager 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(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();