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

Merge remote-tracking branch 'upstream/dev' into jbrazio/2025_3f11ad35

João Brázio 11 месяцев назад
Родитель
Сommit
cf4720bd34
60 измененных файлов с 1234 добавлено и 240 удалено
  1. 44 0
      boards/heltec_vision_master_t190.json
  2. 64 0
      build_as_lib.py
  3. 2 2
      examples/companion_radio/MyMesh.h
  4. 28 2
      examples/companion_radio/ui-new/UITask.cpp
  5. 27 2
      examples/simple_repeater/main.cpp
  6. 16 2
      examples/simple_room_server/main.cpp
  7. 86 29
      examples/simple_sensor/SensorMesh.cpp
  8. 5 3
      examples/simple_sensor/SensorMesh.h
  9. 16 0
      library.json
  10. 2 2
      platformio.ini
  11. 15 2
      src/helpers/CommonCLI.cpp
  12. 4 0
      src/helpers/CommonCLI.h
  13. 12 1
      src/helpers/esp32/SerialWifiInterface.cpp
  14. 39 0
      src/helpers/nrf52/T114Board.cpp
  15. 5 0
      src/helpers/nrf52/T114Board.h
  16. 8 0
      src/helpers/nrf52/ThinkNodeM1Board.h
  17. 19 3
      src/helpers/ui/MomentaryButton.cpp
  18. 2 0
      src/helpers/ui/MomentaryButton.h
  19. 12 1
      src/helpers/ui/ST7789Display.cpp
  20. 5 2
      src/helpers/ui/ST7789Display.h
  21. 2 2
      variants/generic_espnow/platformio.ini
  22. 2 2
      variants/heltec_ct62/platformio.ini
  23. 1 1
      variants/heltec_tracker/platformio.ini
  24. 3 3
      variants/heltec_v2/platformio.ini
  25. 7 5
      variants/heltec_v3/platformio.ini
  26. 5 3
      variants/heltec_vision_master_e213/platformio.ini
  27. 1 0
      variants/heltec_vision_master_e213/target.cpp
  28. 2 0
      variants/heltec_vision_master_e213/target.h
  29. 5 3
      variants/heltec_vision_master_e290/platformio.ini
  30. 1 0
      variants/heltec_vision_master_e290/target.cpp
  31. 2 0
      variants/heltec_vision_master_e290/target.h
  32. 69 0
      variants/heltec_vision_master_t190/HeltecT190Board.cpp
  33. 30 0
      variants/heltec_vision_master_t190/HeltecT190Board.h
  34. 61 0
      variants/heltec_vision_master_t190/pins_arduino.h
  35. 87 0
      variants/heltec_vision_master_t190/platformio.ini
  36. 54 0
      variants/heltec_vision_master_t190/target.cpp
  37. 29 0
      variants/heltec_vision_master_t190/target.h
  38. 5 3
      variants/heltec_wireless_paper/platformio.ini
  39. 1 0
      variants/heltec_wireless_paper/target.cpp
  40. 2 0
      variants/heltec_wireless_paper/target.h
  41. 2 16
      variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h
  42. 204 24
      variants/ikoka_stick_nrf/platformio.ini
  43. 3 3
      variants/lilygo_t3s3/platformio.ini
  44. 3 3
      variants/lilygo_t3s3_sx1276/platformio.ini
  45. 2 2
      variants/lilygo_tbeam_SX1262/platformio.ini
  46. 2 2
      variants/lilygo_tbeam_SX1276/platformio.ini
  47. 2 2
      variants/lilygo_tbeam_supreme_SX1262/platformio.ini
  48. 1 1
      variants/lilygo_tlora_c6/platformio.ini
  49. 23 3
      variants/lilygo_tlora_v2_1/platformio.ini
  50. 12 0
      variants/meshadventurer/platformio.ini
  51. 0 4
      variants/meshadventurer/target.cpp
  52. 35 31
      variants/nano_g2_ultra/nano-g2.h
  53. 5 1
      variants/rak4631/target.cpp
  54. 3 0
      variants/rak4631/target.h
  55. 21 2
      variants/station_g2/platformio.ini
  56. 60 1
      variants/t1000-e/platformio.ini
  57. 1 0
      variants/thinknode_m1/platformio.ini
  58. 2 67
      variants/xiao_c3/platformio.ini
  59. 64 2
      variants/xiao_c6/platformio.ini
  60. 9 3
      variants/xiao_s3_wio/platformio.ini

+ 44 - 0
boards/heltec_vision_master_t190.json

@@ -0,0 +1,44 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "esp32s3_out.ld",
+      "partitions": "default_16MB.csv",
+      "memory_type": "qio_opi"
+    },
+    "core": "esp32",
+    "extra_flags": [
+      "-DBOARD_HAS_PSRAM",
+      "-DARDUINO_USB_MODE=0",
+      "-DARDUINO_USB_CDC_ON_BOOT=1",
+      "-DARDUINO_RUNNING_CORE=1",
+      "-DARDUINO_EVENT_RUNNING_CORE=1"
+    ],
+    "f_cpu": "240000000L",
+    "f_flash": "80000000L",
+    "flash_mode": "qio",
+    "psram_type": "opi",
+    "hwids": [
+      ["0x303A", "0x1001"],
+      ["0x303A", "0x0002"]
+    ],
+    "mcu": "esp32s3",
+    "variant": "heltec_vision_master_t190"
+  },
+  "connectivity": ["wifi", "bluetooth", "lora"],
+  "debug": {
+    "openocd_target": "esp32s3.cfg"
+  },
+  "frameworks": ["arduino", "espidf"],
+  "name": "Heltec Vision Master T190",
+  "upload": {
+    "flash_size": "16MB",
+    "maximum_ram_size": 8388608,
+    "maximum_size": 16777216,
+    "use_1200bps_touch": true,
+    "wait_for_upload_port": true,
+    "require_upload_port": true,
+    "speed": 921600
+  },
+  "url": "https://heltec.org/project/vision-master-t190/",
+  "vendor": "Heltec"
+}

+ 64 - 0
build_as_lib.py

@@ -0,0 +1,64 @@
+from os.path import realpath
+
+Import("env") # type: ignore
+menv=env # type: ignore
+
+src_filter = [
+  '+<*.cpp>',
+  '+<helpers/*.cpp>',
+  '+<helpers/sensors>',
+  '+<helpers/radiolib/*.cpp>',
+  '+<helpers/ui/MomentaryButton.cpp>',
+  '+<helpers/ui/buzzer.cpp>',
+]
+
+# add build and include dirs according to CPPDEFINES
+for item in menv.get("CPPDEFINES", []):
+ 
+    # PLATFORM HANDLING
+    if item == "STM32_PLATFORM":
+        src_filter.append("+<helpers/stm32/*>")
+    elif item == "ESP32":
+        src_filter.append("+<helpers/esp32/*>")
+    elif item == "NRF52_PLATFORM":
+        src_filter.append("+<helpers/nrf52/*>")
+    elif item == "RP2040_PLATFORM":
+        src_filter.append("+<helpers/rp2040/*>")
+    
+    # DISPLAY HANDLING
+    elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS":
+        display_class = item[1]
+        src_filter.append(f"+<helpers/ui/{display_class}.cpp>")
+        if (display_class == "ST7789Display") :
+            src_filter.append(f"+<helpers/ui/OLEDDisplay.cpp>")
+            src_filter.append(f"+<helpers/ui/OLEDDisplayFonts.cpp>")
+
+    # VARIANTS HANDLING
+    elif isinstance(item, tuple) and item[0] == "MC_VARIANT":
+        variant_name = item[1]
+        src_filter.append(f"+<../variants/{variant_name}>")
+    
+    # INCLUDE EXAMPLE CODE IN BUILD (to provide your own support files without touching the tree)
+    elif isinstance(item, tuple) and item[0] == "BUILD_EXAMPLE":
+        example_name = item[1]
+        src_filter.append(f"+<../examples/{example_name}/*.cpp>")
+
+    # EXCLUDE A SOURCE FILE FROM AN EXAMPLE (must be placed after example name or boom)
+    elif isinstance(item, tuple) and item[0] == "EXCLUDE_FROM_EXAMPLE":
+        exclude_name = item[1]
+        if example_name is None:
+            print("***** PLEASE DEFINE EXAMPLE FIRST *****")
+            break
+        src_filter.append(f"-<../examples/{example_name}/{exclude_name}>")
+
+    # DEAL WITH UI VARIANT FOR AN EXAMPLE
+    elif isinstance(item, tuple) and item[0] == "MC_UI_FLAVOR":
+        ui_flavor = item[1]
+        if example_name is None:
+            print("***** PLEASE DEFINE EXAMPLE FIRST *****")
+            break
+        src_filter.append(f"+<../examples/{example_name}/{ui_flavor}/*.cpp>")
+        
+menv.Replace(SRC_FILTER=src_filter)
+
+#print (menv.Dump())

+ 2 - 2
examples/companion_radio/MyMesh.h

@@ -8,11 +8,11 @@
 #define FIRMWARE_VER_CODE 7
 #define FIRMWARE_VER_CODE 7
 
 
 #ifndef FIRMWARE_BUILD_DATE
 #ifndef FIRMWARE_BUILD_DATE
-#define FIRMWARE_BUILD_DATE "24 Jul 2025"
+#define FIRMWARE_BUILD_DATE "1 Sep 2025"
 #endif
 #endif
 
 
 #ifndef FIRMWARE_VERSION
 #ifndef FIRMWARE_VERSION
-#define FIRMWARE_VERSION "v1.7.4"
+#define FIRMWARE_VERSION "v1.8.1"
 #endif
 #endif
 
 
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

+ 28 - 2
examples/companion_radio/ui-new/UITask.cpp

@@ -213,10 +213,10 @@ public:
       display.setColor(DisplayDriver::GREEN);
       display.setColor(DisplayDriver::GREEN);
       display.setTextSize(1);
       display.setTextSize(1);
       if (_shutdown_init) {
       if (_shutdown_init) {
-        display.drawTextCentered(display.width() / 2, 34, "shutting down...");
+        display.drawTextCentered(display.width() / 2, 34, "hibernating...");
       } else {
       } else {
         display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
         display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
-        display.drawTextCentered(display.width() / 2, 64 - 11, "off: " PRESS_LABEL);
+        display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL);
       }
       }
     }
     }
     return 5000;   // next render after 5000 ms
     return 5000;   // next render after 5000 ms
@@ -354,6 +354,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
 #if defined(PIN_USER_BTN)
 #if defined(PIN_USER_BTN)
   user_btn.begin();
   user_btn.begin();
 #endif
 #endif
+#if defined(PIN_USER_BTN_ANA)
+  analog_btn.begin();
+#endif
 
 
   _node_prefs = node_prefs;
   _node_prefs = node_prefs;
   if (_display != NULL) {
   if (_display != NULL) {
@@ -508,6 +511,14 @@ void UITask::loop() {
     c = handleLongPress(KEY_RIGHT);
     c = handleLongPress(KEY_RIGHT);
   }
   }
 #endif
 #endif
+#if defined(PIN_USER_BTN_ANA)
+  ev = analog_btn.check();
+  if (ev == BUTTON_EVENT_CLICK) {
+    c = checkDisplayOn(KEY_SELECT);
+  } else if (ev == BUTTON_EVENT_LONG_PRESS) {
+    c = handleLongPress(KEY_ENTER);
+  }
+#endif
 
 
   if (c != 0 && curr) {
   if (c != 0 && curr) {
     curr->handleInput(c);
     curr->handleInput(c);
@@ -551,7 +562,22 @@ void UITask::loop() {
   if (millis() > next_batt_chck) {
   if (millis() > next_batt_chck) {
     uint16_t milliVolts = getBattMilliVolts();
     uint16_t milliVolts = getBattMilliVolts();
     if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) {
     if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) {
+
+      // show low battery shutdown alert
+      // we should only do this for eink displays, which will persist after power loss
+      #ifdef THINKNODE_M1
+      if (_display != NULL) {
+        _display->startFrame();
+        _display->setTextSize(2);
+        _display->setColor(DisplayDriver::RED);
+        _display->drawTextCentered(_display->width() / 2, 20, "Low Battery.");
+        _display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!");
+        _display->endFrame();
+      }
+      #endif
+
       shutdown();
       shutdown();
+
     }
     }
     next_batt_chck = millis() + 8000;
     next_batt_chck = millis() + 8000;
   }
   }

+ 27 - 2
examples/simple_repeater/main.cpp

@@ -22,11 +22,11 @@
 /* ------------------------------ Config -------------------------------- */
 /* ------------------------------ Config -------------------------------- */
 
 
 #ifndef FIRMWARE_BUILD_DATE
 #ifndef FIRMWARE_BUILD_DATE
-  #define FIRMWARE_BUILD_DATE   "24 Jul 2025"
+  #define FIRMWARE_BUILD_DATE   "1 Sep 2025"
 #endif
 #endif
 
 
 #ifndef FIRMWARE_VERSION
 #ifndef FIRMWARE_VERSION
-  #define FIRMWARE_VERSION   "v1.7.4"
+  #define FIRMWARE_VERSION   "v1.8.1"
 #endif
 #endif
 
 
 #ifndef LORA_FREQ
 #ifndef LORA_FREQ
@@ -811,8 +811,33 @@ public:
     *dp = 0;  // null terminator
     *dp = 0;  // null terminator
   }
   }
 
 
+  void removeNeighbor(const uint8_t* pubkey, int key_len) override {
+#if MAX_NEIGHBOURS
+    for (int i = 0; i < MAX_NEIGHBOURS; i++) {
+      NeighbourInfo* neighbour = &neighbours[i];
+      if(memcmp(neighbour->id.pub_key, pubkey, key_len) == 0){
+        neighbours[i] = NeighbourInfo(); // clear neighbour entry
+      }
+    }
+#endif
+  }
+
   mesh::LocalIdentity& getSelfId() override { return self_id; }
   mesh::LocalIdentity& getSelfId() override { return self_id; }
 
 
+  void saveIdentity(const mesh::LocalIdentity& new_id) override {
+    self_id = new_id;
+#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    IdentityStore store(*_fs, "");
+#elif defined(ESP32)
+    IdentityStore store(*_fs, "/identity");
+#elif defined(RP2040_PLATFORM)
+    IdentityStore store(*_fs, "/identity");
+#else
+    #error "need to define saveIdentity()"
+#endif
+    store.save("_main", self_id);
+  }
+
   void clearStats() override {
   void clearStats() override {
     radio_driver.resetStats();
     radio_driver.resetStats();
     resetStats();
     resetStats();

+ 16 - 2
examples/simple_room_server/main.cpp

@@ -22,11 +22,11 @@
 /* ------------------------------ Config -------------------------------- */
 /* ------------------------------ Config -------------------------------- */
 
 
 #ifndef FIRMWARE_BUILD_DATE
 #ifndef FIRMWARE_BUILD_DATE
-  #define FIRMWARE_BUILD_DATE   "24 Jul 2025"
+  #define FIRMWARE_BUILD_DATE   "1 Sep 2025"
 #endif
 #endif
 
 
 #ifndef FIRMWARE_VERSION
 #ifndef FIRMWARE_VERSION
-  #define FIRMWARE_VERSION   "v1.7.4"
+  #define FIRMWARE_VERSION   "v1.8.1"
 #endif
 #endif
 
 
 #ifndef LORA_FREQ
 #ifndef LORA_FREQ
@@ -865,6 +865,20 @@ public:
 
 
   mesh::LocalIdentity& getSelfId() override { return self_id; }
   mesh::LocalIdentity& getSelfId() override { return self_id; }
 
 
+  void saveIdentity(const mesh::LocalIdentity& new_id) override {
+    self_id = new_id;
+#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    IdentityStore store(*_fs, "");
+#elif defined(ESP32)
+    IdentityStore store(*_fs, "/identity");
+#elif defined(RP2040_PLATFORM)
+    IdentityStore store(*_fs, "/identity");
+#else
+    #error "need to define saveIdentity()"
+#endif
+    store.save("_main", self_id);
+  }
+
   void clearStats() override {
   void clearStats() override {
     radio_driver.resetStats();
     radio_driver.resetStats();
     resetStats();
     resetStats();

+ 86 - 29
examples/simple_sensor/SensorMesh.cpp

@@ -613,6 +613,23 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
   }
   }
 }
 }
 
 
+void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
+  if (dest.out_path_len < 0) {
+    mesh::Packet* ack = createAck(ack_hash);
+    if (ack) sendFlood(ack, TXT_ACK_DELAY);
+  } else {
+    uint32_t d = TXT_ACK_DELAY;
+    if (getExtraAckTransmitCount() > 0) {
+      mesh::Packet* a1 = createMultiAck(ack_hash, 1);
+      if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d);
+      d += 300;
+    }
+
+    mesh::Packet* a2 = createAck(ack_hash);
+    if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d);
+  }
+}
+
 void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
 void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
   int i = matching_peer_indexes[sender_idx];
   int i = matching_peer_indexes[sender_idx];
   if (i < 0 || i >= num_contacts) {
   if (i < 0 || i >= num_contacts) {
@@ -656,38 +673,55 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
     memcpy(&sender_timestamp, data, 4);  // timestamp (by sender's RTC clock - which could be wrong)
     memcpy(&sender_timestamp, data, 4);  // timestamp (by sender's RTC clock - which could be wrong)
     uint flags = (data[4] >> 2);   // message attempt number, and other flags
     uint flags = (data[4] >> 2);   // message attempt number, and other flags
 
 
-    if (!(flags == TXT_TYPE_CLI_DATA)) {
-      MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
-    } else if (sender_timestamp > from.last_timestamp) {  // prevent replay attacks
-      from.last_timestamp = sender_timestamp;
-      from.last_activity = getRTCClock()->getCurrentTime();
-
-      // len can be > original length, but 'text' will be padded with zeroes
-      data[len] = 0; // need to make a C string again, with null terminator
-
-      uint8_t temp[166];
-      char *command = (char *) &data[5];
-      char *reply = (char *) &temp[5];
-      handleCommand(sender_timestamp, command, reply);
-
-      int text_len = strlen(reply);
-      if (text_len > 0) {
-        uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
-        if (timestamp == sender_timestamp) {
-          // WORKAROUND: the two timestamps need to be different, in the CLI view
-          timestamp++;
-        }
-        memcpy(temp, &timestamp, 4);   // mostly an extra blob to help make packet_hash unique
-        temp[4] = (TXT_TYPE_CLI_DATA << 2);
-
-        auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
-        if (reply) {
-          if (from.out_path_len < 0) {
-            sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
+    if (sender_timestamp > from.last_timestamp) {  // prevent replay attacks
+      if (flags == TXT_TYPE_PLAIN) {
+        bool handled = handleIncomingMsg(from, sender_timestamp, &data[5], flags, len - 5);
+        if (handled) { // if msg was handled then send an ack
+          uint32_t ack_hash;    // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
+          mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE);
+
+          if (packet->isRouteFlood()) {
+            // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
+            mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
+                                                    PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
+            if (path) sendFlood(path, TXT_ACK_DELAY);
           } else {
           } else {
-            sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
+            sendAckTo(from, ack_hash);
+          }          
+        }
+      } else if (flags == TXT_TYPE_CLI_DATA) {  
+        from.last_timestamp = sender_timestamp;
+        from.last_activity = getRTCClock()->getCurrentTime();
+
+        // len can be > original length, but 'text' will be padded with zeroes
+        data[len] = 0; // need to make a C string again, with null terminator
+
+        uint8_t temp[166];
+        char *command = (char *) &data[5];
+        char *reply = (char *) &temp[5];
+        handleCommand(sender_timestamp, command, reply);
+
+        int text_len = strlen(reply);
+        if (text_len > 0) {
+          uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
+          if (timestamp == sender_timestamp) {
+            // WORKAROUND: the two timestamps need to be different, in the CLI view
+            timestamp++;
+          }
+          memcpy(temp, &timestamp, 4);   // mostly an extra blob to help make packet_hash unique
+          temp[4] = (TXT_TYPE_CLI_DATA << 2);
+
+          auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
+          if (reply) {
+            if (from.out_path_len < 0) {
+              sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
+            } else {
+              sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
+            }
           }
           }
         }
         }
+      } else {
+        MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
       }
       }
     } else {
     } else {
       MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
       MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
@@ -695,6 +729,15 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
   }
   }
 }
 }
 
 
+bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) {
+  MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from ");
+  #ifdef MESH_DEBUG
+  mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE);
+  Serial.printf(": %s\n", data);
+  #endif
+  return false;
+}
+
 bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
 bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
   int i = matching_peer_indexes[sender_idx];
   int i = matching_peer_indexes[sender_idx];
   if (i < 0 || i >= num_contacts) {
   if (i < 0 || i >= num_contacts) {
@@ -794,6 +837,20 @@ bool SensorMesh::formatFileSystem() {
 #endif
 #endif
 }
 }
 
 
+void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) {
+  self_id = new_id;
+#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+  IdentityStore store(*_fs, "");
+#elif defined(ESP32)
+  IdentityStore store(*_fs, "/identity");
+#elif defined(RP2040_PLATFORM)
+  IdentityStore store(*_fs, "/identity");
+#else
+  #error "need to define saveIdentity()"
+#endif
+  store.save("_main", self_id);
+}
+
 void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
 void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
   set_radio_at = futureMillis(2000);   // give CLI reply some time to be sent back, before applying temp radio params
   set_radio_at = futureMillis(2000);   // give CLI reply some time to be sent back, before applying temp radio params
   pending_freq = freq;
   pending_freq = freq;

+ 5 - 3
examples/simple_sensor/SensorMesh.h

@@ -49,11 +49,11 @@ struct ContactInfo {
 };
 };
 
 
 #ifndef FIRMWARE_BUILD_DATE
 #ifndef FIRMWARE_BUILD_DATE
-  #define FIRMWARE_BUILD_DATE   "24 Jul 2025"
+  #define FIRMWARE_BUILD_DATE   "1 Sep 2025"
 #endif
 #endif
 
 
 #ifndef FIRMWARE_VERSION
 #ifndef FIRMWARE_VERSION
-  #define FIRMWARE_VERSION   "v1.7.4"
+  #define FIRMWARE_VERSION   "v1.8.1"
 #endif
 #endif
 
 
 #define FIRMWARE_ROLE "sensor"
 #define FIRMWARE_ROLE "sensor"
@@ -89,6 +89,7 @@ public:
     strcpy(reply, "not supported");
     strcpy(reply, "not supported");
   }
   }
   mesh::LocalIdentity& getSelfId() override { return self_id; }
   mesh::LocalIdentity& getSelfId() override { return self_id; }
+  void saveIdentity(const mesh::LocalIdentity& new_id) override;
   void clearStats() override { }
   void clearStats() override { }
   void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
   void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
 
 
@@ -140,7 +141,8 @@ protected:
   void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
   void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
   bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
   bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
   void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
   void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
-
+  virtual bool handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
+  void sendAckTo(const ContactInfo& dest, uint32_t ack_hash);
 private:
 private:
   FILESYSTEM* _fs;
   FILESYSTEM* _fs;
   unsigned long next_local_advert, next_flood_advert;
   unsigned long next_local_advert, next_flood_advert;

+ 16 - 0
library.json

@@ -0,0 +1,16 @@
+{
+    "name": "MeshCore",
+    "version" : "1.8.0",
+    "dependencies": {
+        "SPI": "*",
+        "Wire": "*",
+        "jgromes/RadioLib": "^7.1.2",
+        "rweather/Crypto": "^0.4.0",
+        "adafruit/RTClib": "^2.1.3",
+        "melopero/Melopero RV3028": "^1.1.0",
+        "electroniccats/CayenneLPP": "1.4.0"
+    },
+    "build": {
+        "extraScript": "build_as_lib.py"
+    }
+}

+ 2 - 2
platformio.ini

@@ -53,7 +53,7 @@ build_src_filter =
 
 
 [esp32_base]
 [esp32_base]
 extends = arduino_base
 extends = arduino_base
-platform = platformio/espressif32@^6.11.0
+platform = platformio/espressif32@6.11.0
 monitor_filters = esp32_exception_decoder
 monitor_filters = esp32_exception_decoder
 extra_scripts = merge-bin.py
 extra_scripts = merge-bin.py
 build_flags = ${arduino_base.build_flags}
 build_flags = ${arduino_base.build_flags}
@@ -68,7 +68,7 @@ lib_deps =
 ; esp32c6 uses arduino framework 3.x
 ; esp32c6 uses arduino framework 3.x
 [esp32c6_base]
 [esp32c6_base]
 extends = esp32_base
 extends = esp32_base
-platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip
 
 
 ; ----------------- NRF52 ---------------------
 ; ----------------- NRF52 ---------------------
 
 

+ 15 - 2
src/helpers/CommonCLI.cpp

@@ -165,6 +165,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
       }
       }
     } else if (memcmp(command, "neighbors", 9) == 0) {
     } else if (memcmp(command, "neighbors", 9) == 0) {
       _callbacks->formatNeighborsReply(reply);
       _callbacks->formatNeighborsReply(reply);
+    } else if (memcmp(command, "neighbor.remove ", 16) == 0) {
+      const char* hex = &command[16];
+      uint8_t pubkey[PUB_KEY_SIZE];
+      int hex_len = min((int)strlen(hex), PUB_KEY_SIZE*2);
+      int pubkey_len = hex_len / 2;
+      if (mesh::Utils::fromHex(pubkey, pubkey_len, hex)) {
+        _callbacks->removeNeighbor(pubkey, pubkey_len);
+        strcpy(reply, "OK");
+      } else {
+        strcpy(reply, "ERR: bad pubkey");
+      }
     } else if (memcmp(command, "tempradio ", 10) == 0) {
     } else if (memcmp(command, "tempradio ", 10) == 0) {
       strcpy(tmp, &command[10]);
       strcpy(tmp, &command[10]);
       const char *parts[5];
       const char *parts[5];
@@ -257,7 +268,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
       } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
       } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
         _prefs->agc_reset_interval = atoi(&config[19]) / 4;
         _prefs->agc_reset_interval = atoi(&config[19]) / 4;
         savePrefs();
         savePrefs();
-        strcpy(reply, "OK");
+        sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
       } else if (memcmp(config, "multi.acks ", 11) == 0) {
       } else if (memcmp(config, "multi.acks ", 11) == 0) {
         _prefs->multi_acks = atoi(&config[11]);
         _prefs->multi_acks = atoi(&config[11]);
         savePrefs();
         savePrefs();
@@ -294,7 +305,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
         uint8_t prv_key[PRV_KEY_SIZE];
         uint8_t prv_key[PRV_KEY_SIZE];
         bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
         bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
         if (success) {
         if (success) {
-          _callbacks->getSelfId().readFrom(prv_key, PRV_KEY_SIZE);
+          mesh::LocalIdentity new_id;
+          new_id.readFrom(prv_key, PRV_KEY_SIZE);
+          _callbacks->saveIdentity(new_id);
           strcpy(reply, "OK");
           strcpy(reply, "OK");
         } else {
         } else {
           strcpy(reply, "Error, invalid key");
           strcpy(reply, "Error, invalid key");

+ 4 - 0
src/helpers/CommonCLI.h

@@ -43,7 +43,11 @@ public:
   virtual void dumpLogFile() = 0;
   virtual void dumpLogFile() = 0;
   virtual void setTxPower(uint8_t power_dbm) = 0;
   virtual void setTxPower(uint8_t power_dbm) = 0;
   virtual void formatNeighborsReply(char *reply) = 0;
   virtual void formatNeighborsReply(char *reply) = 0;
+  virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
+    // no op by default
+  };
   virtual mesh::LocalIdentity& getSelfId() = 0;
   virtual mesh::LocalIdentity& getSelfId() = 0;
+  virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
   virtual void clearStats() = 0;
   virtual void clearStats() = 0;
   virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
   virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
 };
 };

+ 12 - 1
src/helpers/esp32/SerialWifiInterface.cpp

@@ -44,7 +44,18 @@ bool SerialWifiInterface::isWriteBusy() const {
 }
 }
 
 
 size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
 size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
-  if (!client) client = server.available();
+  // check if new client connected
+  auto newClient = server.available();
+  if (newClient) {
+
+    // disconnect existing client
+    deviceConnected = false;
+    client.stop();
+
+    // switch active connection to new client
+    client = newClient;
+    
+  }
 
 
   if (client.connected()) {
   if (client.connected()) {
     if (!deviceConnected) {
     if (!deviceConnected) {

+ 39 - 0
src/helpers/nrf52/T114Board.cpp

@@ -26,6 +26,45 @@ void T114Board::begin() {
 
 
   pinMode(PIN_VBAT_READ, INPUT);
   pinMode(PIN_VBAT_READ, INPUT);
 
 
+  // Enable SoftDevice low-power mode
+  sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
+
+  // Enable DC/DC converter for better efficiency (REG1 stage)
+  NRF_POWER->DCDCEN = 1;
+
+  // Power down unused communication peripherals
+  // UART1 - Not used on T114
+  NRF_UARTE1->ENABLE = 0;
+
+  // SPIM2/SPIS2 - Not used (SPI is on SPIM0)
+  NRF_SPIM2->ENABLE = 0;
+  NRF_SPIS2->ENABLE = 0;
+
+  // TWI1 (I2C1) - Not used (I2C is on TWI0)
+  NRF_TWIM1->ENABLE = 0;
+  NRF_TWIS1->ENABLE = 0;
+
+  // PWM modules - Not used for standard T114 functions
+  NRF_PWM1->ENABLE = 0;
+  NRF_PWM2->ENABLE = 0;
+  NRF_PWM3->ENABLE = 0;
+
+  // PDM (Digital Microphone Interface) - Not used
+  NRF_PDM->ENABLE = 0;
+
+  // I2S - Not used
+  NRF_I2S->ENABLE = 0;
+
+  // QSPI - Not used (no external flash)
+  NRF_QSPI->ENABLE = 0;
+
+  // Disable unused analog peripherals
+  // SAADC channels - only keep what's needed for battery monitoring
+  NRF_SAADC->ENABLE = 0;  // Re-enable only when needed for measurements
+
+  // COMP - Comparator not used
+  NRF_COMP->ENABLE = 0;
+
 #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
 #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
   Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
   Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
 #endif
 #endif

+ 5 - 0
src/helpers/nrf52/T114Board.h

@@ -40,6 +40,9 @@ public:
 
 
   uint16_t getBattMilliVolts() override {
   uint16_t getBattMilliVolts() override {
     int adcvalue = 0;
     int adcvalue = 0;
+
+    NRF_SAADC->ENABLE = 1;
+
     analogReadResolution(12);
     analogReadResolution(12);
     analogReference(AR_INTERNAL_3_0);
     analogReference(AR_INTERNAL_3_0);
     pinMode(PIN_BAT_CTL, OUTPUT);          // battery adc can be read only ctrl pin 6 set to high
     pinMode(PIN_BAT_CTL, OUTPUT);          // battery adc can be read only ctrl pin 6 set to high
@@ -49,6 +52,8 @@ public:
     adcvalue = analogRead(PIN_VBAT_READ);
     adcvalue = analogRead(PIN_VBAT_READ);
     digitalWrite(6, 0);
     digitalWrite(6, 0);
 
 
+    NRF_SAADC->ENABLE = 0;
+
     return (uint16_t)((float)adcvalue * MV_LSB * 4.9);
     return (uint16_t)((float)adcvalue * MV_LSB * 4.9);
   }
   }
 
 

+ 8 - 0
src/helpers/nrf52/ThinkNodeM1Board.h

@@ -57,6 +57,14 @@ public:
   }
   }
 
 
   void powerOff() override {
   void powerOff() override {
+
+    // turn off all leds, sd_power_system_off will not do this for us
+    #ifdef P_LORA_TX_LED
+    digitalWrite(P_LORA_TX_LED, LOW);
+    #endif
+
+    // power off board
     sd_power_system_off();
     sd_power_system_off();
+
   }
   }
 };
 };

+ 19 - 3
src/helpers/ui/MomentaryButton.cpp

@@ -8,16 +8,29 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse
   prev = _reverse ? HIGH : LOW;
   prev = _reverse ? HIGH : LOW;
   cancel = 0;
   cancel = 0;
   _long_millis = long_press_millis;
   _long_millis = long_press_millis;
+  _threshold = 0;
+}
+
+MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, int analog_threshold) {
+  _pin = pin;
+  _reverse = false;
+  _pull = false;
+  down_at = 0;
+  prev = LOW;
+  cancel = 0;
+  _long_millis = long_press_millis;
+  _threshold = analog_threshold;
 }
 }
 
 
 void MomentaryButton::begin() {
 void MomentaryButton::begin() {
-  if (_pin >= 0) {
+  if (_pin >= 0 && _threshold == 0) {
     pinMode(_pin, _pull ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT);
     pinMode(_pin, _pull ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT);
   }
   }
 }
 }
 
 
 bool  MomentaryButton::isPressed() const {
 bool  MomentaryButton::isPressed() const {
-  return isPressed(digitalRead(_pin));
+  int btn = _threshold > 0 ? (analogRead(_pin) < _threshold) : digitalRead(_pin);
+  return isPressed(btn);
 }
 }
 
 
 void MomentaryButton::cancelClick() {
 void MomentaryButton::cancelClick() {
@@ -25,6 +38,9 @@ void MomentaryButton::cancelClick() {
 }
 }
 
 
 bool MomentaryButton::isPressed(int level) const {
 bool MomentaryButton::isPressed(int level) const {
+  if (_threshold > 0) {
+    return level;
+  }
   if (_reverse) {
   if (_reverse) {
     return level == LOW;
     return level == LOW;
   } else {
   } else {
@@ -36,7 +52,7 @@ int MomentaryButton::check(bool repeat_click) {
   if (_pin < 0) return BUTTON_EVENT_NONE;
   if (_pin < 0) return BUTTON_EVENT_NONE;
 
 
   int event = BUTTON_EVENT_NONE;
   int event = BUTTON_EVENT_NONE;
-  int btn = digitalRead(_pin);
+  int btn = _threshold > 0 ? (analogRead(_pin) < _threshold) : digitalRead(_pin);
   if (btn != prev) {
   if (btn != prev) {
     if (isPressed(btn)) {
     if (isPressed(btn)) {
       down_at = millis();
       down_at = millis();

+ 2 - 0
src/helpers/ui/MomentaryButton.h

@@ -11,12 +11,14 @@ class MomentaryButton {
   int8_t prev, cancel;
   int8_t prev, cancel;
   bool _reverse, _pull;
   bool _reverse, _pull;
   int _long_millis;
   int _long_millis;
+  int _threshold;  // analog mode
   unsigned long down_at;
   unsigned long down_at;
 
 
   bool isPressed(int level) const;
   bool isPressed(int level) const;
 
 
 public:
 public:
   MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false);
   MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false);
+  MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold);
   void begin();
   void begin();
   int check(bool repeat_click=false);  // returns one of BUTTON_EVENT_*
   int check(bool repeat_click=false);  // returns one of BUTTON_EVENT_*
   void cancelClick();  // suppress next BUTTON_EVENT_CLICK (if already in DOWN state)
   void cancelClick();  // suppress next BUTTON_EVENT_CLICK (if already in DOWN state)

+ 12 - 1
src/helpers/ui/ST7789Display.cpp

@@ -18,7 +18,11 @@ bool ST7789Display::begin() {
     pinMode(PIN_TFT_VDD_CTL, OUTPUT);
     pinMode(PIN_TFT_VDD_CTL, OUTPUT);
     pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
     pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
     digitalWrite(PIN_TFT_VDD_CTL, LOW);
     digitalWrite(PIN_TFT_VDD_CTL, LOW);
+  #ifdef PIN_TFT_LEDA_CTL_ACTIVE
+    digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
+  #else
     digitalWrite(PIN_TFT_LEDA_CTL, LOW);
     digitalWrite(PIN_TFT_LEDA_CTL, LOW);
+  #endif
     digitalWrite(PIN_TFT_RST, HIGH);
     digitalWrite(PIN_TFT_RST, HIGH);
 
 
     display.init();
     display.init();
@@ -43,15 +47,22 @@ void ST7789Display::turnOn() {
     delay(20);
     delay(20);
 
 
     // Now turn on the backlight
     // Now turn on the backlight
+  #ifdef PIN_TFT_LEDA_CTL_ACTIVE
+    digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
+  #else
     digitalWrite(PIN_TFT_LEDA_CTL, LOW);
     digitalWrite(PIN_TFT_LEDA_CTL, LOW);
-    
+  #endif    
     _isOn = true;
     _isOn = true;
   }
   }
 }
 }
 
 
 void ST7789Display::turnOff() {
 void ST7789Display::turnOff() {
   digitalWrite(PIN_TFT_VDD_CTL, HIGH);
   digitalWrite(PIN_TFT_VDD_CTL, HIGH);
+#ifdef PIN_TFT_LEDA_CTL_ACTIVE
+  digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
+#else
   digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
   digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
+#endif
   digitalWrite(PIN_TFT_RST, LOW);
   digitalWrite(PIN_TFT_RST, LOW);
   _isOn = false;
   _isOn = false;
 }
 }

+ 5 - 2
src/helpers/ui/ST7789Display.h

@@ -4,7 +4,7 @@
 #include <Wire.h>
 #include <Wire.h>
 #include <SPI.h>
 #include <SPI.h>
 #include <Adafruit_GFX.h>
 #include <Adafruit_GFX.h>
-#include <ST7789Spi.h>
+#include "ST7789Spi.h"
 
 
 class ST7789Display : public DisplayDriver {
 class ST7789Display : public DisplayDriver {
   ST7789Spi display;
   ST7789Spi display;
@@ -14,8 +14,11 @@ class ST7789Display : public DisplayDriver {
 
 
   bool i2c_probe(TwoWire& wire, uint8_t addr);
   bool i2c_probe(TwoWire& wire, uint8_t addr);
 public:
 public:
+#ifdef HELTEC_VISION_MASTER_T190
+  ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;}
+#else
   ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
   ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
-
+#endif
   bool begin();
   bool begin();
 
 
   bool isOn() override { return _isOn; }
   bool isOn() override { return _isOn; }

+ 2 - 2
variants/generic_espnow/platformio.ini

@@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter}
 extends = Generic_ESPNOW
 extends = Generic_ESPNOW
 build_flags =
 build_flags =
   ${Generic_ESPNOW.build_flags}
   ${Generic_ESPNOW.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=1
   -D MAX_GROUP_CHANNELS=1
 build_src_filter = ${Generic_ESPNOW.build_src_filter}
 build_src_filter = ${Generic_ESPNOW.build_src_filter}
   +<../examples/simple_secure_chat/main.cpp>
   +<../examples/simple_secure_chat/main.cpp>
@@ -54,7 +54,7 @@ lib_deps =
 extends = Generic_ESPNOW
 extends = Generic_ESPNOW
 build_flags =
 build_flags =
   ${Generic_ESPNOW.build_flags}
   ${Generic_ESPNOW.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1

+ 2 - 2
variants/heltec_ct62/platformio.ini

@@ -55,7 +55,7 @@ build_flags =
   ${Heltec_ct62.build_flags}
   ${Heltec_ct62.build_flags}
 ;  -D ARDUINO_USB_MODE=1
 ;  -D ARDUINO_USB_MODE=1
 ;  -D ARDUINO_USB_CDC_ON_BOOT=1
 ;  -D ARDUINO_USB_CDC_ON_BOOT=1
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
@@ -73,7 +73,7 @@ build_flags =
   ${Heltec_ct62.build_flags}
   ${Heltec_ct62.build_flags}
 ;  -D ARDUINO_USB_MODE=1
 ;  -D ARDUINO_USB_MODE=1
 ;  -D ARDUINO_USB_CDC_ON_BOOT=1
 ;  -D ARDUINO_USB_CDC_ON_BOOT=1
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456

+ 1 - 1
variants/heltec_tracker/platformio.ini

@@ -43,7 +43,7 @@ build_flags =
   -D ARDUINO_USB_CDC_ON_BOOT=1      ; need for Serial
   -D ARDUINO_USB_CDC_ON_BOOT=1      ; need for Serial
   -D DISPLAY_ROTATION=1
   -D DISPLAY_ROTATION=1
   -D DISPLAY_CLASS=ST7735Display
   -D DISPLAY_CLASS=ST7735Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456 ; HWT will use display for pin
   -D BLE_PIN_CODE=123456 ; HWT will use display for pin
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256

+ 3 - 3
variants/heltec_v2/platformio.ini

@@ -64,7 +64,7 @@ lib_deps =
 extends = Heltec_lora32_v2
 extends = Heltec_lora32_v2
 build_flags =
 build_flags =
   ${Heltec_lora32_v2.build_flags}
   ${Heltec_lora32_v2.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=170
   -D MAX_GROUP_CHANNELS=1
   -D MAX_GROUP_CHANNELS=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -80,7 +80,7 @@ build_flags =
   ${Heltec_lora32_v2.build_flags}
   ${Heltec_lora32_v2.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=170
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
@@ -100,7 +100,7 @@ build_flags =
   ${Heltec_lora32_v2.build_flags}
   ${Heltec_lora32_v2.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=170
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 7 - 5
variants/heltec_v3/platformio.ini

@@ -94,7 +94,7 @@ lib_deps =
 extends = Heltec_lora32_v3
 extends = Heltec_lora32_v3
 build_flags =
 build_flags =
   ${Heltec_lora32_v3.build_flags}
   ${Heltec_lora32_v3.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=1
   -D MAX_GROUP_CHANNELS=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -109,7 +109,7 @@ extends = Heltec_lora32_v3
 build_flags =
 build_flags =
   ${Heltec_lora32_v3.build_flags}
   ${Heltec_lora32_v3.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
@@ -151,7 +151,8 @@ lib_deps =
 extends = Heltec_lora32_v3
 extends = Heltec_lora32_v3
 build_flags =
 build_flags =
   ${Heltec_lora32_v3.build_flags}
   ${Heltec_lora32_v3.build_flags}
-  -D MAX_CONTACTS=100
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
   -D WIFI_DEBUG_LOGGING=1
   -D WIFI_DEBUG_LOGGING=1
@@ -163,7 +164,8 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/MomentaryButton.cpp>
   +<helpers/ui/MomentaryButton.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
-  +<../examples/companion_radio>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
   ${Heltec_lora32_v3.lib_deps}
   ${Heltec_lora32_v3.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
@@ -227,7 +229,7 @@ lib_deps =
 extends = Heltec_lora32_v3
 extends = Heltec_lora32_v3
 build_flags =
 build_flags =
   ${Heltec_lora32_v3.build_flags}
   ${Heltec_lora32_v3.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 5 - 3
variants/heltec_vision_master_e213/platformio.ini

@@ -28,7 +28,7 @@ build_flags =
   -D DISP_MOSI=6
   -D DISP_MOSI=6
   -D Vision_Master_E213
   -D Vision_Master_E213
 build_src_filter = ${esp32_base.build_src_filter}
 build_src_filter = ${esp32_base.build_src_filter}
-  +<../variants/heltec_vision_master_e213>    
+  +<../variants/heltec_vision_master_e213>
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
   https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
   https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
@@ -37,7 +37,8 @@ lib_deps =
 extends = Heltec_Vision_Master_E213_base
 extends = Heltec_Vision_Master_E213_base
 build_flags =
 build_flags =
   ${Heltec_Vision_Master_E213_base.build_flags}
   ${Heltec_Vision_Master_E213_base.build_flags}
-  -D MAX_CONTACTS=100
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=E213Display
   -D DISPLAY_CLASS=E213Display
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
@@ -46,7 +47,8 @@ build_flags =
 build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter}
 build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter}
   +<helpers/ui/E213Display.cpp>
   +<helpers/ui/E213Display.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
-  +<../examples/companion_radio>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
   ${Heltec_Vision_Master_E213_base.lib_deps}
   ${Heltec_Vision_Master_E213_base.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0

+ 1 - 0
variants/heltec_vision_master_e213/target.cpp

@@ -19,6 +19,7 @@ SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 DISPLAY_CLASS display;
 DISPLAY_CLASS display;
+MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
 #endif
 #endif
 
 
 bool radio_init() {
 bool radio_init() {

+ 2 - 0
variants/heltec_vision_master_e213/target.h

@@ -9,6 +9,7 @@
 #include <helpers/SensorManager.h>
 #include <helpers/SensorManager.h>
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 #include <helpers/ui/E213Display.h>
 #include <helpers/ui/E213Display.h>
+#include <helpers/ui/MomentaryButton.h>
 #endif
 #endif
 
 
 extern HeltecE213Board board;
 extern HeltecE213Board board;
@@ -18,6 +19,7 @@ extern SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 extern DISPLAY_CLASS display;
 extern DISPLAY_CLASS display;
+extern MomentaryButton user_btn;
 #endif
 #endif
 
 
 bool radio_init();
 bool radio_init();

+ 5 - 3
variants/heltec_vision_master_e290/platformio.ini

@@ -22,7 +22,7 @@ build_flags =
   -D PIN_BOARD_SCL=38
   -D PIN_BOARD_SCL=38
   -D Vision_Master_E290
   -D Vision_Master_E290
 build_src_filter = ${esp32_base.build_src_filter}
 build_src_filter = ${esp32_base.build_src_filter}
-  +<../variants/heltec_vision_master_e290>    
+  +<../variants/heltec_vision_master_e290>
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
   https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
   https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
@@ -31,7 +31,8 @@ lib_deps =
 extends = Heltec_Vision_Master_E290_base
 extends = Heltec_Vision_Master_E290_base
 build_flags =
 build_flags =
   ${Heltec_Vision_Master_E290_base.build_flags}
   ${Heltec_Vision_Master_E290_base.build_flags}
-  -D MAX_CONTACTS=100
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=E290Display
   -D DISPLAY_CLASS=E290Display
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
@@ -40,7 +41,8 @@ build_flags =
 build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter}
 build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter}
   +<helpers/ui/E290Display.cpp>
   +<helpers/ui/E290Display.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
-  +<../examples/companion_radio>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
   ${Heltec_Vision_Master_E290_base.lib_deps}
   ${Heltec_Vision_Master_E290_base.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0

+ 1 - 0
variants/heltec_vision_master_e290/target.cpp

@@ -19,6 +19,7 @@ SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 DISPLAY_CLASS display;
 DISPLAY_CLASS display;
+MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
 #endif
 #endif
 
 
 bool radio_init() {
 bool radio_init() {

+ 2 - 0
variants/heltec_vision_master_e290/target.h

@@ -9,6 +9,7 @@
 #include <helpers/SensorManager.h>
 #include <helpers/SensorManager.h>
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 #include <helpers/ui/E290Display.h>
 #include <helpers/ui/E290Display.h>
+#include <helpers/ui/MomentaryButton.h>
 #endif
 #endif
 
 
 extern HeltecE290Board board;
 extern HeltecE290Board board;
@@ -18,6 +19,7 @@ extern SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 extern DISPLAY_CLASS display;
 extern DISPLAY_CLASS display;
+extern MomentaryButton user_btn;
 #endif
 #endif
 
 
 bool radio_init();
 bool radio_init();

+ 69 - 0
variants/heltec_vision_master_t190/HeltecT190Board.cpp

@@ -0,0 +1,69 @@
+#include "HeltecT190Board.h"
+
+void HeltecT190Board::begin() {
+    ESP32Board::begin();
+
+    pinMode(PIN_ADC_CTRL, OUTPUT);
+    digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive
+
+    periph_power.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 HeltecT190Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
+    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
+
+    // Make sure the DIO1 and NSS GPIOs are hold 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 HeltecT190Board::powerOff()  {
+    // TODO: re-enable this when there is a definite wake-up source pin:
+    //  enterDeepSleep(0);
+  }
+
+  uint16_t HeltecT190Board::getBattMilliVolts()  {
+    analogReadResolution(10);
+    digitalWrite(PIN_ADC_CTRL, HIGH);
+    delay(10);
+    uint32_t raw = 0;
+    for (int i = 0; i < 8; i++) {
+      raw += analogRead(PIN_VBAT_READ);
+    }
+    raw = raw / 8;
+
+    digitalWrite(PIN_ADC_CTRL, LOW);
+
+    return (5.42 * (3.3 / 1024.0) * raw) * 1000;
+  }
+
+  const char* HeltecT190Board::getManufacturerName() const {
+    return "Heltec T190";
+  }
+

+ 30 - 0
variants/heltec_vision_master_t190/HeltecT190Board.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <Arduino.h>
+#include <helpers/RefCountedDigitalPin.h>
+#include <helpers/ESP32Board.h>
+#include <driver/rtc_io.h>
+
+// LoRa radio module pins for heltec_vision_master_e290 
+#define  P_LORA_DIO_1   14
+#define  P_LORA_NSS      8
+#define  P_LORA_RESET   12
+#define  P_LORA_BUSY    13
+#define  P_LORA_SCLK     9
+#define  P_LORA_MISO    11
+#define  P_LORA_MOSI    10
+
+class HeltecT190Board : public ESP32Board {
+
+public:
+  RefCountedDigitalPin periph_power;
+
+  HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
+
+  void begin();
+  void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
+  void powerOff() override;
+  uint16_t getBattMilliVolts() override;
+  const char* getManufacturerName() const override ;
+
+};

+ 61 - 0
variants/heltec_vision_master_t190/pins_arduino.h

@@ -0,0 +1,61 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include <stdint.h>
+
+static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant
+#define BUILTIN_LED LED_BUILTIN        // Backward compatibility
+#define LED_BUILTIN LED_BUILTIN
+
+static const uint8_t TX = 43;
+static const uint8_t RX = 44;
+
+static const uint8_t SDA = 2;
+static const uint8_t SCL = 1;
+
+static const uint8_t SS = 8;
+static const uint8_t MOSI = 10;
+static const uint8_t MISO = 11;
+static const uint8_t SCK = 9;
+
+static const uint8_t A0 = 1;
+static const uint8_t A1 = 2;
+static const uint8_t A2 = 3;
+static const uint8_t A3 = 4;
+static const uint8_t A4 = 5;
+static const uint8_t A5 = 6;
+static const uint8_t A6 = 7;
+static const uint8_t A7 = 8;
+static const uint8_t A8 = 9;
+static const uint8_t A9 = 10;
+static const uint8_t A10 = 11;
+static const uint8_t A11 = 12;
+static const uint8_t A12 = 13;
+static const uint8_t A13 = 14;
+static const uint8_t A14 = 15;
+static const uint8_t A15 = 16;
+static const uint8_t A16 = 17;
+static const uint8_t A17 = 18;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t RST_LoRa = 12;
+static const uint8_t BUSY_LoRa = 13;
+static const uint8_t DIO1 = 14;
+
+#endif /* Pins_Arduino_h */

+ 87 - 0
variants/heltec_vision_master_t190/platformio.ini

@@ -0,0 +1,87 @@
+[Heltec_Vision_Master_T190_base]
+extends = esp32_base
+board = heltec_vision_master_t190
+build_flags =
+  ${esp32_base.build_flags}
+  -I variants/heltec_vision_master_t190
+  -I src/helpers/ui
+  -D HELTEC_VISION_MASTER_T190
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D LORA_TX_POWER=22
+  -D PIN_USER_BTN=0
+  -D PIN_VEXT_EN=5
+  -D PIN_VEXT_EN_ACTIVE=HIGH
+  -D PIN_VBAT_READ=6
+  -D PIN_ADC_CTRL=46
+  -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 PIN_BOARD_SDA=2
+  -D PIN_BOARD_SCL=1
+  -D PIN_TFT_SCL=38
+  -D PIN_TFT_SDA=48
+  -D PIN_TFT_RST=40
+  -D PIN_TFT_VDD_CTL=7
+  -D PIN_TFT_LEDA_CTL=17
+  -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH
+  -D PIN_TFT_CS=39
+  -D PIN_TFT_DC=47
+  -D ST7789
+  -D DISPLAY_CLASS=ST7789Display
+build_src_filter = ${esp32_base.build_src_filter}
+  +<../variants/heltec_vision_master_t190>
+  +<helpers/*.cpp>
+  +<helpers/ui/ST7789Display.cpp>
+  +<helpers/ui/OLEDDisplay.cpp>
+  +<helpers/ui/OLEDDisplayFonts.cpp>
+lib_deps =
+  ${esp32_base.lib_deps}
+  adafruit/Adafruit GFX Library @ ^1.12.1
+
+[env:Heltec_Vision_Master_T190_radio_ble]
+extends = Heltec_Vision_Master_T190_base
+build_flags =
+  ${Heltec_Vision_Master_T190_base.build_flags}
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456   ; dynamic, random PIN
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter}
+  +<helpers/esp32/*.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${Heltec_Vision_Master_T190_base.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+[env:Heltec_Vision_Master_T190_repeater]
+extends = Heltec_Vision_Master_T190_base
+build_flags =
+  ${Heltec_Vision_Master_T190_base.build_flags}
+  -D ADVERT_NAME='"Heltec T190 Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter}
+  +<../examples/simple_repeater>
+lib_deps =
+  ${Heltec_Vision_Master_T190_base.lib_deps}
+  ${esp32_ota.lib_deps}
+
+[env:Heltec_Vision_Master_T190_room_server]
+extends = Heltec_Vision_Master_T190_base
+build_flags =
+  ${Heltec_Vision_Master_T190_base.build_flags}
+  -D ADVERT_NAME='"Heltec T190 Room"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter}
+  +<../examples/simple_room_server>
+lib_deps =
+  ${Heltec_Vision_Master_T190_base.lib_deps}
+  ${esp32_ota.lib_deps}

+ 54 - 0
variants/heltec_vision_master_t190/target.cpp

@@ -0,0 +1,54 @@
+#include "target.h"
+#include <Arduino.h>
+
+HeltecT190Board board;
+
+#if defined(P_LORA_SCLK)
+  static SPIClass spi(FSPI);
+  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
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+SensorManager sensors;
+
+#ifdef DISPLAY_CLASS
+DISPLAY_CLASS display;
+MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
+#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
+}

+ 29 - 0
variants/heltec_vision_master_t190/target.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/radiolib/RadioLibWrappers.h>
+#include <HeltecT190Board.h>
+#include <helpers/radiolib/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/SensorManager.h>
+#ifdef DISPLAY_CLASS
+#include <helpers/ui/ST7789Display.h>
+#include <helpers/ui/MomentaryButton.h>
+#endif
+
+extern HeltecT190Board board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern SensorManager sensors;
+
+#ifdef DISPLAY_CLASS
+extern DISPLAY_CLASS display;
+extern MomentaryButton user_btn;
+#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();

+ 5 - 3
variants/heltec_wireless_paper/platformio.ini

@@ -28,7 +28,7 @@ build_flags =
   -D ARDUINO_heltec_wifi_lora_32_V3
   -D ARDUINO_heltec_wifi_lora_32_V3
   -D WIRELESS_PAPER
   -D WIRELESS_PAPER
 build_src_filter = ${esp32_base.build_src_filter}
 build_src_filter = ${esp32_base.build_src_filter}
-  +<../variants/heltec_wireless_paper>    
+  +<../variants/heltec_wireless_paper>
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
   https://github.com/todd-herbert/heltec-eink-modules/archive/9207eb6ab2b96f66298e0488740218c17b006af7.zip
   https://github.com/todd-herbert/heltec-eink-modules/archive/9207eb6ab2b96f66298e0488740218c17b006af7.zip
@@ -37,7 +37,8 @@ lib_deps =
 extends = Heltec_Wireless_Paper_base
 extends = Heltec_Wireless_Paper_base
 build_flags =
 build_flags =
   ${Heltec_Wireless_Paper_base.build_flags}
   ${Heltec_Wireless_Paper_base.build_flags}
-  -D MAX_CONTACTS=100
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D DISPLAY_CLASS=E213Display
   -D DISPLAY_CLASS=E213Display
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
   -D BLE_PIN_CODE=123456   ; dynamic, random PIN
@@ -46,7 +47,8 @@ build_flags =
 build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
 build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
   +<helpers/ui/E213Display.cpp>
   +<helpers/ui/E213Display.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
-  +<../examples/companion_radio>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
   ${Heltec_Wireless_Paper_base.lib_deps}
   ${Heltec_Wireless_Paper_base.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0

+ 1 - 0
variants/heltec_wireless_paper/target.cpp

@@ -19,6 +19,7 @@ SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 DISPLAY_CLASS display;
 DISPLAY_CLASS display;
+MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
 #endif
 #endif
 
 
 bool radio_init() {
 bool radio_init() {

+ 2 - 0
variants/heltec_wireless_paper/target.h

@@ -9,6 +9,7 @@
 #include <helpers/SensorManager.h>
 #include <helpers/SensorManager.h>
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 #include <helpers/ui/E213Display.h>
 #include <helpers/ui/E213Display.h>
+#include <helpers/ui/MomentaryButton.h>
 #endif
 #endif
 
 
 extern HeltecV3Board board;
 extern HeltecV3Board board;
@@ -18,6 +19,7 @@ extern SensorManager sensors;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
 extern DISPLAY_CLASS display;
 extern DISPLAY_CLASS display;
+extern MomentaryButton user_btn;
 #endif
 #endif
 
 
 bool radio_init();
 bool radio_init();

+ 2 - 16
variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h

@@ -5,20 +5,6 @@
 
 
 #ifdef XIAO_NRF52
 #ifdef 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
-
 class ikoka_stick_nrf_board : public mesh::MainBoard {
 class ikoka_stick_nrf_board : public mesh::MainBoard {
 protected:
 protected:
   uint8_t startup_reason;
   uint8_t startup_reason;
@@ -29,10 +15,10 @@ public:
 
 
 #if defined(P_LORA_TX_LED)
 #if defined(P_LORA_TX_LED)
   void onBeforeTransmit() override {
   void onBeforeTransmit() override {
-    digitalWrite(P_LORA_TX_LED, HIGH);   // turn TX LED on
+    digitalWrite(P_LORA_TX_LED, LOW);   // turn TX LED on
   }
   }
   void onAfterTransmit() override {
   void onAfterTransmit() override {
-    digitalWrite(P_LORA_TX_LED, LOW);   // turn TX LED off
+    digitalWrite(P_LORA_TX_LED, HIGH);   // turn TX LED off
   }
   }
 #endif
 #endif
 
 

+ 204 - 24
variants/ikoka_stick_nrf/platformio.ini

@@ -19,11 +19,10 @@ lib_deps =
   adafruit/Adafruit INA3221 Library @ ^1.0.1
   adafruit/Adafruit INA3221 Library @ ^1.0.1
   adafruit/Adafruit INA219 @ ^1.2.3
   adafruit/Adafruit INA219 @ ^1.2.3
   adafruit/Adafruit AHTX0 @ ^2.0.5
   adafruit/Adafruit AHTX0 @ ^2.0.5
-  adafruit/Adafruit BME280 Library @ ^2.3.0  
+  adafruit/Adafruit BME280 Library @ ^2.3.0
   adafruit/Adafruit SSD1306 @ ^2.5.13
   adafruit/Adafruit SSD1306 @ ^2.5.13
 
 
-
-[ikoka_stick_nrf]
+[ikoka_stick_nrf_baseboard]
 extends = nrf52840_xiao
 extends = nrf52840_xiao
 ;board_build.ldscript = boards/nrf52840_s140_v7.ld
 ;board_build.ldscript = boards/nrf52840_s140_v7.ld
 build_flags = ${nrf52840_xiao.build_flags}
 build_flags = ${nrf52840_xiao.build_flags}
@@ -34,7 +33,6 @@ build_flags = ${nrf52840_xiao.build_flags}
   -D DISPLAY_ROTATION=2
   -D DISPLAY_ROTATION=2
   -D RADIO_CLASS=CustomSX1262
   -D RADIO_CLASS=CustomSX1262
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D WRAPPER_CLASS=CustomSX1262Wrapper
-  -D LORA_TX_POWER=9
   -D P_LORA_DIO_1=D1
   -D P_LORA_DIO_1=D1
   -D P_LORA_RESET=D2
   -D P_LORA_RESET=D2
   -D P_LORA_BUSY=D3
   -D P_LORA_BUSY=D3
@@ -52,19 +50,59 @@ build_flags = ${nrf52840_xiao.build_flags}
   -D ENV_INCLUDE_BME280=1
   -D ENV_INCLUDE_BME280=1
   -D ENV_INCLUDE_INA3221=1
   -D ENV_INCLUDE_INA3221=1
   -D ENV_INCLUDE_INA219=1
   -D ENV_INCLUDE_INA219=1
+debug_tool = jlink
+upload_protocol = nrfutil
+
+
+;;; abstracted hardware variants
+
+[ikoka_stick_nrf_e22_22dbm]
+extends = ikoka_stick_nrf_baseboard
+; No PA in this model, full 22dBm
+build_flags =
+  ${ikoka_stick_nrf_baseboard.build_flags}
+  -D LORA_TX_POWER=22
 build_src_filter = ${nrf52840_xiao.build_src_filter}
 build_src_filter = ${nrf52840_xiao.build_src_filter}
   +<helpers/*.cpp>
   +<helpers/*.cpp>
   +<helpers/sensors>
   +<helpers/sensors>
   +<helpers/ui/MomentaryButton.cpp>
   +<helpers/ui/MomentaryButton.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<../variants/ikoka_stick_nrf>
   +<../variants/ikoka_stick_nrf>
-debug_tool = jlink
-upload_protocol = nrfutil
 
 
-[env:ikoka_stick_nrf_companion_radio_ble]
-extends = ikoka_stick_nrf
+[ikoka_stick_nrf_e22_30dbm]
+extends = ikoka_stick_nrf_baseboard
+; limit txpower to 20dBm on E22-900M30S. Anything higher will
+; cause distortion in the PA output. 20dBm in -> 30dBm out
 build_flags =
 build_flags =
-  ${ikoka_stick_nrf.build_flags}
+  ${ikoka_stick_nrf_baseboard.build_flags}
+  -D LORA_TX_POWER=20
+build_src_filter = ${nrf52840_xiao.build_src_filter}
+  +<helpers/*.cpp>
+  +<helpers/sensors>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+  +<../variants/ikoka_stick_nrf>
+
+[ikoka_stick_nrf_e22_33dbm]
+extends = ikoka_stick_nrf_baseboard
+; limit txpower to 9dBm on E22-900M33S to avoid hardware damage
+; to the rf amplifier frontend. 9dBm in -> 33dBm out
+build_flags =
+  ${ikoka_stick_nrf_baseboard.build_flags}
+  -D LORA_TX_POWER=9
+build_src_filter = ${nrf52840_xiao.build_src_filter}
+  +<helpers/*.cpp>
+  +<helpers/sensors>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+  +<../variants/ikoka_stick_nrf>
+
+;;; abstracted firmware roles
+
+[ikoka_stick_nrf_companion_radio_ble]
+extends = ikoka_stick_nrf_baseboard
+build_flags =
+  ${ikoka_stick_nrf_baseboard.build_flags}
   -D MAX_CONTACTS=100
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
@@ -73,35 +111,35 @@ build_flags =
 ;  -D BLE_DEBUG_LOGGING=1
 ;  -D BLE_DEBUG_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${ikoka_stick_nrf.build_src_filter}
+build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
   +<helpers/nrf52/SerialBLEInterface.cpp>
   +<helpers/nrf52/SerialBLEInterface.cpp>
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/ui-new/*.cpp>
   +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
-  ${ikoka_stick_nrf.lib_deps}
+  ${ikoka_stick_nrf_baseboard.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
 
 
-[env:ikoka_stick_nrf_companion_radio_usb]
-extends = ikoka_stick_nrf
+[ikoka_stick_nrf_companion_radio_usb]
+extends = ikoka_stick_nrf_baseboard
 build_flags =
 build_flags =
-  ${ikoka_stick_nrf.build_flags}
+  ${ikoka_stick_nrf_baseboard.build_flags}
   -D MAX_CONTACTS=100
   -D MAX_CONTACTS=100
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${ikoka_stick_nrf.build_src_filter}
+build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
   +<helpers/nrf52/SerialBLEInterface.cpp>
   +<helpers/nrf52/SerialBLEInterface.cpp>
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/ui-new/*.cpp>
   +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
-  ${ikoka_stick_nrf.lib_deps}
+  ${ikoka_stick_nrf_baseboard.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
 
 
-[env:ikoka_stick_nrf_repeater]
-extends = ikoka_stick_nrf
+[ikoka_stick_nrf_repeater]
+extends = ikoka_stick_nrf_baseboard
 build_flags =
 build_flags =
-  ${ikoka_stick_nrf.build_flags}
+  ${ikoka_stick_nrf_baseboard.build_flags}
   -D ADVERT_NAME='"Ikoka Stick Repeater"'
   -D ADVERT_NAME='"Ikoka Stick Repeater"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADVERT_LON=0.0
@@ -109,19 +147,161 @@ build_flags =
   -D MAX_NEIGHBOURS=8
   -D MAX_NEIGHBOURS=8
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${ikoka_stick_nrf.build_src_filter}
+build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<../examples/simple_repeater/*.cpp>
   +<../examples/simple_repeater/*.cpp>
 
 
-[env:ikoka_stick_nrf_room_server]
-extends = ikoka_stick_nrf
+[ikoka_stick_nrf_room_server]
+extends = ikoka_stick_nrf_baseboard
 build_flags =
 build_flags =
-  ${ikoka_stick_nrf.build_flags}
+  ${ikoka_stick_nrf_baseboard.build_flags}
   -D ADVERT_NAME='"Ikoka Stick Room"'
   -D ADVERT_NAME='"Ikoka Stick Room"'
   -D ADVERT_LAT=0.0
   -D ADVERT_LAT=0.0
   -D ADVERT_LON=0.0
   -D ADVERT_LON=0.0
   -D ADMIN_PASSWORD='"password"'
   -D ADMIN_PASSWORD='"password"'
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
-build_src_filter = ${ikoka_stick_nrf.build_src_filter}
+build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
   +<../examples/simple_room_server/*.cpp>
   +<../examples/simple_room_server/*.cpp>
+
+;;; hardware + firmware variants
+
+;;;    22dBm EBYTE E22-900M22 variants
+
+[env:ikoka_stick_nrf_22dbm_companion_radio_usb]
+extends =
+   ikoka_stick_nrf_e22_22dbm
+   ikoka_stick_nrf_companion_radio_usb
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_usb.build_flags}
+   ${ikoka_stick_nrf_e22_22dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_usb.build_src_filter}
+   ${ikoka_stick_nrf_e22_22dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_22dbm_companion_radio_ble]
+extends =
+   ikoka_stick_nrf_e22_22dbm
+   ikoka_stick_nrf_companion_radio_ble
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_ble.build_flags}
+   ${ikoka_stick_nrf_e22_22dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_ble.build_src_filter}
+   ${ikoka_stick_nrf_e22_22dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_22dbm_repeater]
+extends =
+   ikoka_stick_nrf_e22_22dbm
+   ikoka_stick_nrf_repeater
+build_flags =
+   ${ikoka_stick_nrf_repeater.build_flags}
+   ${ikoka_stick_nrf_e22_22dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_repeater.build_src_filter}
+   ${ikoka_stick_nrf_e22_22dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_22dbm_room_server]
+extends =
+   ikoka_stick_nrf_e22_22dbm
+   ikoka_stick_nrf_room_server
+build_flags =
+   ${ikoka_stick_nrf_room_server.build_flags}
+   ${ikoka_stick_nrf_e22_22dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_room_server.build_src_filter}
+   ${ikoka_stick_nrf_e22_22dbm.build_src_filter}
+
+
+;;;    30dBm EBYTE E22-900M30 variants
+
+[env:ikoka_stick_nrf_30dbm_companion_radio_usb]
+extends =
+   ikoka_stick_nrf_e22_30dbm
+   ikoka_stick_nrf_companion_radio_usb
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_usb.build_flags}
+   ${ikoka_stick_nrf_e22_30dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_usb.build_src_filter}
+   ${ikoka_stick_nrf_e22_30dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_30dbm_companion_radio_ble]
+extends =
+   ikoka_stick_nrf_e22_30dbm
+   ikoka_stick_nrf_companion_radio_ble
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_ble.build_flags}
+   ${ikoka_stick_nrf_e22_30dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_ble.build_src_filter}
+   ${ikoka_stick_nrf_e22_30dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_30dbm_repeater]
+extends =
+   ikoka_stick_nrf_e22_30dbm
+   ikoka_stick_nrf_repeater
+build_flags =
+   ${ikoka_stick_nrf_repeater.build_flags}
+   ${ikoka_stick_nrf_e22_30dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_repeater.build_src_filter}
+   ${ikoka_stick_nrf_e22_30dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_30dbm_room_server]
+extends =
+   ikoka_stick_nrf_e22_30dbm
+   ikoka_stick_nrf_room_server
+build_flags =
+   ${ikoka_stick_nrf_room_server.build_flags}
+   ${ikoka_stick_nrf_e22_30dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_room_server.build_src_filter}
+   ${ikoka_stick_nrf_e22_30dbm.build_src_filter}
+
+
+;;;    33dBm EBYTE E22-900M33 variants
+
+[env:ikoka_stick_nrf_33dbm_companion_radio_usb]
+extends =
+   ikoka_stick_nrf_e22_33dbm
+   ikoka_stick_nrf_companion_radio_usb
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_usb.build_flags}
+   ${ikoka_stick_nrf_e22_33dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_usb.build_src_filter}
+   ${ikoka_stick_nrf_e22_33dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_33dbm_companion_radio_ble]
+extends =
+   ikoka_stick_nrf_e22_33dbm
+   ikoka_stick_nrf_companion_radio_ble
+build_flags =
+   ${ikoka_stick_nrf_companion_radio_ble.build_flags}
+   ${ikoka_stick_nrf_e22_33dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_companion_radio_ble.build_src_filter}
+   ${ikoka_stick_nrf_e22_33dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_33dbm_repeater]
+extends =
+   ikoka_stick_nrf_e22_33dbm
+   ikoka_stick_nrf_repeater
+build_flags =
+   ${ikoka_stick_nrf_repeater.build_flags}
+   ${ikoka_stick_nrf_e22_33dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_repeater.build_src_filter}
+   ${ikoka_stick_nrf_e22_33dbm.build_src_filter}
+
+[env:ikoka_stick_nrf_33dbm_room_server]
+extends =
+   ikoka_stick_nrf_e22_33dbm
+   ikoka_stick_nrf_room_server
+build_flags =
+   ${ikoka_stick_nrf_room_server.build_flags}
+   ${ikoka_stick_nrf_e22_33dbm.build_flags}
+build_src_filter =
+   ${ikoka_stick_nrf_room_server.build_src_filter}
+   ${ikoka_stick_nrf_e22_33dbm.build_src_filter}

+ 3 - 3
variants/lilygo_t3s3/platformio.ini

@@ -56,7 +56,7 @@ lib_deps =
 extends = LilyGo_T3S3_sx1262
 extends = LilyGo_T3S3_sx1262
 build_flags =
 build_flags =
   ${LilyGo_T3S3_sx1262.build_flags}
   ${LilyGo_T3S3_sx1262.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=1
   -D MAX_GROUP_CHANNELS=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -91,7 +91,7 @@ build_flags =
   ${LilyGo_T3S3_sx1262.build_flags}
   ${LilyGo_T3S3_sx1262.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
@@ -110,7 +110,7 @@ build_flags =
   ${LilyGo_T3S3_sx1262.build_flags}
   ${LilyGo_T3S3_sx1262.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 3 - 3
variants/lilygo_t3s3_sx1276/platformio.ini

@@ -54,7 +54,7 @@ lib_deps =
 extends = LilyGo_T3S3_sx1276
 extends = LilyGo_T3S3_sx1276
 build_flags =
 build_flags =
   ${LilyGo_T3S3_sx1276.build_flags}
   ${LilyGo_T3S3_sx1276.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=1
   -D MAX_GROUP_CHANNELS=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -90,7 +90,7 @@ build_flags =
   ${LilyGo_T3S3_sx1276.build_flags}
   ${LilyGo_T3S3_sx1276.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D MESH_PACKET_LOGGING=1
   -D MESH_PACKET_LOGGING=1
   -D MESH_DEBUG=1
   -D MESH_DEBUG=1
@@ -109,7 +109,7 @@ build_flags =
   ${LilyGo_T3S3_sx1276.build_flags}
   ${LilyGo_T3S3_sx1276.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 2 - 2
variants/lilygo_tbeam_SX1262/platformio.ini

@@ -24,7 +24,7 @@ build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/lilygo_tbeam_SX1262>
   +<../variants/lilygo_tbeam_SX1262>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
-  +<helpers/sensors> 
+  +<helpers/sensors>
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
@@ -38,7 +38,7 @@ board_build.upload.maximum_ram_size=2000000
 build_flags =
 build_flags =
   ${LilyGo_TBeam_SX1262.build_flags}
   ${LilyGo_TBeam_SX1262.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=160
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256

+ 2 - 2
variants/lilygo_tbeam_SX1276/platformio.ini

@@ -22,7 +22,7 @@ build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/lilygo_tbeam_SX1276>
   +<../variants/lilygo_tbeam_SX1276>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
-  +<helpers/sensors> 
+  +<helpers/sensors>
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
@@ -37,7 +37,7 @@ board_build.upload.maximum_ram_size=2000000
 build_flags =
 build_flags =
   ${LilyGo_TBeam_SX1276.build_flags}
   ${LilyGo_TBeam_SX1276.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=160
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
 ;  -D BLE_DEBUG_LOGGING=1
 ;  -D BLE_DEBUG_LOGGING=1

+ 2 - 2
variants/lilygo_tbeam_supreme_SX1262/platformio.ini

@@ -25,7 +25,7 @@ build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/lilygo_tbeam_supreme_SX1262>
   +<../variants/lilygo_tbeam_supreme_SX1262>
   +<helpers/ui/SH1106Display.cpp>
   +<helpers/ui/SH1106Display.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
   +<helpers/esp32/TBeamBoard.cpp>
-  +<helpers/sensors> 
+  +<helpers/sensors>
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
 lib_deps =
 lib_deps =
   ${esp32_base.lib_deps}
   ${esp32_base.lib_deps}
@@ -74,7 +74,7 @@ extends = T_Beam_S3_Supreme_SX1262
 build_flags =
 build_flags =
   ${T_Beam_S3_Supreme_SX1262.build_flags}
   ${T_Beam_S3_Supreme_SX1262.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256

+ 1 - 1
variants/lilygo_tlora_c6/platformio.ini

@@ -67,7 +67,7 @@ lib_deps =
 [env:LilyGo_Tlora_C6_companion_radio_ble]
 [env:LilyGo_Tlora_C6_companion_radio_ble]
 extends = tlora_c6
 extends = tlora_c6
 build_flags = ${tlora_c6.build_flags}
 build_flags = ${tlora_c6.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 23 - 3
variants/lilygo_tlora_v2_1/platformio.ini

@@ -90,7 +90,7 @@ lib_deps =
 extends = LilyGo_TLora_V2_1_1_6
 extends = LilyGo_TLora_V2_1_1_6
 build_flags =
 build_flags =
   ${LilyGo_TLora_V2_1_1_6.build_flags}
   ${LilyGo_TLora_V2_1_1_6.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=160
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -106,7 +106,7 @@ extends = LilyGo_TLora_V2_1_1_6
 build_flags =
 build_flags =
   ${LilyGo_TLora_V2_1_1_6.build_flags}
   ${LilyGo_TLora_V2_1_1_6.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=160
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
@@ -124,7 +124,7 @@ extends = LilyGo_TLora_V2_1_1_6
 build_flags =
 build_flags =
   ${LilyGo_TLora_V2_1_1_6.build_flags}
   ${LilyGo_TLora_V2_1_1_6.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=160
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
@@ -158,3 +158,23 @@ build_flags =
 lib_deps =
 lib_deps =
   ${LilyGo_TLora_V2_1_1_6.lib_deps}
   ${LilyGo_TLora_V2_1_1_6.lib_deps}
   ${esp32_ota.lib_deps}
   ${esp32_ota.lib_deps}
+
+[env:LilyGo_TLora_V2_1_1_6_companion_radio_wifi]
+extends = LilyGo_TLora_V2_1_1_6
+build_flags =
+  ${LilyGo_TLora_V2_1_1_6.build_flags}
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=160
+  -D MAX_GROUP_CHANNELS=8
+  -D WIFI_SSID='"ssid"'
+  -D WIFI_PWD='"password"'
+  -D WIFI_DEBUG_LOGGING=1
+build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
+  +<helpers/esp32/*.cpp>
+  +<helpers/ui/SSD1306Display.cpp>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${LilyGo_TLora_V2_1_1_6.lib_deps}
+  densaugeo/base64 @ ~1.4.0

+ 12 - 0
variants/meshadventurer/platformio.ini

@@ -80,8 +80,11 @@ extends = Meshadventurer
 build_src_filter = ${Meshadventurer.build_src_filter}
 build_src_filter = ${Meshadventurer.build_src_filter}
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 build_flags =
 build_flags =
   ${Meshadventurer.build_flags}
   ${Meshadventurer.build_flags}
+  -I examples/companion_radio/ui-new
   -D RADIO_CLASS=CustomSX1262
   -D RADIO_CLASS=CustomSX1262
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22
@@ -99,8 +102,11 @@ build_src_filter = ${Meshadventurer.build_src_filter}
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 build_flags =
 build_flags =
   ${Meshadventurer.build_flags}
   ${Meshadventurer.build_flags}
+  -I examples/companion_radio/ui-new
   -D RADIO_CLASS=CustomSX1262
   -D RADIO_CLASS=CustomSX1262
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22
@@ -159,8 +165,11 @@ extends = Meshadventurer
 build_src_filter = ${Meshadventurer.build_src_filter}
 build_src_filter = ${Meshadventurer.build_src_filter}
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 build_flags =
 build_flags =
   ${Meshadventurer.build_flags}
   ${Meshadventurer.build_flags}
+  -I examples/companion_radio/ui-new
   -D RADIO_CLASS=CustomSX1268
   -D RADIO_CLASS=CustomSX1268
   -D WRAPPER_CLASS=CustomSX1268Wrapper
   -D WRAPPER_CLASS=CustomSX1268Wrapper
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22
@@ -178,8 +187,11 @@ build_src_filter = ${Meshadventurer.build_src_filter}
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/ui/SSD1306Display.cpp>
   +<helpers/ui/SSD1306Display.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 build_flags =
 build_flags =
   ${Meshadventurer.build_flags}
   ${Meshadventurer.build_flags}
+  -I examples/companion_radio/ui-new
   -D RADIO_CLASS=CustomSX1268
   -D RADIO_CLASS=CustomSX1268
   -D WRAPPER_CLASS=CustomSX1268Wrapper
   -D WRAPPER_CLASS=CustomSX1268Wrapper
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22

+ 0 - 4
variants/meshadventurer/target.cpp

@@ -18,10 +18,6 @@ MASensorManager sensors = MASensorManager(nmea);
   DISPLAY_CLASS display;
   DISPLAY_CLASS display;
 #endif
 #endif
 
 
-#ifndef LORA_CR
-  #define LORA_CR      5
-#endif
-
 bool radio_init() {
 bool radio_init() {
   fallback_clock.begin();
   fallback_clock.begin();
   rtc_clock.begin(Wire);
   rtc_clock.begin(Wire);

+ 35 - 31
variants/nano_g2_ultra/nano-g2.h

@@ -1,39 +1,40 @@
 #pragma once
 #pragma once
 
 
-#include <MeshCore.h>
+#include "variant.h"
+
 #include <Arduino.h>
 #include <Arduino.h>
+#include <MeshCore.h>
 
 
 // LoRa radio module pins
 // LoRa radio module pins
-#define P_LORA_DIO_1 (32 + 10)
-#define P_LORA_NSS (32 + 13)
-#define P_LORA_RESET (32 + 15)
-#define P_LORA_BUSY (32 + 11)
-#define P_LORA_SCLK (0 + 12)
-#define P_LORA_MISO (32 + 9)
-#define P_LORA_MOSI (0 + 11)
+#define P_LORA_DIO_1             (32 + 10)
+#define P_LORA_NSS               (32 + 13)
+#define P_LORA_RESET             (32 + 15)
+#define P_LORA_BUSY              (32 + 11)
+#define P_LORA_SCLK              (0 + 12)
+#define P_LORA_MISO              (32 + 9)
+#define P_LORA_MOSI              (0 + 11)
 
 
 #define SX126X_DIO2_AS_RF_SWITCH true
 #define SX126X_DIO2_AS_RF_SWITCH true
 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
-#define SX126X_POWER_EN 37
+#define SX126X_POWER_EN          37
 
 
 // buttons
 // buttons
-#define PIN_BUTTON1 (32 + 6)
-#define BUTTON_PIN PIN_BUTTON1
-#define PIN_USER_BTN BUTTON_PIN
+#define PIN_BUTTON1              (32 + 6)
+#define BUTTON_PIN               PIN_BUTTON1
+#define PIN_USER_BTN             BUTTON_PIN
 
 
 // GPS
 // GPS
-#define GPS_EN PIN_GPS_STANDBY
+#define GPS_EN                   PIN_GPS_STANDBY
 // built-ins
 // built-ins
-#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
+#define VBAT_MV_PER_LSB          (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
 
 
-#define VBAT_DIVIDER (0.5F)      // 150K + 150K voltage divider on VBAT, actually 100K + 100K
-#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
+#define VBAT_DIVIDER             (0.5F) // 150K + 150K voltage divider on VBAT, actually 100K + 100K
+#define VBAT_DIVIDER_COMP        (2.0F) // Compensation factor for the VBAT divider
 
 
-#define PIN_VBAT_READ (0 + 2)
-#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
+#define PIN_VBAT_READ            (0 + 2)
+#define REAL_VBAT_MV_PER_LSB     (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
 
 
-class NanoG2Ultra : public mesh::MainBoard
-{
+class NanoG2Ultra : public mesh::MainBoard {
 protected:
 protected:
   uint8_t startup_reason;
   uint8_t startup_reason;
 
 
@@ -42,18 +43,21 @@ public:
   uint16_t getBattMilliVolts() override;
   uint16_t getBattMilliVolts() override;
   bool startOTAUpdate(const char *id, char reply[]) override;
   bool startOTAUpdate(const char *id, char reply[]) override;
 
 
-  uint8_t getStartupReason() const override
-  {
-    return startup_reason;
-  }
+  uint8_t getStartupReason() const override { return startup_reason; }
 
 
-  const char *getManufacturerName() const override
-  {
-    return "Nano G2 Ultra";
-  }
+  const char *getManufacturerName() const override { return "Nano G2 Ultra"; }
+
+  void reboot() override { NVIC_SystemReset(); }
+
+  void powerOff() override {
+    // put GPS chip to sleep
+    digitalWrite(PIN_GPS_STANDBY, LOW);
+// unset buzzer to prevent notification circuit activating on hibernate
+#undef PIN_BUZZER
+
+    nrf_gpio_cfg_sense_input(digitalPinToInterrupt(PIN_USER_BTN), NRF_GPIO_PIN_NOPULL,
+                             NRF_GPIO_PIN_SENSE_LOW);
 
 
-  void reboot() override
-  {
-    NVIC_SystemReset();
+    sd_power_system_off();
   }
   }
 };
 };

+ 5 - 1
variants/rak4631/target.cpp

@@ -10,7 +10,11 @@ RAK4631Board board;
 
 
 #ifdef DISPLAY_CLASS
 #ifdef DISPLAY_CLASS
   DISPLAY_CLASS display;
   DISPLAY_CLASS display;
-  MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
+  MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
+
+  #if defined(PIN_USER_BTN_ANA)
+  MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, 20);
+  #endif
 #endif
 #endif
 
 
 RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
 RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);

+ 3 - 0
variants/rak4631/target.h

@@ -13,6 +13,9 @@
   extern DISPLAY_CLASS display;
   extern DISPLAY_CLASS display;
   #include <helpers/ui/MomentaryButton.h>
   #include <helpers/ui/MomentaryButton.h>
   extern MomentaryButton user_btn;
   extern MomentaryButton user_btn;
+  #if defined(PIN_USER_BTN_ANA)
+  extern MomentaryButton analog_btn;
+  #endif
 #endif
 #endif
 
 
 extern RAK4631Board board;
 extern RAK4631Board board;

+ 21 - 2
variants/station_g2/platformio.ini

@@ -45,6 +45,25 @@ lib_deps =
   ${Station_G2.lib_deps}
   ${Station_G2.lib_deps}
   ${esp32_ota.lib_deps}
   ${esp32_ota.lib_deps}
 
 
+[env:Station_G2_logging_repeater]
+extends = Station_G2
+build_flags =
+  ${Station_G2.build_flags}
+  -D ADVERT_NAME='"Station G2 Logging 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 SX126X_RX_BOOSTED_GAIN=1
+;  https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance
+;  -D MESH_DEBUG=1
+build_src_filter = ${Station_G2.build_src_filter}
+  +<../examples/simple_repeater>
+lib_deps =
+  ${Station_G2.lib_deps}
+  ${esp32_ota.lib_deps}
+
 [env:Station_G2_room_server]
 [env:Station_G2_room_server]
 extends = Station_G2
 extends = Station_G2
 build_src_filter = ${Station_G2.build_src_filter}
 build_src_filter = ${Station_G2.build_src_filter}
@@ -68,7 +87,7 @@ build_flags =
   ${Station_G2.build_flags}
   ${Station_G2.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SH1106Display
   -D DISPLAY_CLASS=SH1106Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_PACKET_LOGGING=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
 ; NOTE: DO NOT ENABLE -->  -D MESH_DEBUG=1
@@ -86,7 +105,7 @@ build_flags =
   ${Station_G2.build_flags}
   ${Station_G2.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
   -D DISPLAY_CLASS=SH1106Display
   -D DISPLAY_CLASS=SH1106Display
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1

+ 60 - 1
variants/t1000-e/platformio.ini

@@ -34,6 +34,65 @@ build_src_filter = ${nrf52840_t1000e.build_src_filter}
 debug_tool = jlink
 debug_tool = jlink
 upload_protocol = nrfutil
 upload_protocol = nrfutil
 
 
+[env:t1000e_repeater]
+extends = t1000-e
+build_flags = ${t1000-e.build_flags}
+  -I examples/companion_radio/ui-orig
+  -D ADVERT_NAME='"t1000-e 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
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+build_src_filter = ${t1000-e.build_src_filter}
+  +<../examples/simple_repeater>
+lib_deps = ${t1000-e.lib_deps}
+  stevemarple/MicroNMEA @ ^2.0.6
+
+[env:t1000e_room_server]
+extends = t1000-e
+build_flags = ${t1000-e.build_flags}
+  -I examples/companion_radio/ui-orig
+  -D ADVERT_NAME='"t1000-e 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
+  -D RX_BOOSTED_GAIN=true
+  -D RF_SWITCH_TABLE
+build_src_filter = ${t1000-e.build_src_filter}
+  +<../examples/simple_room_server>
+lib_deps = ${t1000-e.lib_deps}
+  stevemarple/MicroNMEA @ ^2.0.6
+
+[env:t1000e_companion_radio_usb]
+extends = t1000-e
+build_flags = ${t1000-e.build_flags}
+  -I examples/companion_radio/ui-orig
+  -D MAX_CONTACTS=100
+  -D MAX_GROUP_CHANNELS=8
+;  -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      ; P1/5 - required for T1000-E
+build_src_filter = ${t1000-e.build_src_filter}
+  +<helpers/ui/buzzer.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-orig/*.cpp>
+lib_deps = ${t1000-e.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+  stevemarple/MicroNMEA @ ^2.0.6
+  end2endzone/NonBlockingRTTTL@^1.3.0
+
 [env:t1000e_companion_radio_ble]
 [env:t1000e_companion_radio_ble]
 extends = t1000-e
 extends = t1000-e
 build_flags = ${t1000-e.build_flags}
 build_flags = ${t1000-e.build_flags}
@@ -58,4 +117,4 @@ build_src_filter = ${t1000-e.build_src_filter}
 lib_deps = ${t1000-e.lib_deps}
 lib_deps = ${t1000-e.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
   stevemarple/MicroNMEA @ ^2.0.6
   stevemarple/MicroNMEA @ ^2.0.6
-  end2endzone/NonBlockingRTTTL@^1.3.0
+  end2endzone/NonBlockingRTTTL@^1.3.0

+ 1 - 0
variants/thinknode_m1/platformio.ini

@@ -77,6 +77,7 @@ build_flags =
   -D DISPLAY_CLASS=GxEPDDisplay
   -D DISPLAY_CLASS=GxEPDDisplay
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
   -D PIN_BUZZER=6
   -D PIN_BUZZER=6
+  -D AUTO_SHUTDOWN_MILLIVOLTS=3300
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
 build_src_filter = ${ThinkNode_M1.build_src_filter}
 build_src_filter = ${ThinkNode_M1.build_src_filter}

+ 2 - 67
variants/xiao_c3/platformio.ini

@@ -19,28 +19,6 @@ build_flags =
 build_src_filter = ${esp32_base.build_src_filter}
 build_src_filter = ${esp32_base.build_src_filter}
   +<../variants/xiao_c3>
   +<../variants/xiao_c3>
 
 
-[Xiao_esp32_C3_custom]
-extends = esp32_base
-board = seeed_xiao_esp32c3
-build_flags =
-  ${esp32_base.build_flags}
-  -I variants/xiao_c3
-  -D ESP32_CPU_FREQ=80
-  -D LORA_TX_BOOST_PIN=D3
-  -D P_LORA_TX_LED=D5
-  -D PIN_VBAT_READ=D0
-  -D P_LORA_DIO_1=D2
-  -D P_LORA_NSS=D4
-  -D P_LORA_RESET=RADIOLIB_NC
-  -D P_LORA_BUSY=D1
-  -D PIN_BOARD_SDA=D6
-  -D PIN_BOARD_SCL=D7
-  -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>
-
 [env:Xiao_C3_Repeater_sx1262]
 [env:Xiao_C3_Repeater_sx1262]
 extends = Xiao_esp32_C3
 extends = Xiao_esp32_C3
 build_src_filter = ${Xiao_esp32_C3.build_src_filter}
 build_src_filter = ${Xiao_esp32_C3.build_src_filter}
@@ -74,7 +52,7 @@ build_flags =
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D SX126X_RX_BOOSTED_GAIN=1
   -D SX126X_RX_BOOSTED_GAIN=1
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
@@ -97,7 +75,7 @@ build_flags =
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D WRAPPER_CLASS=CustomSX1262Wrapper
   -D SX126X_RX_BOOSTED_GAIN=1
   -D SX126X_RX_BOOSTED_GAIN=1
   -D LORA_TX_POWER=22
   -D LORA_TX_POWER=22
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D OFFLINE_QUEUE_SIZE=256
   -D OFFLINE_QUEUE_SIZE=256
   ; -D BLE_DEBUG_LOGGING=1
   ; -D BLE_DEBUG_LOGGING=1
@@ -107,46 +85,3 @@ lib_deps =
   ${Xiao_esp32_C3.lib_deps}
   ${Xiao_esp32_C3.lib_deps}
   ${esp32_ota.lib_deps}
   ${esp32_ota.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
-
-[env:Xiao_C3_Repeater_sx1262_custom]
-extends = Xiao_esp32_C3_custom
-build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter}
-  +<../examples/simple_repeater/main.cpp>
-build_flags =
-  ${Xiao_esp32_C3_custom.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_custom.lib_deps}
-  ${esp32_ota.lib_deps}
-  bakercp/CRC32 @ ^2.0.0
-
-[env:Xiao_C3_Repeater_sx1268_custom]
-extends = Xiao_esp32_C3_custom
-build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter}
-  +<../examples/simple_repeater/main.cpp>
-build_flags =
-  ${Xiao_esp32_C3_custom.build_flags}
-  -D RADIO_CLASS=CustomSX1268
-  -D WRAPPER_CLASS=CustomSX1268Wrapper
-  -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_custom.lib_deps}
-  ${esp32_ota.lib_deps}
-  bakercp/CRC32 @ ^2.0.0

+ 64 - 2
variants/xiao_c6/platformio.ini

@@ -50,7 +50,7 @@ lib_deps =
 [env:Xiao_C6_companion_radio_ble]
 [env:Xiao_C6_companion_radio_ble]
 extends = Xiao_C6
 extends = Xiao_C6
 build_flags = ${Xiao_C6.build_flags}
 build_flags = ${Xiao_C6.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1
@@ -107,7 +107,7 @@ lib_deps =
 [env:Meshimi_companion_radio_ble]
 [env:Meshimi_companion_radio_ble]
 extends = Meshimi
 extends = Meshimi
 build_flags = ${Meshimi.build_flags}
 build_flags = ${Meshimi.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D BLE_DEBUG_LOGGING=1
   -D BLE_DEBUG_LOGGING=1
@@ -121,3 +121,65 @@ build_src_filter = ${Meshimi.build_src_filter}
 lib_deps =
 lib_deps =
   ${Meshimi.lib_deps}
   ${Meshimi.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
+
+; WHY2025 badge variant
+; requires soldering 2 pins between the esp32-C6 and the lora chip as shown here: https://wiki.why2025.org/Project:Meshtastic_on_the_WHY2025_badge
+; also requires wiping the esp32-P4
+[WHY2025_badge]
+extends = Xiao_C6
+board_build.partitions = default_8MB.csv
+board_upload.flash_size = 8MB
+board_upload.maximum_size = 8388608
+build_flags =
+  ${Xiao_C6.build_flags}
+  -D P_LORA_SCLK=6
+  -D P_LORA_MISO=2
+  -D P_LORA_MOSI=7
+  -D P_LORA_NSS=4
+  -D P_LORA_DIO_1=5
+  -D P_LORA_BUSY=11
+  -D P_LORA_RESET=1
+  -D SX126X_TXEN=3
+  -UPIN_BOARD_SDA
+  -UPIN_BOARD_SCL
+  -UP_LORA_TX_LED
+  -USX126X_RXEN
+  -USX126X_DIO2_AS_RF_SWITCH
+  -USX126X_DIO3_TCXO_VOLTAGE
+
+[env:WHY2025_badge_Repeater]
+extends = WHY2025_badge
+build_src_filter = ${WHY2025_badge.build_src_filter}
+  +<../examples/simple_repeater/main.cpp>
+build_flags =
+  ${WHY2025_badge.build_flags}
+  -D ADVERT_NAME='"WHY2025 Badge 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 =
+  ${WHY2025_badge.lib_deps}
+;  ${esp32_ota.lib_deps}
+
+[env:WHY2025_badge_companion_radio_ble]
+extends = WHY2025_badge
+build_flags = ${WHY2025_badge.build_flags}
+  -D MAX_CONTACTS=300
+  -D MAX_GROUP_CHANNELS=8
+  -D BLE_PIN_CODE=123456
+  -D BLE_DEBUG_LOGGING=1
+  -D OFFLINE_QUEUE_SIZE=256
+  -D ENABLE_PRIVATE_KEY_IMPORT=1
+  -D ENABLE_PRIVATE_KEY_EXPORT=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${WHY2025_badge.build_src_filter}
+  +<helpers/esp32/*.cpp>
+  -<helpers/esp32/ESPNOWRadio.cpp>
+  +<../examples/companion_radio/*.cpp>
+lib_deps =
+  ${WHY2025_badge.lib_deps}
+  densaugeo/base64 @ ~1.4.0

+ 9 - 3
variants/xiao_s3_wio/platformio.ini

@@ -65,7 +65,7 @@ lib_deps =
 extends = Xiao_S3_WIO
 extends = Xiao_S3_WIO
 build_flags =
 build_flags =
   ${Xiao_S3_WIO.build_flags}
   ${Xiao_S3_WIO.build_flags}
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
@@ -80,7 +80,7 @@ extends = Xiao_S3_WIO
 build_flags =
 build_flags =
   ${Xiao_S3_WIO.build_flags}
   ${Xiao_S3_WIO.build_flags}
   -I examples/companion_radio/ui-new
   -I examples/companion_radio/ui-new
-  -D MAX_CONTACTS=100
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
   -D BLE_PIN_CODE=123456
   -D BLE_PIN_CODE=123456
   -D DISPLAY_CLASS=SSD1306Display
   -D DISPLAY_CLASS=SSD1306Display
@@ -103,15 +103,21 @@ lib_deps =
 extends = Xiao_S3_WIO
 extends = Xiao_S3_WIO
 build_flags =
 build_flags =
   ${Xiao_S3_WIO.build_flags}
   ${Xiao_S3_WIO.build_flags}
-  -D MAX_CONTACTS=100
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=300
   -D MAX_GROUP_CHANNELS=8
   -D MAX_GROUP_CHANNELS=8
+  -D DISPLAY_CLASS=SSD1306Display
   -D SERIAL_TX=D6
   -D SERIAL_TX=D6
   -D SERIAL_RX=D7
   -D SERIAL_RX=D7
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_PACKET_LOGGING=1
 ;  -D MESH_DEBUG=1
 ;  -D MESH_DEBUG=1
 build_src_filter = ${Xiao_S3_WIO.build_src_filter}
 build_src_filter = ${Xiao_S3_WIO.build_src_filter}
+  +<helpers/ui/SSD1306Display.cpp>
   +<helpers/esp32/*.cpp>
   +<helpers/esp32/*.cpp>
+  +<helpers/ui/MomentaryButton.cpp>
   +<../examples/companion_radio/*.cpp>
   +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
 lib_deps =
 lib_deps =
   ${Xiao_S3_WIO.lib_deps}
   ${Xiao_S3_WIO.lib_deps}
   densaugeo/base64 @ ~1.4.0
   densaugeo/base64 @ ~1.4.0
+  adafruit/Adafruit SSD1306 @ ^2.5.13