From 4541380632a791dd7e2de70ea1c1e36f2f9d85a9 Mon Sep 17 00:00:00 2001 From: jankowski-t Date: Mon, 30 Jun 2025 03:46:18 +0200 Subject: [PATCH 1/2] Support MeshAdventurer Added support for MeshAdventurer, including radio, display, button, GPS, voltage reading --- src/helpers/MeshadventurerBoard.h | 81 +++++++++ variants/meshadventurer/platformio.ini | 234 +++++++++++++++++++++++++ variants/meshadventurer/target.cpp | 152 ++++++++++++++++ variants/meshadventurer/target.h | 46 +++++ variants/meshadventurer/variant.h | 44 +++++ 5 files changed, 557 insertions(+) create mode 100644 src/helpers/MeshadventurerBoard.h create mode 100644 variants/meshadventurer/platformio.ini create mode 100644 variants/meshadventurer/target.cpp create mode 100644 variants/meshadventurer/target.h create mode 100644 variants/meshadventurer/variant.h diff --git a/src/helpers/MeshadventurerBoard.h b/src/helpers/MeshadventurerBoard.h new file mode 100644 index 00000000..65e11102 --- /dev/null +++ b/src/helpers/MeshadventurerBoard.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +// LoRa radio module pins for Meshadventurer +#define P_LORA_DIO_1 33 +#define P_LORA_NSS 18 +#define P_LORA_RESET 23 +#define P_LORA_BUSY 32 +#define P_LORA_SCLK 5 +#define P_LORA_MISO 19 +#define P_LORA_MOSI 27 + +#define PIN_VBAT_READ 35 + +#include "ESP32Board.h" + +#include + +class MeshadventurerBoard : public ESP32Board { + +public: + void begin() { + ESP32Board::begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void powerOff() override { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < 4; i++) { + raw += analogReadMilliVolts(PIN_VBAT_READ); + } + raw = raw / 4; + + return (2 * raw); + } + + const char* getManufacturerName() const override { + return "Meshadventurer"; + } +}; diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini new file mode 100644 index 00000000..5fea4103 --- /dev/null +++ b/variants/meshadventurer/platformio.ini @@ -0,0 +1,234 @@ +[Meshadventurer] +extends = esp32_base +board = esp32doit-devkit-v1 +board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +build_flags = + ${esp32_base.build_flags} + -I variants/meshadventurer + -D MESHADVENTURER + -D P_LORA_TX_LED=2 + -D PIN_VBAT_READ=35 + -D PIN_USER_BTN_ANA=39 + -D P_LORA_DIO_1=33 + -D P_LORA_NSS=18 + -D P_LORA_RESET=23 + -D P_LORA_BUSY=32 + -D P_LORA_SCLK=5 + -D P_LORA_MOSI=27 + -D P_LORA_MISO=19 + -D SX126X_TXEN=13 + -D SX126X_RXEN=14 + -D PIN_BOARD_SDA=21 + -D PIN_BOARD_SCL=22 + -D SX126X_DIO2_AS_RF_SWITCH=false + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_GPS_RX=12 + -D PIN_GPS_TX=15 + -D DISPLAY_CLASS=SSD1306Display +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/meshadventurer> +lib_deps = + ${esp32_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Meshadventurer_sx1262_repeater] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_repeater/main.cpp> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"Meshadventurer Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1268_repeater] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_repeater/main.cpp> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"Meshadventurer Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1262_companion_radio_usb] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/companion_radio> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1262_companion_radio_ble] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/companion_radio> + + + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1262_terminal_chat] +extends = Meshadventurer +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> + + +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1262_room_server] +extends = Meshadventurer +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"Meshadventurer Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_room_server> + + +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1268_companion_radio_usb] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/companion_radio> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1268_companion_radio_ble] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/companion_radio> + + + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1268_terminal_chat] +extends = Meshadventurer +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> + + +lib_deps = + ${Meshadventurer.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshadventurer_sx1268_room_server] +extends = Meshadventurer +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"Meshadventurer Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_room_server> + + +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} \ No newline at end of file diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp new file mode 100644 index 00000000..43e171c0 --- /dev/null +++ b/variants/meshadventurer/target.cpp @@ -0,0 +1,152 @@ +#include +#include "target.h" + +#include + +MeshadventurerBoard board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MASensorManager sensors = MASensorManager(nmea); + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); +#endif + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(1); + +#if defined(SX126X_RXEN) && defined(SX126X_TXEN) + radio.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); +#endif + +#ifdef SX126X_CURRENT_LIMIT + radio.setCurrentLimit(SX126X_CURRENT_LIMIT); +#endif +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + +void MASensorManager::start_gps() { + if(!gps_active) { + MESH_DEBUG_PRINTLN("starting GPS"); + gps_active = true; + } +} + +void MASensorManager::stop_gps() { + if(gps_active) { + MESH_DEBUG_PRINTLN("stopping GPS"); + gps_active = false; + } +} + +bool MASensorManager::begin() { + Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX); + Serial1.begin(9600); + delay(500); + return true; +} + +bool MASensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if(requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); + } + return true; +} + +void MASensorManager::loop() { + static long next_gps_update = 0; + _location->loop(); + if(millis() > next_gps_update && gps_active) { + if(_location->isValid()) { + node_lat = ((double)_location->getLatitude()) / 1000000.; + node_lon = ((double)_location->getLongitude()) / 1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + 1000; + } +} + +int MASensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char* MASensorManager::getSettingName(int i) const { + return i == 0 ? "gps" : NULL; +} +const char* MASensorManager::getSettingValue(int i) const { + if(i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} +bool MASensorManager::setSettingValue(const char* name, const char* value) { + if(strcmp(name, "gps") == 0) { + if(strcmp(value, "0") == 0) { + stop_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h new file mode 100644 index 00000000..0e0235ba --- /dev/null +++ b/variants/meshadventurer/target.h @@ -0,0 +1,46 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include +#endif + +class MASensorManager : public SensorManager { + bool gps_active = false; + LocationProvider * _location; + + void start_gps(); + void stop_gps(); +public: + MASensorManager(LocationProvider &location): _location(&location) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; + +extern MeshadventurerBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern MASensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/meshadventurer/variant.h b/variants/meshadventurer/variant.h new file mode 100644 index 00000000..356a3e17 --- /dev/null +++ b/variants/meshadventurer/variant.h @@ -0,0 +1,44 @@ +// For OLED LCD +#define I2C_SDA 21 +#define I2C_SCL 22 + +// For GPS, 'undef's not needed +#define GPS_TX_PIN 15 +#define GPS_RX_PIN 12 +#define PIN_GPS_EN 4 +#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here + +#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam +#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_MULTIPLIER 2 +#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards +#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). +#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) + +// Radio +#define USE_SX1262 // E22-900M30S uses SX1262 +#define USE_SX1268 // E22-400M30S uses SX1268 +#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V + +#define SX126X_CS 18 // EBYTE module's NSS pin +#define SX126X_SCK 5 // EBYTE module's SCK pin +#define SX126X_MOSI 27 // EBYTE module's MOSI pin +#define SX126X_MISO 19 // EBYTE module's MISO pin +#define SX126X_RESET 23 // EBYTE module's NRST pin +#define SX126X_BUSY 32 // EBYTE module's BUSY pin +#define SX126X_DIO1 33 // EBYTE module's DIO1 pin + +// The E22's TXEN pin is connected to MCU pin, E22's RXEN pin is connected to MCU pin (allows for ramping up PA before transmission +// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent +// a short if they are both connected at the same time and there's a slight non-neglibible delay and/or voltage difference between +// DIO2 and TXEN). +#define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU +#define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU + +#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure +#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure +#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure +#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure +#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure From c91356016bbcf0366bd0478af9c6f33a00cc2a2d Mon Sep 17 00:00:00 2001 From: jankowski-t Date: Mon, 30 Jun 2025 05:40:07 +0200 Subject: [PATCH 2/2] Fixed MeshAdventurer repeater build failing Update build_src_filter in platformio.ini --- variants/meshadventurer/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index 5fea4103..f8995e18 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -36,7 +36,7 @@ lib_deps = [env:Meshadventurer_sx1262_repeater] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater> + build_flags = ${Meshadventurer.build_flags} @@ -57,7 +57,7 @@ lib_deps = [env:Meshadventurer_sx1268_repeater] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater> + build_flags = ${Meshadventurer.build_flags}