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] 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 876dc9c33..cac30be82 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 000000000..169255f64 --- /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 000000000..79c1bdbc1 --- /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 000000000..694b8bd12 --- /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 000000000..52ae21bc7 --- /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 000000000..b2b65182f --- /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 000000000..045175969 --- /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