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/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/src/helpers/rp2040/SerialBLEInterface.cpp b/src/helpers/rp2040/SerialBLEInterface.cpp new file mode 100644 index 0000000000..e6e1b1af26 --- /dev/null +++ b/src/helpers/rp2040/SerialBLEInterface.cpp @@ -0,0 +1,118 @@ +#include "SerialBLEInterface.h" +#include + +#define BLE_HEALTH_CHECK_INTERVAL 10000 + +void SerialBLEInterface::onConnect(BLEServer* p) { + (void)p; + _isConnected = true; + send_queue_len = 0; + recv_queue_len = 0; +} + +void SerialBLEInterface::onDisconnect(BLEServer* p) { + (void)p; + _isConnected = false; + send_queue_len = 0; + recv_queue_len = 0; + if (_isEnabled) { + BLE.startAdvertising(); + } +} + +void SerialBLEInterface::onWrite(BLECharacteristic* c) { + size_t len = c->valueLen(); + const uint8_t* data = (const uint8_t*)c->valueData(); + 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]; + } +} + +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; + + char dev_name[48]; + snprintf(dev_name, sizeof(dev_name), "%s%s", prefix, name); + + BLE.begin(String(dev_name)); + + _server = BLE.server(); + _server->setCallbacks(this); + + _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(); +} + +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) 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[]) { + // 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); + shiftSendQueueLeft(); + } + + // return next received frame + if (recv_queue_len > 0) { + size_t len = recv_queue[0].len; + 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..eb2f4bf953 --- /dev/null +++ b/src/helpers/rp2040/SerialBLEInterface.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../BaseSerialInterface.h" +#include +#include +#include +#include + +class SerialBLEInterface : public BaseSerialInterface, public BLEServerCallbacks, public BLECharacteristicCallbacks { + BLEServer* _server = nullptr; + 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; + uint8_t buf[MAX_FRAME_SIZE]; + }; + + #define FRAME_QUEUE_SIZE 12 + #define BLE_WRITE_MIN_INTERVAL 60 + + 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; + + // 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 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; +}; + +#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_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..bb37f97a27 --- /dev/null +++ b/variants/pico_dragino_sx1276/platformio.ini @@ -0,0 +1,97 @@ +[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 + -I variants/waveshare_rp2040_lora + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -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 + -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 PICO_FLASH_SIZE_BYTES=2097152 +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='"CHENWA-ROOF-ACM"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -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} +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/pico_dragino_sx1276/target.cpp b/variants/pico_dragino_sx1276/target.cpp new file mode 100644 index 0000000000..87e36e7f5f --- /dev/null +++ b/variants/pico_dragino_sx1276/target.cpp @@ -0,0 +1,62 @@ +#include "target.h" +#include +#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() { + Serial.begin(115200); + delay(2000); + + Serial1.setRX(PIN_GPS_RX); + Serial1.setTX(PIN_GPS_TX); + Serial1.begin(9600); + rtc_clock.begin(Wire); + SPI.begin(); + + 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 { + Serial.println("LittleFS still failed!"); + } + } + + return radio.std_init(NULL);; +} + +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..0451759693 --- /dev/null +++ b/variants/pico_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 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..043217f603 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/platformio.ini @@ -0,0 +1,51 @@ +[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 + -I variants/waveshare_rp2040_lora + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -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 + -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 + -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> + + + + + + +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..aee20371e3 --- /dev/null +++ b/variants/pico_w_dragino_sx1276/target.cpp @@ -0,0 +1,63 @@ +#include "target.h" +#include +#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() { + Serial.begin(115200); + delay(2000); + + Serial1.setRX(PIN_GPS_RX); + Serial1.setTX(PIN_GPS_TX); + Serial1.begin(9600); + rtc_clock.begin(Wire); + SPI.begin(); + bool result = radio.std_init(NULL); + + 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 { + Serial.println("LittleFS still 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_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 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 13d406792b..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 @@ -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