From 0d74735fe6d6af1f82c025ddb4f47177b80eb0d8 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Mon, 11 May 2026 23:27:01 -0700 Subject: [PATCH 1/9] Created Dragino Pico Board Def Currently just LoRa over usb --- variants/pico_dragino_sx1276/WaveshareBoard.h | 61 +++++++++++++++++++ variants/pico_dragino_sx1276/platformio.ini | 58 ++++++++++++++++++ variants/pico_dragino_sx1276/target.cpp | 56 +++++++++++++++++ variants/pico_dragino_sx1276/target.h | 20 ++++++ 4 files changed, 195 insertions(+) create mode 100644 variants/pico_dragino_sx1276/WaveshareBoard.h create mode 100644 variants/pico_dragino_sx1276/platformio.ini create mode 100644 variants/pico_dragino_sx1276/target.cpp create mode 100644 variants/pico_dragino_sx1276/target.h diff --git a/variants/pico_dragino_sx1276/WaveshareBoard.h b/variants/pico_dragino_sx1276/WaveshareBoard.h new file mode 100644 index 0000000000..694b8bd122 --- /dev/null +++ b/variants/pico_dragino_sx1276/WaveshareBoard.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +// LoRa radio module pins for Waveshare RP2040-LoRa-HF/LF +// https://files.waveshare.com/wiki/RP2040-LoRa/Rp2040-lora-sch.pdf + +/* + * This board has no built-in way to read battery voltage. + * Nevertheless it's very easy to make it work, you only require two 1% resistors. + * + * BAT+ -----+ + * | + * VSYS --+ -/\/\/\/\- --+ + * 200k | + * +-- GPIO28 + * | + * GND --+ -/\/\/\/\- --+ + * | 100k + * BAT- -----+ + */ +#define PIN_VBAT_READ 28 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (3.0f * 3.3f * 1000) + +class WaveshareBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); } + void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); } +#endif + + uint16_t getBattMilliVolts() override { +#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw) / 4096; +#else + return 0; +#endif + } + + const char *getManufacturerName() const override { return "Waveshare RP2040-LoRa"; } + + void reboot() override { rp2040.reboot(); } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; diff --git a/variants/pico_dragino_sx1276/platformio.ini b/variants/pico_dragino_sx1276/platformio.ini new file mode 100644 index 0000000000..e9f8d861a6 --- /dev/null +++ b/variants/pico_dragino_sx1276/platformio.ini @@ -0,0 +1,58 @@ +[pico_dragino_sx1276] +extends = rp2040_base +board = pico +build_flags = + ${rp2040_base.build_flags} + -I variants/pico_dragino_sx1276 + -I variants/waveshare_rp2040_lora + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -D SX127X_CURRENT_LIMIT=120 + -D LORA_TX_POWER=17 + -D LORA_FREQ=915.0 + -D P_LORA_DIO_0=6 + -D P_LORA_DIO_1=7 + -D P_LORA_RST=8 + -D P_LORA_SCLK=18 + -D P_LORA_MISO=16 + -D P_LORA_MOSI=19 + -D P_LORA_NSS=17 + -D PIN_GPS_RX=9 + -D PIN_GPS_TX=8 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${rp2040_base.build_src_filter} + +<../variants/pico_dragino_sx1276> + +<../variants/waveshare_rp2040_lora/WaveshareBoard.cpp> +lib_deps = + ${rp2040_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:pico_dragino_companion_radio_usb] +extends = pico_dragino_sx1276 +build_flags = + ${pico_dragino_sx1276.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${pico_dragino_sx1276.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = + ${pico_dragino_sx1276.lib_deps} + densaugeo/base64 @ ~1.4.0 +lib_ignore = + BLE + WiFi + +[env:pico_dragino_repeater] +extends = pico_dragino_sx1276 +build_flags = + ${pico_dragino_sx1276.build_flags} + -D ADVERT_NAME='"Pico Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 +build_src_filter = ${pico_dragino_sx1276.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${pico_dragino_sx1276.lib_deps} \ No newline at end of file diff --git a/variants/pico_dragino_sx1276/target.cpp b/variants/pico_dragino_sx1276/target.cpp new file mode 100644 index 0000000000..39e636e632 --- /dev/null +++ b/variants/pico_dragino_sx1276/target.cpp @@ -0,0 +1,56 @@ +#include "target.h" + +#include +#include + +WaveshareBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RST, P_LORA_DIO_1, SPI); +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + Serial.begin(115200); + delay(3000); + Serial.println("Starting up..."); + + rtc_clock.begin(Wire); + Serial.println("RTC done"); + + pinMode(P_LORA_RST, OUTPUT); + digitalWrite(P_LORA_RST, LOW); + delay(10); + digitalWrite(P_LORA_RST, HIGH); + delay(10); + Serial.println("Reset done"); + + SPI.begin(); + Serial.println("SPI done"); + + bool result = radio.std_init(NULL); + Serial.println(result ? "Radio OK" : "Radio FAILED"); + return result; +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(int8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} \ No newline at end of file diff --git a/variants/pico_dragino_sx1276/target.h b/variants/pico_dragino_sx1276/target.h new file mode 100644 index 0000000000..18702442e3 --- /dev/null +++ b/variants/pico_dragino_sx1276/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern WaveshareBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file From 6b567a5db1f6b25e9b70870adbf37b2f618e9e9e Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Tue, 12 May 2026 10:01:59 -0700 Subject: [PATCH 2/9] Setup GPS funcitonality --- variants/pico_dragino_sx1276/platformio.ini | 9 +++- variants/pico_dragino_sx1276/target.cpp | 49 ++++++++++++++------- variants/pico_dragino_sx1276/target.h | 5 ++- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/variants/pico_dragino_sx1276/platformio.ini b/variants/pico_dragino_sx1276/platformio.ini index e9f8d861a6..ac55d75ea0 100644 --- a/variants/pico_dragino_sx1276/platformio.ini +++ b/variants/pico_dragino_sx1276/platformio.ini @@ -17,12 +17,17 @@ build_flags = -D P_LORA_MISO=16 -D P_LORA_MOSI=19 -D P_LORA_NSS=17 - -D PIN_GPS_RX=9 - -D PIN_GPS_TX=8 -D ENV_INCLUDE_GPS=1 + -D PIN_GPS_RX=13 + -D PIN_GPS_TX=12 + -D setPins=setPinout + -D ENV_SKIP_GPS_DETECT=1 + -D PERSISTANT_GPS=1 build_src_filter = ${rp2040_base.build_src_filter} +<../variants/pico_dragino_sx1276> +<../variants/waveshare_rp2040_lora/WaveshareBoard.cpp> + + + + lib_deps = ${rp2040_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/pico_dragino_sx1276/target.cpp b/variants/pico_dragino_sx1276/target.cpp index 39e636e632..b2b65182f4 100644 --- a/variants/pico_dragino_sx1276/target.cpp +++ b/variants/pico_dragino_sx1276/target.cpp @@ -1,7 +1,8 @@ #include "target.h" - #include #include +#include +#include WaveshareBoard board; @@ -10,28 +11,46 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +MicroNMEALocationProvider nmea(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(nmea); bool radio_init() { - Serial.begin(115200); - delay(3000); - Serial.println("Starting up..."); + pinMode(25, OUTPUT); + + // step 1 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.setRX(PIN_GPS_RX); + + // step 2 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.setTX(PIN_GPS_TX); - rtc_clock.begin(Wire); - Serial.println("RTC done"); + // step 3 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.begin(9600); - pinMode(P_LORA_RST, OUTPUT); - digitalWrite(P_LORA_RST, LOW); - delay(10); - digitalWrite(P_LORA_RST, HIGH); - delay(10); - Serial.println("Reset done"); + // step 4 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + rtc_clock.begin(Wire); + // step 5 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); SPI.begin(); - Serial.println("SPI done"); + // step 6 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); bool result = radio.std_init(NULL); - Serial.println(result ? "Radio OK" : "Radio FAILED"); + + if (result) { + for (int i = 0; i < 10; i++) { + digitalWrite(25,HIGH); delay(100); digitalWrite(25,LOW); delay(100); + } + } else { + for (int i = 0; i < 3; i++) { + digitalWrite(25,HIGH); delay(500); digitalWrite(25,LOW); delay(500); + } + } return result; } diff --git a/variants/pico_dragino_sx1276/target.h b/variants/pico_dragino_sx1276/target.h index 18702442e3..0451759693 100644 --- a/variants/pico_dragino_sx1276/target.h +++ b/variants/pico_dragino_sx1276/target.h @@ -5,13 +5,14 @@ #include #include #include -#include +#include +#include #include extern WaveshareBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From 8e45e10c7a8fe8779ccd34f01c5af1fb05ce7092 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Wed, 13 May 2026 00:10:06 -0700 Subject: [PATCH 3/9] Bluetooth almost there! Letting Chat try --- examples/companion_radio/main.cpp | 36 +++-- src/helpers/rp2040/SerialBLEInterface.cpp | 129 ++++++++++++++++++ src/helpers/rp2040/SerialBLEInterface.h | 58 ++++++++ .../pico_w_dragino_sx1276/WaveshareBoard.h | 61 +++++++++ variants/pico_w_dragino_sx1276/platformio.ini | 49 +++++++ variants/pico_w_dragino_sx1276/target.cpp | 75 ++++++++++ variants/pico_w_dragino_sx1276/target.h | 21 +++ 7 files changed, 410 insertions(+), 19 deletions(-) create mode 100644 src/helpers/rp2040/SerialBLEInterface.cpp create mode 100644 src/helpers/rp2040/SerialBLEInterface.h create mode 100644 variants/pico_w_dragino_sx1276/WaveshareBoard.h create mode 100644 variants/pico_w_dragino_sx1276/platformio.ini create mode 100644 variants/pico_w_dragino_sx1276/target.cpp create mode 100644 variants/pico_w_dragino_sx1276/target.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 876dc9c33c..cac30be82a 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -53,16 +53,16 @@ static uint32_t _atoi(const char* sp) { ArduinoSerialInterface serial_interface; #endif #elif defined(RP2040_PLATFORM) - //#ifdef WIFI_SSID - // #include - // SerialWifiInterface serial_interface; - // #ifndef TCP_PORT - // #define TCP_PORT 5000 - // #endif - // #elif defined(BLE_PIN_CODE) - // #include - // SerialBLEInterface serial_interface; - #if defined(SERIAL_RX) + #ifdef WIFI_SSID + #include + SerialWifiInterface serial_interface; + #ifndef TCP_PORT + #define TCP_PORT 5000 + #endif + #elif defined(BLE_PIN_CODE) + #include + SerialBLEInterface serial_interface; + #elif defined(SERIAL_RX) #include ArduinoSerialInterface serial_interface; HardwareSerial companion_serial(1); @@ -167,21 +167,19 @@ void setup() { #endif ); - //#ifdef WIFI_SSID - // WiFi.begin(WIFI_SSID, WIFI_PWD); - // serial_interface.begin(TCP_PORT); - // #elif defined(BLE_PIN_CODE) - // char dev_name[32+16]; - // sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - // serial_interface.begin(dev_name, the_mesh.getBLEPin()); - #if defined(SERIAL_RX) + #ifdef WIFI_SSID + WiFi.begin(WIFI_SSID, WIFI_PWD); + serial_interface.begin(TCP_PORT); + #elif defined(BLE_PIN_CODE) + serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); + #elif defined(SERIAL_RX) companion_serial.setPins(SERIAL_RX, SERIAL_TX); companion_serial.begin(115200); serial_interface.begin(companion_serial); #else serial_interface.begin(Serial); #endif - the_mesh.startInterface(serial_interface); + the_mesh.startInterface(serial_interface); #elif defined(ESP32) SPIFFS.begin(true); store.begin(); diff --git a/src/helpers/rp2040/SerialBLEInterface.cpp b/src/helpers/rp2040/SerialBLEInterface.cpp new file mode 100644 index 0000000000..169255f649 --- /dev/null +++ b/src/helpers/rp2040/SerialBLEInterface.cpp @@ -0,0 +1,129 @@ +#include "SerialBLEInterface.h" +#include + +#define BLE_HEALTH_CHECK_INTERVAL 10000 + +void SerialBLEInterface::onConnect(BLEServer* p) { + (void)p; + Serial.println("BLE: connected"); + _isConnected = true; + send_queue_len = 0; + recv_queue_len = 0; +} + +void SerialBLEInterface::onDisconnect(BLEServer* p) { + (void)p; + Serial.println("BLE: disconnected"); + _isConnected = false; + send_queue_len = 0; + recv_queue_len = 0; + if (_isEnabled) { + BLE.startAdvertising(); + } +} + +void SerialBLEInterface::shiftSendQueueLeft() { + if (send_queue_len > 0) { + send_queue_len--; + for (uint8_t i = 0; i < send_queue_len; i++) { + send_queue[i] = send_queue[i + 1]; + } + } +} + +void SerialBLEInterface::shiftRecvQueueLeft() { + if (recv_queue_len > 0) { + recv_queue_len--; + for (uint8_t i = 0; i < recv_queue_len; i++) { + recv_queue[i] = recv_queue[i + 1]; + } + } +} + +void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { + (void)pin_code; // TODO: add security/PIN support later + + char dev_name[48]; + snprintf(dev_name, sizeof(dev_name), "%s%s", prefix, name); + + BLE.begin(dev_name); + + _server = BLE.server(); + _server->setCallbacks(this); + _server->addService(&_uart); + _uart.begin(); + _uart.setAutoflush(0); // disable autoflush so we control when data is sent + + BLE.setSecurity(BLESecurityNone); + + enable(); +} + +void SerialBLEInterface::enable() { + if (_isEnabled) return; + _isEnabled = true; + _last_health_check = millis(); + BLE.startAdvertising(); +} + +void SerialBLEInterface::disable() { + _isEnabled = false; + _isConnected = false; + BLE.stopAdvertising(); +} + +size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { + if (!_isConnected || len == 0 || len > MAX_FRAME_SIZE) return 0; + if (send_queue_len >= FRAME_QUEUE_SIZE) { + BLE_DEBUG_PRINTLN("send queue full"); + return 0; + } + send_queue[send_queue_len].len = len; + memcpy(send_queue[send_queue_len].buf, src, len); + send_queue_len++; + return len; +} + +size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { + // drain send queue + if (send_queue_len > 0 && _isConnected) { + Frame& f = send_queue[0]; + size_t written = _uart.write(f.buf, f.len); + _uart.flush(); + Serial.printf("BLE: wrote %d of %d bytes\n", written, f.len); + if (written == f.len) { + shiftSendQueueLeft(); + } + } + + // read incoming + int avail = _uart.available(); + if (avail > 0) { + Serial.printf("BLE: %d bytes available\n", avail); + if (recv_queue_len < FRAME_QUEUE_SIZE) { + if (avail > MAX_FRAME_SIZE) avail = MAX_FRAME_SIZE; + recv_queue[recv_queue_len].len = avail; + _uart.readBytes(recv_queue[recv_queue_len].buf, avail); + recv_queue_len++; + } + } + + if (recv_queue_len > 0) { + size_t len = recv_queue[0].len; + Serial.printf("BLE: returning frame len=%d hdr=0x%02x\n", len, recv_queue[0].buf[0]); + memcpy(dest, recv_queue[0].buf, len); + shiftRecvQueueLeft(); + return len; + } + + // advertising watchdog + if (_isEnabled && !_isConnected) { + unsigned long now = millis(); + if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) { + _last_health_check = now; + BLE.startAdvertising(); + } + } + + return 0; +} \ No newline at end of file diff --git a/src/helpers/rp2040/SerialBLEInterface.h b/src/helpers/rp2040/SerialBLEInterface.h new file mode 100644 index 0000000000..79c1bdbc18 --- /dev/null +++ b/src/helpers/rp2040/SerialBLEInterface.h @@ -0,0 +1,58 @@ +#pragma once + +#include "../BaseSerialInterface.h" +#include +#include +#include + +#ifndef BLE_TX_POWER +#define BLE_TX_POWER 4 +#endif + +#if BLE_DEBUG_LOGGING && ARDUINO + #include + #define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__) +#else + #define BLE_DEBUG_PRINTLN(...) {} +#endif + +class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks { + BLEServer* _server = nullptr; + BLEServiceUART _uart; + bool _isEnabled = false; + bool _isConnected = false; + unsigned long _last_health_check = 0; + + struct Frame { + uint8_t len; + uint8_t buf[MAX_FRAME_SIZE]; + }; + + #define FRAME_QUEUE_SIZE 12 + + uint8_t send_queue_len = 0; + Frame send_queue[FRAME_QUEUE_SIZE]; + + uint8_t recv_queue_len = 0; + Frame recv_queue[FRAME_QUEUE_SIZE]; + + void shiftSendQueueLeft(); + void shiftRecvQueueLeft(); + +public: + SerialBLEInterface() {} + + void begin(const char* prefix, char* name, uint32_t pin_code); + + // BLEServerCallbacks + void onConnect(BLEServer* p) override; + void onDisconnect(BLEServer* p) override; + + void enable() override; + void disable() override; + bool isEnabled() const override { return _isEnabled; } + bool isConnected() const override { return _isConnected; } + bool isWriteBusy() const override { return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3); } + size_t writeFrame(const uint8_t src[], size_t len) override; + size_t checkRecvFrame(uint8_t dest[]) override; +}; \ No newline at end of file diff --git a/variants/pico_w_dragino_sx1276/WaveshareBoard.h b/variants/pico_w_dragino_sx1276/WaveshareBoard.h new file mode 100644 index 0000000000..694b8bd122 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/WaveshareBoard.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +// LoRa radio module pins for Waveshare RP2040-LoRa-HF/LF +// https://files.waveshare.com/wiki/RP2040-LoRa/Rp2040-lora-sch.pdf + +/* + * This board has no built-in way to read battery voltage. + * Nevertheless it's very easy to make it work, you only require two 1% resistors. + * + * BAT+ -----+ + * | + * VSYS --+ -/\/\/\/\- --+ + * 200k | + * +-- GPIO28 + * | + * GND --+ -/\/\/\/\- --+ + * | 100k + * BAT- -----+ + */ +#define PIN_VBAT_READ 28 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (3.0f * 3.3f * 1000) + +class WaveshareBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); } + void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); } +#endif + + uint16_t getBattMilliVolts() override { +#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw) / 4096; +#else + return 0; +#endif + } + + const char *getManufacturerName() const override { return "Waveshare RP2040-LoRa"; } + + void reboot() override { rp2040.reboot(); } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; diff --git a/variants/pico_w_dragino_sx1276/platformio.ini b/variants/pico_w_dragino_sx1276/platformio.ini new file mode 100644 index 0000000000..52ae21bc77 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/platformio.ini @@ -0,0 +1,49 @@ +[pico_w_dragino_sx1276] +extends = rp2040_base +board = rpipicow +build_flags = + ${rp2040_base.build_flags} + -I variants/pico_w_dragino_sx1276 + -I variants/waveshare_rp2040_lora + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -D SX127X_CURRENT_LIMIT=120 + -D LORA_TX_POWER=17 + -D LORA_FREQ=915.0 + -D P_LORA_DIO_0=6 + -D P_LORA_DIO_1=7 + -D P_LORA_RST=8 + -D P_LORA_SCLK=18 + -D P_LORA_MISO=16 + -D P_LORA_MOSI=19 + -D P_LORA_NSS=17 + -D ENV_INCLUDE_GPS=1 + -D PIN_GPS_RX=13 + -D PIN_GPS_TX=12 + -D setPins=setPinout + -D ENV_SKIP_GPS_DETECT=1 + -D PERSISTANT_GPS=1 + -D BLE_PIN_CODE=123456 + -D RTOS_STACK_SIZE_BLE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH +build_src_filter = ${rp2040_base.build_src_filter} + +<../variants/pico_w_dragino_sx1276> + +<../variants/waveshare_rp2040_lora/WaveshareBoard.cpp> + + + + + + +lib_deps = + ${rp2040_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:pico_w_dragino_companion_radio_ble] +extends = pico_w_dragino_sx1276 +build_flags = + ${pico_w_dragino_sx1276.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${pico_w_dragino_sx1276.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = + ${pico_w_dragino_sx1276.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/pico_w_dragino_sx1276/target.cpp b/variants/pico_w_dragino_sx1276/target.cpp new file mode 100644 index 0000000000..b2b65182f4 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/target.cpp @@ -0,0 +1,75 @@ +#include "target.h" +#include +#include +#include +#include + +WaveshareBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RST, P_LORA_DIO_1, SPI); +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +MicroNMEALocationProvider nmea(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(nmea); + +bool radio_init() { + pinMode(25, OUTPUT); + + // step 1 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.setRX(PIN_GPS_RX); + + // step 2 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.setTX(PIN_GPS_TX); + + // step 3 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + Serial1.begin(9600); + + // step 4 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + rtc_clock.begin(Wire); + + // step 5 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + SPI.begin(); + + // step 6 + digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); + bool result = radio.std_init(NULL); + + if (result) { + for (int i = 0; i < 10; i++) { + digitalWrite(25,HIGH); delay(100); digitalWrite(25,LOW); delay(100); + } + } else { + for (int i = 0; i < 3; i++) { + digitalWrite(25,HIGH); delay(500); digitalWrite(25,LOW); delay(500); + } + } + return result; +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(int8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} \ No newline at end of file diff --git a/variants/pico_w_dragino_sx1276/target.h b/variants/pico_w_dragino_sx1276/target.h new file mode 100644 index 0000000000..0451759693 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/target.h @@ -0,0 +1,21 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +extern WaveshareBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file From 40c3784d5ddf6345b75dd507bbb35027000042a8 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Wed, 13 May 2026 17:08:29 -0700 Subject: [PATCH 4/9] Working State --- src/helpers/rp2040/SerialBLEInterface.cpp | 70 +++++++++++------------ src/helpers/rp2040/SerialBLEInterface.h | 40 +++++++------ variants/pico_w_dragino_sx1276/target.cpp | 1 + 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/helpers/rp2040/SerialBLEInterface.cpp b/src/helpers/rp2040/SerialBLEInterface.cpp index 169255f649..880605c4d6 100644 --- a/src/helpers/rp2040/SerialBLEInterface.cpp +++ b/src/helpers/rp2040/SerialBLEInterface.cpp @@ -22,39 +22,50 @@ void SerialBLEInterface::onDisconnect(BLEServer* p) { } } +void SerialBLEInterface::onWrite(BLECharacteristic* c) { + size_t len = c->valueLen(); + const uint8_t* data = (const uint8_t*)c->valueData(); + Serial.printf("BLE: onWrite len=%d hdr=0x%02x\n", len, len > 0 ? data[0] : 0); + if (len > 0 && len <= MAX_FRAME_SIZE && recv_queue_len < FRAME_QUEUE_SIZE) { + recv_queue[recv_queue_len].len = len; + memcpy(recv_queue[recv_queue_len].buf, data, len); + recv_queue_len++; + } +} + void SerialBLEInterface::shiftSendQueueLeft() { if (send_queue_len > 0) { send_queue_len--; - for (uint8_t i = 0; i < send_queue_len; i++) { - send_queue[i] = send_queue[i + 1]; - } + for (uint8_t i = 0; i < send_queue_len; i++) send_queue[i] = send_queue[i + 1]; } } void SerialBLEInterface::shiftRecvQueueLeft() { if (recv_queue_len > 0) { recv_queue_len--; - for (uint8_t i = 0; i < recv_queue_len; i++) { - recv_queue[i] = recv_queue[i + 1]; - } + for (uint8_t i = 0; i < recv_queue_len; i++) recv_queue[i] = recv_queue[i + 1]; } } void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { - (void)pin_code; // TODO: add security/PIN support later + (void)pin_code; char dev_name[48]; snprintf(dev_name, sizeof(dev_name), "%s%s", prefix, name); - BLE.begin(dev_name); + BLE.begin(String(dev_name)); _server = BLE.server(); _server->setCallbacks(this); - _server->addService(&_uart); - _uart.begin(); - _uart.setAutoflush(0); // disable autoflush so we control when data is sent - BLE.setSecurity(BLESecurityNone); + _service = new BLEService(String(SERVICE_UUID)); + _txChar = new BLECharacteristic(String(TX_UUID), BLERead | BLENotify); + _rxChar = new BLECharacteristic(String(RX_UUID), BLEWrite | BLEWriteWithoutResponse); + _rxChar->setCallbacks(this); + + _service->addCharacteristic(_txChar); + _service->addCharacteristic(_rxChar); + _server->addService(_service); enable(); } @@ -74,10 +85,7 @@ void SerialBLEInterface::disable() { size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { if (!_isConnected || len == 0 || len > MAX_FRAME_SIZE) return 0; - if (send_queue_len >= FRAME_QUEUE_SIZE) { - BLE_DEBUG_PRINTLN("send queue full"); - return 0; - } + if (send_queue_len >= FRAME_QUEUE_SIZE) return 0; send_queue[send_queue_len].len = len; memcpy(send_queue[send_queue_len].buf, src, len); send_queue_len++; @@ -85,33 +93,19 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { } size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { - // drain send queue - if (send_queue_len > 0 && _isConnected) { - Frame& f = send_queue[0]; - size_t written = _uart.write(f.buf, f.len); - _uart.flush(); - Serial.printf("BLE: wrote %d of %d bytes\n", written, f.len); - if (written == f.len) { - shiftSendQueueLeft(); - } - } - - // read incoming - int avail = _uart.available(); - if (avail > 0) { - Serial.printf("BLE: %d bytes available\n", avail); - if (recv_queue_len < FRAME_QUEUE_SIZE) { - if (avail > MAX_FRAME_SIZE) avail = MAX_FRAME_SIZE; - recv_queue[recv_queue_len].len = avail; - _uart.readBytes(recv_queue[recv_queue_len].buf, avail); - recv_queue_len++; - } + // send queued frames + if (send_queue_len > 0 && _isConnected && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL) { + _last_write = millis(); + _txChar->setValue(send_queue[0].buf, send_queue[0].len); + Serial.printf("BLE: notify len=%d hdr=0x%02x\n", send_queue[0].len, send_queue[0].buf[0]); + shiftSendQueueLeft(); } + // return next received frame if (recv_queue_len > 0) { size_t len = recv_queue[0].len; - Serial.printf("BLE: returning frame len=%d hdr=0x%02x\n", len, recv_queue[0].buf[0]); memcpy(dest, recv_queue[0].buf, len); + Serial.printf("BLE: recv frame len=%d hdr=0x%02x\n", len, dest[0]); shiftRecvQueueLeft(); return len; } diff --git a/src/helpers/rp2040/SerialBLEInterface.h b/src/helpers/rp2040/SerialBLEInterface.h index 79c1bdbc18..eb2f4bf953 100644 --- a/src/helpers/rp2040/SerialBLEInterface.h +++ b/src/helpers/rp2040/SerialBLEInterface.h @@ -3,25 +3,22 @@ #include "../BaseSerialInterface.h" #include #include -#include +#include +#include -#ifndef BLE_TX_POWER -#define BLE_TX_POWER 4 -#endif - -#if BLE_DEBUG_LOGGING && ARDUINO - #include - #define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__) -#else - #define BLE_DEBUG_PRINTLN(...) {} -#endif - -class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks { +class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks, public BLECharacteristicCallbacks { BLEServer* _server = nullptr; - BLEServiceUART _uart; + BLEService* _service = nullptr; + BLECharacteristic* _txChar = new BLECharacteristic(String(TX_UUID), BLERead | BLENotify); + BLECharacteristic* _rxChar = _rxChar = new BLECharacteristic(String(RX_UUID), BLEWrite | BLEWriteWithoutResponse); bool _isEnabled = false; bool _isConnected = false; unsigned long _last_health_check = 0; + unsigned long _last_write = 0; + + static constexpr const char* SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; + static constexpr const char* TX_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; + static constexpr const char* RX_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; struct Frame { uint8_t len; @@ -29,6 +26,7 @@ class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks }; #define FRAME_QUEUE_SIZE 12 + #define BLE_WRITE_MIN_INTERVAL 60 uint8_t send_queue_len = 0; Frame send_queue[FRAME_QUEUE_SIZE]; @@ -48,11 +46,21 @@ class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks void onConnect(BLEServer* p) override; void onDisconnect(BLEServer* p) override; + // BLECharacteristicCallbacks - called when app writes to RX characteristic + void onWrite(BLECharacteristic* c) override; + void enable() override; void disable() override; bool isEnabled() const override { return _isEnabled; } bool isConnected() const override { return _isConnected; } - bool isWriteBusy() const override { return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3); } + bool isWriteBusy() const override { return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; } size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; -}; \ No newline at end of file +}; + +#if BLE_DEBUG_LOGGING && ARDUINO + #include + #define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__) +#else + #define BLE_DEBUG_PRINTLN(...) {} +#endif \ No newline at end of file diff --git a/variants/pico_w_dragino_sx1276/target.cpp b/variants/pico_w_dragino_sx1276/target.cpp index b2b65182f4..aec34ed785 100644 --- a/variants/pico_w_dragino_sx1276/target.cpp +++ b/variants/pico_w_dragino_sx1276/target.cpp @@ -66,6 +66,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { } void radio_set_tx_power(int8_t dbm) { + Serial.printf("TX power set: %d dBm\n", dbm); radio.setOutputPower(dbm); } From 9256683534bd66d65a0677ce4eb04e1e86ba2390 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Wed, 13 May 2026 18:43:28 -0700 Subject: [PATCH 5/9] Full functionality for ble --- src/helpers/rp2040/SerialBLEInterface.cpp | 5 --- variants/pico_dragino_sx1276/platformio.ini | 2 + variants/pico_dragino_sx1276/target.cpp | 41 +++++++------------ variants/pico_w_dragino_sx1276/platformio.ini | 2 + variants/pico_w_dragino_sx1276/target.cpp | 41 +++++++------------ 5 files changed, 32 insertions(+), 59 deletions(-) diff --git a/src/helpers/rp2040/SerialBLEInterface.cpp b/src/helpers/rp2040/SerialBLEInterface.cpp index 880605c4d6..e6e1b1af26 100644 --- a/src/helpers/rp2040/SerialBLEInterface.cpp +++ b/src/helpers/rp2040/SerialBLEInterface.cpp @@ -5,7 +5,6 @@ void SerialBLEInterface::onConnect(BLEServer* p) { (void)p; - Serial.println("BLE: connected"); _isConnected = true; send_queue_len = 0; recv_queue_len = 0; @@ -13,7 +12,6 @@ void SerialBLEInterface::onConnect(BLEServer* p) { void SerialBLEInterface::onDisconnect(BLEServer* p) { (void)p; - Serial.println("BLE: disconnected"); _isConnected = false; send_queue_len = 0; recv_queue_len = 0; @@ -25,7 +23,6 @@ void SerialBLEInterface::onDisconnect(BLEServer* p) { void SerialBLEInterface::onWrite(BLECharacteristic* c) { size_t len = c->valueLen(); const uint8_t* data = (const uint8_t*)c->valueData(); - Serial.printf("BLE: onWrite len=%d hdr=0x%02x\n", len, len > 0 ? data[0] : 0); if (len > 0 && len <= MAX_FRAME_SIZE && recv_queue_len < FRAME_QUEUE_SIZE) { recv_queue[recv_queue_len].len = len; memcpy(recv_queue[recv_queue_len].buf, data, len); @@ -97,7 +94,6 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { if (send_queue_len > 0 && _isConnected && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL) { _last_write = millis(); _txChar->setValue(send_queue[0].buf, send_queue[0].len); - Serial.printf("BLE: notify len=%d hdr=0x%02x\n", send_queue[0].len, send_queue[0].buf[0]); shiftSendQueueLeft(); } @@ -105,7 +101,6 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { if (recv_queue_len > 0) { size_t len = recv_queue[0].len; memcpy(dest, recv_queue[0].buf, len); - Serial.printf("BLE: recv frame len=%d hdr=0x%02x\n", len, dest[0]); shiftRecvQueueLeft(); return len; } diff --git a/variants/pico_dragino_sx1276/platformio.ini b/variants/pico_dragino_sx1276/platformio.ini index ac55d75ea0..158da4fe33 100644 --- a/variants/pico_dragino_sx1276/platformio.ini +++ b/variants/pico_dragino_sx1276/platformio.ini @@ -1,6 +1,7 @@ [pico_dragino_sx1276] extends = rp2040_base board = pico +board_build.filesystem_size = 0.5m build_flags = ${rp2040_base.build_flags} -I variants/pico_dragino_sx1276 @@ -23,6 +24,7 @@ build_flags = -D setPins=setPinout -D ENV_SKIP_GPS_DETECT=1 -D PERSISTANT_GPS=1 + -D PICO_FLASH_SIZE_BYTES=2097152 build_src_filter = ${rp2040_base.build_src_filter} +<../variants/pico_dragino_sx1276> +<../variants/waveshare_rp2040_lora/WaveshareBoard.cpp> diff --git a/variants/pico_dragino_sx1276/target.cpp b/variants/pico_dragino_sx1276/target.cpp index b2b65182f4..87e36e7f5f 100644 --- a/variants/pico_dragino_sx1276/target.cpp +++ b/variants/pico_dragino_sx1276/target.cpp @@ -1,5 +1,6 @@ #include "target.h" #include +#include #include #include #include @@ -16,42 +17,28 @@ MicroNMEALocationProvider nmea(Serial1, &rtc_clock); EnvironmentSensorManager sensors(nmea); bool radio_init() { - pinMode(25, OUTPUT); + Serial.begin(115200); + delay(2000); - // step 1 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.setRX(PIN_GPS_RX); - - // step 2 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.setTX(PIN_GPS_TX); - - // step 3 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.begin(9600); - - // step 4 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); rtc_clock.begin(Wire); - - // step 5 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); SPI.begin(); - // step 6 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); - bool result = radio.std_init(NULL); - - if (result) { - for (int i = 0; i < 10; i++) { - digitalWrite(25,HIGH); delay(100); digitalWrite(25,LOW); delay(100); - } + if (LittleFS.begin()) { + Serial.println("LittleFS mounted OK"); + } else { + Serial.println("LittleFS mount failed, formatting..."); + LittleFS.format(); + if (LittleFS.begin()) { + Serial.println("LittleFS formatted and mounted OK"); } else { - for (int i = 0; i < 3; i++) { - digitalWrite(25,HIGH); delay(500); digitalWrite(25,LOW); delay(500); - } + Serial.println("LittleFS still failed!"); } - return result; + } + + return radio.std_init(NULL);; } uint32_t radio_get_rng_seed() { diff --git a/variants/pico_w_dragino_sx1276/platformio.ini b/variants/pico_w_dragino_sx1276/platformio.ini index 52ae21bc77..bf5ca1171a 100644 --- a/variants/pico_w_dragino_sx1276/platformio.ini +++ b/variants/pico_w_dragino_sx1276/platformio.ini @@ -1,6 +1,7 @@ [pico_w_dragino_sx1276] extends = rp2040_base board = rpipicow +board_build.filesystem_size = 0.5m build_flags = ${rp2040_base.build_flags} -I variants/pico_w_dragino_sx1276 @@ -26,6 +27,7 @@ build_flags = -D BLE_PIN_CODE=123456 -D RTOS_STACK_SIZE_BLE=4096 -D PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH + -D PICO_FLASH_SIZE_BYTES=2097152 build_src_filter = ${rp2040_base.build_src_filter} +<../variants/pico_w_dragino_sx1276> +<../variants/waveshare_rp2040_lora/WaveshareBoard.cpp> diff --git a/variants/pico_w_dragino_sx1276/target.cpp b/variants/pico_w_dragino_sx1276/target.cpp index aec34ed785..aee20371e3 100644 --- a/variants/pico_w_dragino_sx1276/target.cpp +++ b/variants/pico_w_dragino_sx1276/target.cpp @@ -1,5 +1,6 @@ #include "target.h" #include +#include #include #include #include @@ -16,41 +17,28 @@ MicroNMEALocationProvider nmea(Serial1, &rtc_clock); EnvironmentSensorManager sensors(nmea); bool radio_init() { - pinMode(25, OUTPUT); + Serial.begin(115200); + delay(2000); - // step 1 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.setRX(PIN_GPS_RX); - - // step 2 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.setTX(PIN_GPS_TX); - - // step 3 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); Serial1.begin(9600); - - // step 4 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); rtc_clock.begin(Wire); - - // step 5 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); SPI.begin(); - - // step 6 - digitalWrite(25,HIGH); delay(300); digitalWrite(25,LOW); delay(300); bool result = radio.std_init(NULL); - - if (result) { - for (int i = 0; i < 10; i++) { - digitalWrite(25,HIGH); delay(100); digitalWrite(25,LOW); delay(100); - } + + if (LittleFS.begin()) { + Serial.println("LittleFS mounted OK"); + } else { + Serial.println("LittleFS mount failed, formatting..."); + LittleFS.format(); + if (LittleFS.begin()) { + Serial.println("LittleFS formatted and mounted OK"); } else { - for (int i = 0; i < 3; i++) { - digitalWrite(25,HIGH); delay(500); digitalWrite(25,LOW); delay(500); - } + Serial.println("LittleFS still failed!"); } + } + return result; } @@ -66,7 +54,6 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { } void radio_set_tx_power(int8_t dbm) { - Serial.printf("TX power set: %d dBm\n", dbm); radio.setOutputPower(dbm); } From 628832689c05289e96c25a378c249876b7eacfdd Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Sat, 16 May 2026 12:12:38 -0700 Subject: [PATCH 6/9] upped power --- variants/pico_w_dragino_sx1276/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/pico_w_dragino_sx1276/platformio.ini b/variants/pico_w_dragino_sx1276/platformio.ini index bf5ca1171a..043217f603 100644 --- a/variants/pico_w_dragino_sx1276/platformio.ini +++ b/variants/pico_w_dragino_sx1276/platformio.ini @@ -8,8 +8,8 @@ build_flags = -I variants/waveshare_rp2040_lora -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper - -D SX127X_CURRENT_LIMIT=120 - -D LORA_TX_POWER=17 + -D SX127X_CURRENT_LIMIT=240 + -D LORA_TX_POWER=20 -D LORA_FREQ=915.0 -D P_LORA_DIO_0=6 -D P_LORA_DIO_1=7 From 30159c45f1df0dfae6f911674ee7f8e5174f1c01 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Sat, 23 May 2026 10:43:49 -0700 Subject: [PATCH 7/9] trying wifi config --- variants/xiao_s3_wio/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 13d406792b..5d1364396e 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -210,8 +210,8 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID='"myssid"' - -D WIFI_PWD='"password"' + -D WIFI_SSID='"BATK-25308"' + -D WIFI_PWD='"03262007Rl:)"' ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 660d5a4ae54f39c1539a2cc7ce9f42be90512878 Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:33:29 -0700 Subject: [PATCH 8/9] updated repeaters --- examples/mobile_repeater/MobilityWatchdog.h | 137 ++++++++++++++++++++ examples/mobile_repeater/main.cpp | 112 ++++++++++++++++ variants/pico_dragino_sx1276/platformio.ini | 48 +++++-- variants/xiao_s3_wio/platformio.ini | 4 +- 4 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 examples/mobile_repeater/MobilityWatchdog.h create mode 100644 examples/mobile_repeater/main.cpp diff --git a/examples/mobile_repeater/MobilityWatchdog.h b/examples/mobile_repeater/MobilityWatchdog.h new file mode 100644 index 0000000000..4f4a12aac7 --- /dev/null +++ b/examples/mobile_repeater/MobilityWatchdog.h @@ -0,0 +1,137 @@ +#pragma once +#include +#include +#include "target.h" + +#ifndef STATIONARY_SECS +#define STATIONARY_SECS 600 +#endif +#ifndef MOVE_THRESHOLD_M +#define MOVE_THRESHOLD_M 8 +#endif +#ifndef WATCHDOG_POLL_SECS +#define WATCHDOG_POLL_SECS 20 +#endif +// Don't poll at all until this many seconds after boot. +// Gives sensors.begin() / NMEA parser time to fully initialise. +#ifndef WATCHDOG_BOOT_DELAY_SECS +#define WATCHDOG_BOOT_DELAY_SECS 30 +#endif + +class MyMesh; + +class MobilityWatchdog { +public: + enum State { DRIVING, PARKED }; + + explicit MobilityWatchdog(MyMesh& mesh) + : _mesh(mesh), _state(DRIVING), + _lastPollS(0), _stationaryStartS(0), + _anchorLat(0.0), _anchorLon(0.0), + _gpsEverValid(false) {} + + void begin() { + char reply[64]; + _mesh.handleCommand(0, "set repeat off", reply); + Serial.println("[watchdog] driving mode — repeat off"); + } + + void loop() { + uint32_t now_s = millis() / 1000UL; + + // Wait for boot delay before doing anything + if (now_s < (uint32_t)WATCHDOG_BOOT_DELAY_SECS) return; + + if ((now_s - _lastPollS) < (uint32_t)WATCHDOG_POLL_SECS) return; + _lastPollS = now_s; + + // Safely get the location provider + LocationProvider* gps = sensors.getLocationProvider(); + if (!gps) return; + + // isValid() and coordinate reads are virtual calls — guard with + // a local copy of the pointer to avoid any race with sensors.loop() + bool valid = false; + long rawLat = 0, rawLon = 0; + + // Wrap in a simple validity check before reading coords + valid = gps->isValid(); + if (!valid) { + if (!_gpsEverValid) { + Serial.println("[watchdog] no GPS fix yet"); + } + _stationaryStartS = 0; + return; + } + + rawLat = gps->getLatitude(); + rawLon = gps->getLongitude(); + + // Sanity check — discard obviously bad reads + if (rawLat == 0 && rawLon == 0) return; + + _gpsEverValid = true; + + double lat = rawLat / 1000000.0; + double lon = rawLon / 1000000.0; + + if (_stationaryStartS == 0) { + _anchorLat = lat; + _anchorLon = lon; + _stationaryStartS = now_s; + Serial.printf("[watchdog] first fix %.6f, %.6f\n", lat, lon); + return; + } + + float distM = _haversineM(_anchorLat, _anchorLon, lat, lon); + + if (distM > (float)MOVE_THRESHOLD_M) { + if (_state == PARKED) { + Serial.printf("[watchdog] moving (%.1fm) — repeat off\n", distM); + char reply[64]; + _mesh.handleCommand(0, "advert", reply); + _mesh.handleCommand(0, "set repeat off", reply); + _state = DRIVING; + } + _anchorLat = lat; + _anchorLon = lon; + _stationaryStartS = now_s; + return; + } + + uint32_t stationaryS = now_s - _stationaryStartS; + if (_state == DRIVING && stationaryS >= (uint32_t)STATIONARY_SECS) { + Serial.println("[watchdog] parked — repeat on"); + char reply[64]; + _mesh.handleCommand(0, "set repeat on", reply); + _mesh.handleCommand(0, "advert", reply); + _state = PARKED; + } else if (_state == DRIVING) { + Serial.printf("[watchdog] stationary %lus/%us (%.1fm)\n", + (unsigned long)stationaryS, + (unsigned)STATIONARY_SECS, + distM); + } + } + + State getState() const { return _state; } + +private: + MyMesh& _mesh; + State _state; + uint32_t _lastPollS; + uint32_t _stationaryStartS; + double _anchorLat, _anchorLon; + bool _gpsEverValid; + + static float _haversineM(double lat1, double lon1, + double lat2, double lon2) { + const double R = 6371000.0; + double dLat = (lat2 - lat1) * M_PI / 180.0; + double dLon = (lon2 - lon1) * M_PI / 180.0; + double a = sin(dLat/2)*sin(dLat/2) + + cos(lat1*M_PI/180.0)*cos(lat2*M_PI/180.0) + * sin(dLon/2)*sin(dLon/2); + return (float)(R * 2.0 * atan2(sqrt(a), sqrt(1.0 - a))); + } +}; \ No newline at end of file diff --git a/examples/mobile_repeater/main.cpp b/examples/mobile_repeater/main.cpp new file mode 100644 index 0000000000..4b81cfac12 --- /dev/null +++ b/examples/mobile_repeater/main.cpp @@ -0,0 +1,112 @@ +#include +#include +#include "../simple_repeater/MyMesh.h" +#include "MobilityWatchdog.h" + +#ifdef DISPLAY_CLASS + #include "UITask.h" + static UITask ui_task(display); +#endif + +StdRNG fast_rng; +SimpleMeshTables tables; + +MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables); +static MobilityWatchdog watchdog(the_mesh); + +void halt() { + while (1) ; +} + +static char command[160]; +unsigned long lastActive = 0; +unsigned long nextSleepinSecs = 120; + +void setup() { + Serial.begin(115200); + + board.begin(); + + if (!radio_init()) { + halt(); + } + + fast_rng.begin(radio_get_rng_seed()); + + LittleFS.begin(); + FILESYSTEM* fs = &LittleFS; + IdentityStore store(LittleFS, "/identity"); + store.begin(); + + if (!store.load("_main", the_mesh.self_id)) { + the_mesh.self_id = radio_new_identity(); + int count = 0; + while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { + the_mesh.self_id = radio_new_identity(); count++; + } + store.save("_main", the_mesh.self_id); + } + + command[0] = 0; + lastActive = millis(); + + sensors.begin(); + the_mesh.begin(fs); + +#ifdef DISPLAY_CLASS + ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); +#endif + + watchdog.begin(); + +#if ENABLE_ADVERT_ON_BOOT == 1 + the_mesh.sendSelfAdvertisement(16000, false); +#endif +} + +void loop() { + // Serial CLI + int len = strlen(command); + while (Serial.available() && len < sizeof(command)-1) { + char c = Serial.read(); + if (c != '\n') { + command[len++] = c; + command[len] = 0; + Serial.print(c); + } + if (c == '\r') break; + } + if (len == sizeof(command)-1) { + command[sizeof(command)-1] = '\r'; + } + if (len > 0 && command[len - 1] == '\r') { + Serial.print('\n'); + command[len - 1] = 0; + char reply[160]; + the_mesh.handleCommand(0, command, reply); + if (reply[0]) { + Serial.print(" -> "); Serial.println(reply); + } + command[0] = 0; + } + + the_mesh.loop(); + sensors.loop(); + watchdog.loop(); + +#ifdef DISPLAY_CLASS + ui_task.loop(); +#endif + + rtc_clock.tick(); + + if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { + if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { + board.sleep(1800); + lastActive = millis(); + nextSleepinSecs = 5; + } else { + nextSleepinSecs += 5; + } + } +} \ No newline at end of file diff --git a/variants/pico_dragino_sx1276/platformio.ini b/variants/pico_dragino_sx1276/platformio.ini index 158da4fe33..bb37f97a27 100644 --- a/variants/pico_dragino_sx1276/platformio.ini +++ b/variants/pico_dragino_sx1276/platformio.ini @@ -8,9 +8,12 @@ build_flags = -I variants/waveshare_rp2040_lora -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper - -D SX127X_CURRENT_LIMIT=120 - -D LORA_TX_POWER=17 - -D LORA_FREQ=915.0 + -D SX127X_CURRENT_LIMIT=240 + -D LORA_TX_POWER=20 + -D LORA_FREQ=910.525 + -D LORA_BW=62.5 + -D LORA_SF=7 + -D LORA_CR=5 -D P_LORA_DIO_0=6 -D P_LORA_DIO_1=7 -D P_LORA_RST=8 @@ -53,13 +56,42 @@ lib_ignore = extends = pico_dragino_sx1276 build_flags = ${pico_dragino_sx1276.build_flags} - -D ADVERT_NAME='"Pico Repeater"' + -D ADVERT_NAME='"CHENWA-ROOF-ACM"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D PERSISTANT_GPS=1 + -D ADMIN_PASSWORD='"LegoMan1!#"' + -D MAX_NEIGHBOURS=100 + -D PERSISTANT_GPS=0 build_src_filter = ${pico_dragino_sx1276.build_src_filter} +<../examples/simple_repeater> lib_deps = - ${pico_dragino_sx1276.lib_deps} \ No newline at end of file + ${pico_dragino_sx1276.lib_deps} +lib_ignore = + BLE + WiFi + +[env:pico_dragino_mobile_repeater] +extends = pico_dragino_sx1276 +build_flags = + ${pico_dragino_sx1276.build_flags} + -D ADVERT_NAME='"SPKNWA-MOB"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"acm_mob_1!#"' + -D MAX_NEIGHBOURS=100 + -D PERSISTANT_GPS=1 + -D STATIONARY_SECS=300 ; 10 min parked → repeater on + -D MOVE_THRESHOLD_M=8 ; metres before considered moving + -D WATCHDOG_POLL_SECS=20 ; GPS check interval + -D ENV_INCLUDE_GPS=1 + -D ENABLE_ADVERT_ON_BOOT=1 +build_src_filter = + ${pico_dragino_sx1276.build_src_filter} + +<../examples/simple_repeater/MyMesh.cpp> + +<../examples/simple_repeater/UITask.cpp> + +<../examples/mobile_repeater/main.cpp> +lib_deps = + ${pico_dragino_sx1276.lib_deps} +lib_ignore = + BLE + WiFi \ No newline at end of file diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 5d1364396e..ab1f9bd238 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -16,6 +16,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=8 -D P_LORA_MOSI=9 -D PIN_USER_BTN=21 + -D PIN_VBAT_READ=10 -D PIN_STATUS_LED=48 -D PIN_BOARD_SDA=D4 -D PIN_BOARD_SCL=D5 @@ -23,8 +24,7 @@ build_flags = ${esp32_base.build_flags} -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=140 - -D USE_SX1262 + -D SX126X_CURRENT_LIMIT=140 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 From 449c33edd48bdd1e8370aa7c852a8b6bb6ccd3da Mon Sep 17 00:00:00 2001 From: LegoManACM <41910060+LegoManACM@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:09:13 -0700 Subject: [PATCH 9/9] Update WiFi SSID and password in platformio.ini --- variants/xiao_s3_wio/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index ab1f9bd238..453e6b5eb2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -210,8 +210,8 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID='"BATK-25308"' - -D WIFI_PWD='"03262007Rl:)"' + -D WIFI_SSID='"SSID"' + -D WIFI_PWD='"PASSWORD"' ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1