Compare commits

..

10 Commits

Author SHA1 Message Date
a57b3bc6de Merge stashed changes; use upstream flash.py 2026-03-18 17:34:25 +03:00
James L
b3b6cd4302 Fix flash utility compatibility and rebuild firmware binaries 2026-03-15 19:57:17 -04:00
James L
7e56611fe6 Add AI development note to README 2026-03-15 17:54:11 -04:00
James L
56c1a6b881 Normalize release binary file modes 2026-03-14 12:19:11 -04:00
James L
79cb2d49e8 Add transport mode notes and config updates 2026-03-14 12:18:37 -04:00
James L
d8c925769d v1.0.22: Update precompiled firmware binaries for V3 and V4 2026-03-11 21:43:25 -04:00
James L
300676e5ae Add SF=5 option to config portal spreading factor dropdown 2026-03-11 21:42:24 -04:00
James L
d49c55a843 v1.0.21: Update precompiled firmware binaries for V3 and V4 2026-03-11 21:35:56 -04:00
James L
85d8fc7f78 Set default screen blank time to 5 minutes and added SF=5 as an option 2026-03-11 21:14:25 -04:00
James L
949c13c7b1 feat: add LED indicators and headless mode support for V3/V4
- Detect missing OLED at boot, set headless_mode flag
- LED solid: normal operation (radio online)
- LED fast blink: button held >5s (entering WCC config mode)
- LED slow breathe: WiFi Captive Configure portal active
- Allow 1-3s button press in WCC mode to power off
- Next boot after WCC power-off skips config portal (unless unconfigured)
- LED indicators active on both V3 and V4, with or without display
- Clean up LED PWM on deep sleep
2026-03-08 13:59:25 -04:00
21 changed files with 485 additions and 116 deletions

View File

@@ -122,6 +122,8 @@
#define MODEL_FE 0xFE // Homebrew board, max 17dBm output power #define MODEL_FE 0xFE // Homebrew board, max 17dBm output power
#define MODEL_FF 0xFF // Homebrew board, max 14dBm output power #define MODEL_FF 0xFF // Homebrew board, max 14dBm output power
#define BOARD_MESHADVENTURER_S3 0xF2
#if defined(__AVR_ATmega1284P__) #if defined(__AVR_ATmega1284P__)
#define PLATFORM PLATFORM_AVR #define PLATFORM PLATFORM_AVR
#define MCU_VARIANT MCU_1284P #define MCU_VARIANT MCU_1284P
@@ -449,6 +451,50 @@
const int pin_miso = 11; const int pin_miso = 11;
const int pin_sclk = 9; const int pin_sclk = 9;
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define IS_ESP32S3 true
#define HAS_DISPLAY true
#define HAS_NP true
#define HAS_BLUETOOTH false
#define HAS_BLE true
#define HAS_WIFI true
#define HAS_CONSOLE true
#define HAS_EEPROM true
#define HAS_BUSY true
#define HAS_INPUT true
#define HAS_TCXO true
#define MODEM SX1262
#define DIO2_AS_RF_SWITCH false
#define HAS_RF_SWITCH_RX_TX true
#define HAS_LORA_LNA true
#define LORA_LNA_GAIN 30
#define LORA_LNA_GVT 14
#define PA_MAX_OUTPUT 22
const int pin_cs = 39;
const int pin_sclk = 38;
const int pin_miso = 40;
const int pin_mosi = 18;
const int pin_busy = 7;
const int pin_reset = 43;
const int pin_dio = 15;
const int pin_txen = 9;
const int pin_rxen = 8;
const int pin_tcxo_enable = -1;
const int pin_btn_usr1 = 4;
const int pin_np = 48;
#if HAS_NP == false
#if defined(EXTERNAL_LEDS)
const int pin_led_rx = 48;
const int pin_led_tx = 48;
#else
const int pin_led_rx = 48;
const int pin_led_tx = 48;
#endif
#endif
#elif BOARD_MODEL == BOARD_RNODE_NG_20 #elif BOARD_MODEL == BOARD_RNODE_NG_20
#define HAS_DISPLAY true #define HAS_DISPLAY true
#define HAS_BLUETOOTH true #define HAS_BLUETOOTH true

75
BoundaryConfig.h Normal file → Executable file
View File

@@ -1,7 +1,10 @@
// Copyright (C) 2026, Boundary Mode Extension // Copyright (C) 2026, Boundary Mode Extension
// Based on microReticulum_Firmware by Mark Qvist // Based on microReticulum_Firmware by Mark Qvist
// //
// BoundaryConfig.h — Captive-portal web configuration for Boundary Mode. // BoundaryConfig.h — Captive-portal web configuration for the legacy
// "Boundary Mode" path. This should be renamed to "Transport Mode"
// together with the rest of the boundary-mode terminology. In this fork,
// transport/boundary mode is the only intended mode of operation.
// When triggered (first boot with no config, or button hold >5s), // When triggered (first boot with no config, or button hold >5s),
// the device starts a WiFi AP with a web form for all settings: // the device starts a WiFi AP with a web form for all settings:
// WiFi STA credentials, TCP backbone params, LoRa radio params, // WiFi STA credentials, TCP backbone params, LoRa radio params,
@@ -59,6 +62,16 @@ static const char* const BW_OPTIONS_LABELS[] PROGMEM = {
}; };
static const int BW_OPTIONS_COUNT = sizeof(BW_OPTIONS_HZ) / sizeof(BW_OPTIONS_HZ[0]); static const int BW_OPTIONS_COUNT = sizeof(BW_OPTIONS_HZ) / sizeof(BW_OPTIONS_HZ[0]);
static uint8_t config_default_display_rotation() {
#if BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_RAK4631
return 0;
#elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TBEAM_S_V1
return 1;
#else
return 3;
#endif
}
// ─── HTML Page Generation ──────────────────────────────────────────────────── // ─── HTML Page Generation ────────────────────────────────────────────────────
static void config_send_html() { static void config_send_html() {
@@ -87,10 +100,10 @@ static void config_send_html() {
if (cur_txp == 0xFF) cur_txp = PA_MAX_OUTPUT; // Default to board max if (cur_txp == 0xFF) cur_txp = PA_MAX_OUTPUT; // Default to board max
// Default frequency if not set // Default frequency if not set
if (cur_freq == 0) cur_freq = 914875000; // 914.875 MHz default if (cur_freq == 0) cur_freq = 868825000; // 914.875 MHz default
if (cur_bw == 0) cur_bw = 125000; // 125 kHz default if (cur_bw == 0) cur_bw = 125000; // 125 kHz default
if (cur_sf == 0) cur_sf = 10; // SF10 default if (cur_sf == 0) cur_sf = 10; // SF10 default
if (cur_cr < 5 || cur_cr > 8) cur_cr = 5; // CR 4/5 default if (cur_cr < 5 || cur_cr > 8) cur_cr = 7; // CR 4/5 default
// Build the HTML page // Build the HTML page
String html = F( String html = F(
@@ -221,9 +234,9 @@ static void config_send_html() {
} }
html += F("</select>"); html += F("</select>");
// Spreading Factor — dropdown 6-12 // Spreading Factor — dropdown 5-12
html += F("<label>Spreading Factor</label><select name='sf'>"); html += F("<label>Spreading Factor</label><select name='sf'>");
for (int sf = 6; sf <= 12; sf++) { for (int sf = 5; sf <= 12; sf++) {
html += F("<option value='"); html += F("<option value='");
html += String(sf); html += String(sf);
html += "'"; html += "'";
@@ -299,11 +312,16 @@ static void config_send_html() {
); );
// Read current blanking timeout from EEPROM (stored as minutes, 0 = never) // Read current blanking timeout from EEPROM (stored as minutes, 0 = never)
uint8_t cur_blank = 0; uint8_t cur_blank = 5;
if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) {
cur_blank = EEPROM.read(eeprom_addr(ADDR_CONF_DBLK)); cur_blank = EEPROM.read(eeprom_addr(ADDR_CONF_DBLK));
} }
uint8_t cur_rotation = EEPROM.read(eeprom_addr(ADDR_CONF_DROT));
if (cur_rotation > 3) {
cur_rotation = config_default_display_rotation();
}
static const uint8_t blank_vals[] = { 0, 1, 5, 10, 30, 60 }; static const uint8_t blank_vals[] = { 0, 1, 5, 10, 30, 60 };
static const char* blank_labels[] = { "Never", "1 minute", "5 minutes", "10 minutes", "30 minutes", "60 minutes" }; static const char* blank_labels[] = { "Never", "1 minute", "5 minutes", "10 minutes", "30 minutes", "60 minutes" };
static const int blank_count = 6; static const int blank_count = 6;
@@ -320,6 +338,23 @@ static void config_send_html() {
html += F("</select>"); html += F("</select>");
html += F("<p class='note'>Turn off display after inactivity to save power</p>"); html += F("<p class='note'>Turn off display after inactivity to save power</p>");
html += F("<label>Display Orientation</label><select name='disp_rot'>");
html += F("<option value='0'");
if (cur_rotation == 0) html += F(" selected");
html += F(">Landscape</option>");
html += F("<option value='1'");
if (cur_rotation == 1) html += F(" selected");
html += F(">Portrait</option>");
html += F("<option value='2'");
if (cur_rotation == 2) html += F(" selected");
html += F(">Landscape Flipped</option>");
html += F("<option value='3'");
if (cur_rotation == 3) html += F(" selected");
html += F(">Portrait Flipped</option>");
html += F("</select>");
html += F("<p class='note'>Choose the orientation that matches your OLED mounting. "
"Landscape modes place the two status panes side by side; portrait modes stack them.</p>");
// ── Submit ── // ── Submit ──
html += F( html += F(
"<button type='submit'>Save &amp; Reboot</button>" "<button type='submit'>Save &amp; Reboot</button>"
@@ -353,6 +388,14 @@ static void config_handle_save() {
// Set WiFi mode to STA // Set WiFi mode to STA
EEPROM.write(eeprom_addr(ADDR_CONF_WIFI), WR_WIFI_STA); EEPROM.write(eeprom_addr(ADDR_CONF_WIFI), WR_WIFI_STA);
// Boundary mode always uses DHCP on the STA interface. Clear the legacy
// static IP and netmask slots so stale values from older firmware or tools
// cannot force a persistent static address.
for (int i = 0; i < 4; i++) {
EEPROM.write(config_addr(ADDR_CONF_IP + i), 0x00);
EEPROM.write(config_addr(ADDR_CONF_NM + i), 0x00);
}
// ── WiFi enable setting ── // ── WiFi enable setting ──
boundary_state.wifi_enabled = (config_server->arg("wifi_en").toInt() == 1); boundary_state.wifi_enabled = (config_server->arg("wifi_en").toInt() == 1);
@@ -370,6 +413,12 @@ static void config_handle_save() {
eeprom_update(eeprom_addr(ADDR_CONF_DBLK), blank_val); eeprom_update(eeprom_addr(ADDR_CONF_DBLK), blank_val);
} }
int display_rotation = config_server->arg("disp_rot").toInt();
if (display_rotation < 0 || display_rotation > 3) {
display_rotation = config_default_display_rotation();
}
eeprom_update(eeprom_addr(ADDR_CONF_DROT), (uint8_t)display_rotation);
// ── TCP backbone settings ── // ── TCP backbone settings ──
boundary_state.tcp_mode = (uint8_t)config_server->arg("tcp_mode").toInt(); // 0=disabled, 1=client boundary_state.tcp_mode = (uint8_t)config_server->arg("tcp_mode").toInt(); // 0=disabled, 1=client
if (boundary_state.tcp_mode > 1) boundary_state.tcp_mode = 0; if (boundary_state.tcp_mode > 1) boundary_state.tcp_mode = 0;
@@ -421,7 +470,7 @@ static void config_handle_save() {
if (bw_val > 0) lora_bw = bw_val; if (bw_val > 0) lora_bw = bw_val;
int sf_val = config_server->arg("sf").toInt(); int sf_val = config_server->arg("sf").toInt();
if (sf_val >= 6 && sf_val <= 12) lora_sf = sf_val; if (sf_val >= 5 && sf_val <= 12) lora_sf = sf_val;
int cr_val = config_server->arg("cr").toInt(); int cr_val = config_server->arg("cr").toInt();
if (cr_val >= 5 && cr_val <= 8) lora_cr = cr_val; if (cr_val >= 5 && cr_val <= 8) lora_cr = cr_val;
@@ -481,6 +530,14 @@ static void config_handle_redirect() {
// ─── Check if config is needed ─────────────────────────────────────────────── // ─── Check if config is needed ───────────────────────────────────────────────
bool boundary_needs_config() { bool boundary_needs_config() {
// If the RTNode app marker is missing, this node was either never
// configured by RTNode or was flashed from a different firmware family
// such as stock RNode. Force the portal so RTNode can claim and rewrite
// its persisted settings explicitly.
if (!boundary_app_marker_valid()) {
return true;
}
// Check if WiFi SSID is configured // Check if WiFi SSID is configured
char ssid[33]; char ssid[33];
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
@@ -557,6 +614,10 @@ void config_portal_start() {
display.display(); display.display();
} }
#endif #endif
// Headless: LED ramp will be driven from the WCC portal loop
if (headless_mode) {
Serial.println("[Config] Headless mode — LED will breathe during config portal");
}
} }
// ─── Stop Config Portal ────────────────────────────────────────────────────── // ─── Stop Config Portal ──────────────────────────────────────────────────────

View File

@@ -1,9 +1,11 @@
// Copyright (C) 2026, Boundary Mode Extension // Copyright (C) 2026, Boundary Mode Extension
// Based on microReticulum_Firmware by Mark Qvist // Based on microReticulum_Firmware by Mark Qvist
// //
// BoundaryMode.h — Configuration and runtime state for the Boundary Mode // BoundaryMode.h — Configuration and runtime state for the legacy
// firmware variant. This header defines the WiFi backbone connection // "Boundary Mode" firmware variant. Going forward this should be renamed
// parameters and boundary-specific operational settings. // "Transport Mode". It is the only intended operating mode for this fork;
// the old multi-mode split is kept only for compatibility while the codebase
// is being simplified.
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
@@ -15,8 +17,19 @@
#ifdef BOUNDARY_MODE #ifdef BOUNDARY_MODE
// Compatibility alias for the planned rename from Boundary Mode to
// Transport Mode. New code should prefer the transport terminology even
// while the legacy BOUNDARY_MODE compile-time flag still exists.
#ifndef TRANSPORT_MODE
#define TRANSPORT_MODE 1
#endif
// ─── Boundary Mode Configuration ──────────────────────────────────────────── // ─── Boundary Mode Configuration ────────────────────────────────────────────
// //
// NOTE: "Boundary Mode" is the legacy name. This should be relabeled
// "Transport Mode" once the remaining non-transport code paths are removed.
// In practice this is the only supported mode in this firmware branch.
//
// The boundary node operates with TWO RNS interfaces: // The boundary node operates with TWO RNS interfaces:
// //
// 1. LoRaInterface (MODE_GATEWAY) — radio side, handles LoRa mesh // 1. LoRaInterface (MODE_GATEWAY) — radio side, handles LoRa mesh
@@ -72,10 +85,16 @@
#define ADDR_CONF_IFAC_EN 0xD6 // IFAC enable flag (1 byte, 0x73 = enabled) #define ADDR_CONF_IFAC_EN 0xD6 // IFAC enable flag (1 byte, 0x73 = enabled)
#define ADDR_CONF_IFAC_NAME 0xD7 // Network name (33 bytes, null-terminated) #define ADDR_CONF_IFAC_NAME 0xD7 // Network name (33 bytes, null-terminated)
#define ADDR_CONF_IFAC_PASS 0xF8 // Passphrase (33 bytes, null-terminated) #define ADDR_CONF_IFAC_PASS 0xF8 // Passphrase (33 bytes, null-terminated)
// Total: 0x119 (281 bytes — extends beyond 256-byte CONFIG area into #define ADDR_CONF_APP_MARKER0 0x119 // RTNode app marker byte 0
#define ADDR_CONF_APP_MARKER1 0x11A // RTNode app marker byte 1
#define ADDR_CONF_APP_VERSION 0x11B // RTNode app config version
// Total: 0x11C (284 bytes — extends beyond 256-byte CONFIG area into
// unused EEPROM gap; safe on ESP32 where EEPROM starts at 824) // unused EEPROM gap; safe on ESP32 where EEPROM starts at 824)
#define BOUNDARY_ENABLE_BYTE 0x73 #define BOUNDARY_ENABLE_BYTE 0x73
#define BOUNDARY_APP_MARKER0 0x52
#define BOUNDARY_APP_MARKER1 0x54
#define BOUNDARY_APP_VERSION 0x01
// ─── Boundary Mode Runtime State ───────────────────────────────────────────── // ─── Boundary Mode Runtime State ─────────────────────────────────────────────
#ifndef BOUNDARY_STATE_DEFINED #ifndef BOUNDARY_STATE_DEFINED
@@ -115,6 +134,24 @@ extern BoundaryState boundary_state;
// ─── Boundary Mode EEPROM Load/Save ───────────────────────────────────────── // ─── Boundary Mode EEPROM Load/Save ─────────────────────────────────────────
inline bool boundary_app_marker_valid() {
return EEPROM.read(config_addr(ADDR_CONF_APP_MARKER0)) == BOUNDARY_APP_MARKER0 &&
EEPROM.read(config_addr(ADDR_CONF_APP_MARKER1)) == BOUNDARY_APP_MARKER1;
}
inline bool boundary_app_version_matches() {
return boundary_app_marker_valid() &&
EEPROM.read(config_addr(ADDR_CONF_APP_VERSION)) == BOUNDARY_APP_VERSION;
}
inline void boundary_clear_app_marker() {
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER0), 0xFF);
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER1), 0xFF);
EEPROM.write(config_addr(ADDR_CONF_APP_VERSION), 0xFF);
EEPROM.write(config_addr(ADDR_CONF_BMODE), 0xFF);
EEPROM.commit();
}
inline void boundary_load_config() { inline void boundary_load_config() {
// Check if boundary mode is configured // Check if boundary mode is configured
uint8_t bmode = EEPROM.read(config_addr(ADDR_CONF_BMODE)); uint8_t bmode = EEPROM.read(config_addr(ADDR_CONF_BMODE));
@@ -258,6 +295,9 @@ inline void boundary_save_config() {
EEPROM.write(config_addr(ADDR_CONF_IFAC_PASS + i), boundary_state.ifac_passphrase[i]); EEPROM.write(config_addr(ADDR_CONF_IFAC_PASS + i), boundary_state.ifac_passphrase[i]);
} }
EEPROM.write(config_addr(ADDR_CONF_IFAC_PASS + 32), 0x00); EEPROM.write(config_addr(ADDR_CONF_IFAC_PASS + 32), 0x00);
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER0), BOUNDARY_APP_MARKER0);
EEPROM.write(config_addr(ADDR_CONF_APP_MARKER1), BOUNDARY_APP_MARKER1);
EEPROM.write(config_addr(ADDR_CONF_APP_VERSION), BOUNDARY_APP_VERSION);
EEPROM.commit(); EEPROM.commit();
} }

View File

@@ -114,7 +114,14 @@
#define CSMA_CW_PER_BAND_WINDOWS 15 #define CSMA_CW_PER_BAND_WINDOWS 15
#define CSMA_BAND_1_MAX_AIRTIME 7 #define CSMA_BAND_1_MAX_AIRTIME 7
#define CSMA_BAND_N_MIN_AIRTIME 85 #define CSMA_BAND_N_MIN_AIRTIME 85
// Increase threshold for specific boards
#if BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define CSMA_INFR_THRESHOLD_DB 14
#else
#define CSMA_INFR_THRESHOLD_DB 11 #define CSMA_INFR_THRESHOLD_DB 11
#endif
#define CSMA_RFENV_RECAL_MS 2500 #define CSMA_RFENV_RECAL_MS 2500
#define CSMA_RFENV_RECAL_LIMIT_DB -83 #define CSMA_RFENV_RECAL_LIMIT_DB -83
bool interference_detected = false; bool interference_detected = false;
@@ -145,6 +152,7 @@
bool hw_ready = false; bool hw_ready = false;
bool radio_error = false; bool radio_error = false;
bool disp_ready = false; bool disp_ready = false;
bool headless_mode = false;
bool pmu_ready = false; bool pmu_ready = false;
bool promisc = false; bool promisc = false;
bool implicit = false; bool implicit = false;

View File

@@ -97,6 +97,11 @@ extern BoundaryState boundary_state;
#define DISP_ADDR 0x3C #define DISP_ADDR 0x3C
#define SCL_OLED 18 #define SCL_OLED 18
#define SDA_OLED 17 #define SDA_OLED 17
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define DISP_RST -1
#define DISP_ADDR 0x3C
#define SCL_OLED 44
#define SDA_OLED 42
#elif BOARD_MODEL == BOARD_RAK4631 #elif BOARD_MODEL == BOARD_RAK4631
// RAK1921/SSD1306 // RAK1921/SSD1306
#define DISP_RST -1 #define DISP_RST -1
@@ -334,6 +339,8 @@ bool display_init() {
digitalWrite(pin_display_en, HIGH); digitalWrite(pin_display_en, HIGH);
delay(50); delay(50);
Wire.begin(SDA_OLED, SCL_OLED); Wire.begin(SDA_OLED, SCL_OLED);
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
Wire.begin(SDA_OLED, SCL_OLED);
#elif BOARD_MODEL == BOARD_LORA32_V1_0 #elif BOARD_MODEL == BOARD_LORA32_V1_0
int pin_display_en = 16; int pin_display_en = 16;
digitalWrite(pin_display_en, LOW); digitalWrite(pin_display_en, LOW);
@@ -455,6 +462,9 @@ bool display_init() {
#elif BOARD_MODEL == BOARD_HELTEC32_V4 #elif BOARD_MODEL == BOARD_HELTEC32_V4
disp_mode = DISP_MODE_PORTRAIT; disp_mode = DISP_MODE_PORTRAIT;
display.setRotation(1); display.setRotation(1);
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
disp_mode = DISP_MODE_LANDSCAPE;
display.setRotation(0);
#elif BOARD_MODEL == BOARD_HELTEC_T114 #elif BOARD_MODEL == BOARD_HELTEC_T114
disp_mode = DISP_MODE_PORTRAIT; disp_mode = DISP_MODE_PORTRAIT;
display.setRotation(1); display.setRotation(1);

View File

@@ -96,6 +96,9 @@
display.display(); display.display();
} }
#endif #endif
headless_led_fast_blink();
} else if (display_lock_white) {
headless_led_fast_blink();
} }
} }
} }

17
Power.h
View File

@@ -147,6 +147,23 @@ float pmu_temperature = PMU_TEMP_MIN-1;
bool bat_voltage_dropping = false; bool bat_voltage_dropping = false;
float bat_delay_v = 0; float bat_delay_v = 0;
float bat_state_change_v = 0; float bat_state_change_v = 0;
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define BAT_V_MIN 3.05
#define BAT_V_MAX 4.0
#define BAT_V_CHG 4.48
#define BAT_V_FLOAT 4.33
#define BAT_SAMPLES 7
const uint8_t pin_vbat = 1;
const uint8_t pin_ctrl = 37;
float bat_p_samples[BAT_SAMPLES];
float bat_v_samples[BAT_SAMPLES];
uint8_t bat_samples_count = 0;
int bat_discharging_samples = 0;
int bat_charging_samples = 0;
int bat_charged_samples = 0;
bool bat_voltage_dropping = false;
float bat_delay_v = 0;
float bat_state_change_v = 0;
#elif BOARD_MODEL == BOARD_HELTEC_T114 #elif BOARD_MODEL == BOARD_HELTEC_T114
#define BAT_V_MIN 3.15 #define BAT_V_MIN 3.15
#define BAT_V_MAX 4.165 #define BAT_V_MAX 4.165

View File

@@ -2,6 +2,8 @@
A custom firmware for the **Heltec WiFi LoRa 32 V4** (ESP32-S3 + SX1262) that operates as a **Transport Node** — bridging a local LoRa radio network with a remote TCP/IP backbone (such as [rmap.world](https://rmap.world)) over WiFi. A custom firmware for the **Heltec WiFi LoRa 32 V4** (ESP32-S3 + SX1262) that operates as a **Transport Node** — bridging a local LoRa radio network with a remote TCP/IP backbone (such as [rmap.world](https://rmap.world)) over WiFi.
This project was primarily developed with the use of AI assistance.
``` ```
Android / Sideband Remote Android / Sideband Remote
┌──────────┐ ┌────────────┐ Reticulum ┌──────────┐ ┌────────────┐ Reticulum
@@ -52,25 +54,27 @@ This firmware was designed for the **Heltec WiFi LoRa 32 V4**. This board was ch
The easiest way to flash a pre-built firmware. You only need Python 3 and a USB cable. The easiest way to flash a pre-built firmware. You only need Python 3 and a USB cable.
```bash ```bash
# Install esptool (one time)
pip install esptool
# Clone this repo (or download just flash.py + the firmware binary) # Clone this repo (or download just flash.py + the firmware binary)
git clone https://github.com/jrl290/RTNode-HeltecV4.git git clone https://github.com/jrl290/RTNode-HeltecV4.git
cd RTNode-HeltecV4 cd RTNode-HeltecV4
# Download latest firmware from GitHub Releases and flash # Download latest firmware from GitHub Releases and flash
# (auto-detects V3 vs V4 from flash size) # (auto-detects V3 vs V4 from flash size)
python flash.py --download python flash.py
# Optional: use your machine's installed esptool instead of the bundled copy
python flash.py --use-system-esptool
# Or specify board explicitly # Or specify board explicitly
python flash.py --download --board v3 python flash.py --board v3
python flash.py --download --board v4 python flash.py --board v4
# Or flash a local binary # Or flash a local binary
python flash.py --file rtnode_heltec_v4.bin python flash.py --file rtnode_heltec_v4.bin
``` ```
By default, `flash.py` uses the bundled `Release/esptool/esptool.py` for reproducible flashing. Only use `--use-system-esptool` if you explicitly want to override that with a host-installed esptool.
The flash utility auto-detects whether a V3 or V4 is connected by querying the flash size (8MB = V3, 16MB = V4). You can override with `--board v3` or `--board v4`. It will list all available serial ports and prompt you to choose one. If no ports are detected, you may need to hold the **BOOT** button while pressing **RESET** to enter download mode. The flash utility auto-detects whether a V3 or V4 is connected by querying the flash size (8MB = V3, 16MB = V4). You can override with `--board v3` or `--board v4`. It will list all available serial ports and prompt you to choose one. If no ports are detected, you may need to hold the **BOOT** button while pressing **RESET** to enter download mode.
### Option B: Build from Source (PlatformIO) ### Option B: Build from Source (PlatformIO)

View File

@@ -28,6 +28,9 @@
#include "Utilities.h" #include "Utilities.h"
// CBA Boundary Mode // CBA Boundary Mode
// NOTE: Boundary Mode is the legacy name. This firmware branch intends to
// converge on a single Transport Mode, with the BOUNDARY_MODE symbol kept
// temporarily as a compatibility shim during cleanup.
#ifdef BOUNDARY_MODE #ifdef BOUNDARY_MODE
#include "BoundaryMode.h" #include "BoundaryMode.h"
#include "TcpInterface.h" #include "TcpInterface.h"
@@ -243,6 +246,9 @@ TcpInterface* local_tcp_interface_ptr = nullptr;
// RTC memory flag — survives software reset but not power cycle // RTC memory flag — survives software reset but not power cycle
RTC_NOINIT_ATTR uint32_t boundary_config_request; RTC_NOINIT_ATTR uint32_t boundary_config_request;
#define BOUNDARY_CONFIG_MAGIC 0xC0F19A7E #define BOUNDARY_CONFIG_MAGIC 0xC0F19A7E
// RTC flag to skip config portal on next boot (set when user powers off from WCC)
RTC_NOINIT_ATTR uint32_t boundary_skip_config;
#define BOUNDARY_SKIP_MAGIC 0x5E1FC0F0
// Bootloop detection: count rapid reboots in RTC memory. // Bootloop detection: count rapid reboots in RTC memory.
// After BOOTLOOP_THRESHOLD consecutive reboots within BOOTLOOP_WINDOW_MS, // After BOOTLOOP_THRESHOLD consecutive reboots within BOOTLOOP_WINDOW_MS,
@@ -350,7 +356,7 @@ void setup() {
boot_seq(); boot_seq();
#endif #endif
#if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4 && BOARD_MODEL != BOARD_HELTEC32_V3 #if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4 && BOARD_MODEL != BOARD_HELTEC32_V3 && BOARD_MODEL != BOARD_MESHADVENTURER_S3
// Some boards need to wait until the hardware UART is set up before booting // Some boards need to wait until the hardware UART is set up before booting
// the full firmware. In the case of the RAK4631, Heltec T114, and Heltec V3, // the full firmware. In the case of the RAK4631, Heltec T114, and Heltec V3,
// the line below will wait until a serial connection is actually established // the line below will wait until a serial connection is actually established
@@ -473,7 +479,17 @@ void setup() {
display_unblank(); display_unblank();
disp_ready = display_init(); disp_ready = display_init();
if (disp_ready) {
update_display(); update_display();
} else {
headless_mode = true;
Serial.println("[Headless] No display detected — running in headless mode");
}
#endif
// LED solid on at boot for V3/V4 boards (with or without display)
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3
headless_led_solid();
#endif #endif
// ── Boundary Mode: check if config portal is needed ── // ── Boundary Mode: check if config portal is needed ──
@@ -512,22 +528,66 @@ void setup() {
// Enter config mode if: first boot with no config, OR button-triggered reboot, // Enter config mode if: first boot with no config, OR button-triggered reboot,
// OR bootloop detected // OR bootloop detected
bool app_marker_missing = !boundary_app_marker_valid();
bool need_config = boundary_needs_config(); bool need_config = boundary_needs_config();
bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC); bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC);
bool skip_config = (boundary_skip_config == BOUNDARY_SKIP_MAGIC);
boundary_config_request = 0; // Clear flag immediately boundary_config_request = 0; // Clear flag immediately
boundary_skip_config = 0; // Clear skip flag immediately
// Skip flag only suppresses a button-triggered re-entry, not a genuinely
// unconfigured device. If there's no config saved, always show the portal.
if (skip_config && config_requested) {
Serial.println("[Boundary] Skipping config portal — user requested normal boot");
config_requested = false;
}
if (need_config || config_requested || bootloop_detected) { if (need_config || config_requested || bootloop_detected) {
if (bootloop_detected) { if (bootloop_detected) {
Serial.println("[Boundary] Entering config portal due to bootloop recovery"); Serial.println("[Boundary] Entering config portal due to bootloop recovery");
} else if (config_requested) { } else if (config_requested) {
Serial.println("[Boundary] Config mode requested via button hold"); Serial.println("[Boundary] Config mode requested via button hold");
} else if (app_marker_missing) {
Serial.println("[Boundary] RTNode app marker missing — previous firmware was not RTNode or config is unclaimed");
Serial.println("[Boundary] Starting config portal to migrate settings into RTNode");
} else { } else {
Serial.println("[Boundary] No configuration found — starting config portal"); Serial.println("[Boundary] No configuration found — starting config portal");
} }
config_portal_start(); config_portal_start();
// Block here: only run the config portal until user saves and device reboots // Block here: only run the config portal until user saves and device reboots
// Track button state for "off" action (1-3s press = sleep)
bool wcc_btn_down = false;
uint32_t wcc_btn_down_at = 0;
while (config_portal_is_active()) { while (config_portal_is_active()) {
config_portal_loop(); config_portal_loop();
// Headless LED: slow ramp breathe effect during WCC mode
headless_led_ramp();
// Button handling: allow 1-3s press to turn off (deep sleep)
// Next power-on boots to normal mode since boundary_config_request is cleared
#if HAS_INPUT
{
int btn = digitalRead(pin_btn_usr1);
if (btn == LOW && !wcc_btn_down) {
wcc_btn_down = true;
wcc_btn_down_at = millis();
} else if (btn == HIGH && wcc_btn_down) {
uint32_t held = millis() - wcc_btn_down_at;
wcc_btn_down = false;
if (held >= 700 && held <= 5000) {
Serial.println("[Boundary] Button press in WCC mode — powering off");
boundary_skip_config = BOUNDARY_SKIP_MAGIC; // Skip config on next boot
headless_led_off();
config_portal_stop();
#if HAS_SLEEP
sleep_now();
#endif
}
}
}
#endif
#if MCU_VARIANT == MCU_ESP32 #if MCU_VARIANT == MCU_ESP32
esp_task_wdt_reset(); esp_task_wdt_reset();
#endif #endif
@@ -1681,6 +1741,9 @@ void serial_callback(uint8_t sbyte) {
eeprom_conf_save(); eeprom_conf_save();
} else if (command == CMD_CONF_DELETE) { } else if (command == CMD_CONF_DELETE) {
eeprom_conf_delete(); eeprom_conf_delete();
#ifdef BOUNDARY_MODE
boundary_clear_app_marker();
#endif
} else if (command == CMD_FB_EXT) { } else if (command == CMD_FB_EXT) {
#if HAS_DISPLAY == true #if HAS_DISPLAY == true
if (sbyte == 0xFF) { if (sbyte == 0xFF) {
@@ -2511,6 +2574,13 @@ void loop() {
if (disp_ready && !display_updating) update_display(); if (disp_ready && !display_updating) update_display();
#endif #endif
// LED solid when operational on V3/V4 boards (yield to fast blink during white screen)
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3
if (radio_online && !display_lock_white) {
headless_led_solid();
}
#endif
#if HAS_PMU #if HAS_PMU
if (pmu_ready) update_pmu(); if (pmu_ready) update_pmu();
#endif #endif
@@ -2558,6 +2628,8 @@ void sleep_now() {
#endif #endif
#endif #endif
#if BOARD_MODEL == BOARD_HELTEC32_V4 #if BOARD_MODEL == BOARD_HELTEC32_V4
headless_led_off();
headless_led_detach_pwm();
digitalWrite(LORA_PA_CPS, LOW); digitalWrite(LORA_PA_CPS, LOW);
digitalWrite(LORA_PA_CSD, LOW); digitalWrite(LORA_PA_CSD, LOW);
digitalWrite(LORA_PA_PWR_EN, LOW); digitalWrite(LORA_PA_PWR_EN, LOW);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -78,6 +78,13 @@ void wifi_remote_start_ap() {
void wifi_remote_start_sta() { void wifi_remote_start_sta() {
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
#ifdef BOUNDARY_MODE
// Boundary mode does not expose static STA addressing in its config flow.
// Always return the station interface to DHCP so stale legacy EEPROM data
// cannot pin the node to an unintended address.
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
#else
uint8_t ip[4]; bool ip_ok = true; uint8_t ip[4]; bool ip_ok = true;
for (uint8_t i = 0; i < 4; i++) { ip[i] = EEPROM.read(config_addr(ADDR_CONF_IP+i)); } for (uint8_t i = 0; i < 4; i++) { ip[i] = EEPROM.read(config_addr(ADDR_CONF_IP+i)); }
if (ip[0]==0x00 && ip[1]==0x00 && ip[2]==0x00 && ip[3]==0x00) { ip_ok = false; } if (ip[0]==0x00 && ip[1]==0x00 && ip[2]==0x00 && ip[3]==0x00) { ip_ok = false; }
@@ -97,6 +104,7 @@ void wifi_remote_start_sta() {
IPAddress dns2(1, 1, 1, 1); IPAddress dns2(1, 1, 1, 1);
WiFi.config(sta_ip, sta_gw, sta_nm, dns1, dns2); WiFi.config(sta_ip, sta_gw, sta_nm, dns1, dns2);
} }
#endif
delay(100); delay(100);
if (wr_ssid[0] != 0x00) { if (wr_ssid[0] != 0x00) {

View File

@@ -72,6 +72,10 @@ uint8_t eeprom_read(uint32_t mapped_addr);
#endif #endif
#if HAS_INPUT == true #if HAS_INPUT == true
// Forward declarations for headless LED functions (defined later in this file)
void headless_led_fast_blink();
void headless_led_ramp();
void headless_led_off();
#include "Input.h" #include "Input.h"
#endif #endif
@@ -320,6 +324,13 @@ extern RNS::Reticulum reticulum;
void led_tx_off() { digitalWrite(pin_led_tx, LOW); } void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { } void led_id_on() { }
void led_id_off() { } void led_id_off() { }
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
void led_tx_on() { digitalWrite(pin_led_tx, HIGH); }
void led_tx_off() { digitalWrite(pin_led_tx, LOW); }
void led_id_on() { }
void led_id_off() { }
#elif BOARD_MODEL == BOARD_LORA32_V2_1 #elif BOARD_MODEL == BOARD_LORA32_V2_1
void led_rx_on() { digitalWrite(pin_led_rx, HIGH); } void led_rx_on() { digitalWrite(pin_led_rx, HIGH); }
void led_rx_off() { digitalWrite(pin_led_rx, LOW); } void led_rx_off() { digitalWrite(pin_led_rx, LOW); }
@@ -382,6 +393,79 @@ extern RNS::Reticulum reticulum;
#endif #endif
#endif #endif
// ── Headless LED indicators (for Heltec V4 without OLED) ─────────────────
// Uses LEDC PWM for smooth ramp effects on pin_led_tx (GPIO 35)
#if BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == MESHADVENTURER_S3
#define HEADLESS_LED_CHANNEL 0
bool headless_led_pwm_attached = false;
void headless_led_ensure_pwm() {
if (!headless_led_pwm_attached) {
ledcSetup(HEADLESS_LED_CHANNEL, 5000, 8); // channel 0, 5kHz, 8-bit
ledcAttachPin(pin_led_tx, HEADLESS_LED_CHANNEL);
headless_led_pwm_attached = true;
}
}
void headless_led_detach_pwm() {
if (headless_led_pwm_attached) {
ledcDetachPin(pin_led_tx);
headless_led_pwm_attached = false;
pinMode(pin_led_tx, OUTPUT);
}
}
// Solid ON — normal headless operation
void headless_led_solid() {
headless_led_ensure_pwm();
ledcWrite(HEADLESS_LED_CHANNEL, 255);
}
// Fast blink — replaces "white screen" indicator (non-blocking, call from loop)
void headless_led_fast_blink() {
headless_led_ensure_pwm();
static uint32_t last_toggle = 0;
static bool on = false;
uint32_t now = millis();
if (now - last_toggle >= 100) { // 5Hz blink
last_toggle = now;
on = !on;
ledcWrite(HEADLESS_LED_CHANNEL, on ? 255 : 0);
}
}
// Slow ramp in/out — breathe effect for WiFi Captive Configure mode
void headless_led_ramp() {
headless_led_ensure_pwm();
static uint32_t last_step = 0;
static uint8_t brightness = 0;
static int8_t direction = 1;
uint32_t now = millis();
if (now - last_step >= 10) { // ~100 steps/sec, full cycle ~5 seconds
last_step = now;
brightness += direction;
if (brightness >= 255) { brightness = 255; direction = -1; }
if (brightness == 0) { direction = 1; }
ledcWrite(HEADLESS_LED_CHANNEL, brightness);
}
}
void headless_led_off() {
if (headless_led_pwm_attached) {
ledcWrite(HEADLESS_LED_CHANNEL, 0);
} else {
digitalWrite(pin_led_tx, LOW);
}
}
#else
void headless_led_ensure_pwm() {}
void headless_led_detach_pwm() {}
void headless_led_solid() {}
void headless_led_fast_blink() {}
void headless_led_ramp() {}
void headless_led_off() {}
#endif
void hard_reset(void) { void hard_reset(void) {
#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 #if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560
wdt_enable(WDTO_15MS); wdt_enable(WDTO_15MS);
@@ -1696,6 +1780,8 @@ bool eeprom_model_valid() {
if (model == MODEL_C5 || model == MODEL_CA) { if (model == MODEL_C5 || model == MODEL_CA) {
#elif BOARD_MODEL == BOARD_HELTEC32_V4 #elif BOARD_MODEL == BOARD_HELTEC32_V4
if (model == MODEL_C8) { if (model == MODEL_C8) {
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
if (model == MODEL_C8) {
#elif BOARD_MODEL == BOARD_HELTEC_T114 #elif BOARD_MODEL == BOARD_HELTEC_T114
if (model == MODEL_C6 || model == MODEL_C7) { if (model == MODEL_C6 || model == MODEL_C7) {
#elif BOARD_MODEL == BOARD_RAK4631 #elif BOARD_MODEL == BOARD_RAK4631

View File

@@ -121,6 +121,8 @@ def device_provision(env):
env.Execute("rnodeconf --product b1 --model b9 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT")) env.Execute("rnodeconf --product b1 --model b9 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT"))
elif variant in ("heltec32v4", "heltec32v4_local", "heltec32v4_boundary", "heltec32v4_boundary_local"): elif variant in ("heltec32v4", "heltec32v4_local", "heltec32v4_boundary", "heltec32v4_boundary_local"):
env.Execute("rnodeconf --product b1 --model b9 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT")) env.Execute("rnodeconf --product b1 --model b9 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT"))
elif variant in ("meshadventurer_s3_boundary"):
env.Execute("rnodeconf --product f0 --model fe --hwrev 1 --rom " + env.subst("$UPLOAD_PORT"))
elif variant in ("rak4631", "rak4631_local"): elif variant in ("rak4631", "rak4631_local"):
env.Execute("rnodeconf --product 10 --model 12 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT")) env.Execute("rnodeconf --product 10 --model 12 --hwrev 1 --rom " + env.subst("$UPLOAD_PORT"))
elif variant in ("heltec_t114", "heltec_t114_local"): elif variant in ("heltec_t114", "heltec_t114_local"):

View File

@@ -323,8 +323,6 @@ def find_esptool():
usually the newest version), then fall back to the bundled script. usually the newest version), then fall back to the bundled script.
""" """
# 1. pip-installed esptool on PATH # 1. pip-installed esptool on PATH
if shutil.which("esptool.py"):
return ["esptool.py"]
if shutil.which("esptool"): if shutil.which("esptool"):
return ["esptool"] return ["esptool"]
@@ -851,8 +849,8 @@ def flash_firmware(firmware_path, port, esptool_cmd, baud=None,
flash_addr = f"0x{APP_ADDR:x}" flash_addr = f"0x{APP_ADDR:x}"
print(f" Detected: app-only binary -> flash at {flash_addr}") print(f" Detected: app-only binary -> flash at {flash_addr}")
before_arg = "no_reset" if no_reset_before else "default_reset" before_arg = "no_reset" if no_reset_before else "default-reset"
after_arg = "no_reset" if no_hard_reset else "hard_reset" after_arg = "no_reset" if no_hard_reset else "hard-reset"
cmd = esptool_cmd + [ cmd = esptool_cmd + [
"--chip", CHIP, "--chip", CHIP,
@@ -860,14 +858,14 @@ def flash_firmware(firmware_path, port, esptool_cmd, baud=None,
"--baud", baud, "--baud", baud,
"--before", before_arg, "--before", before_arg,
"--after", after_arg, "--after", after_arg,
"write_flash", "write-flash",
"-z", "-z",
"--flash_mode", mode, "--flash-mode", mode,
"--flash_freq", FLASH_FREQ, "--flash-freq", FLASH_FREQ,
"--flash_size", flash_size, "--flash-size", flash_size,
] ]
if verify: if verify:
cmd.append("--verify") cmd.append("--no-diff-verify")
cmd += [flash_addr, firmware_path] cmd += [flash_addr, firmware_path]
print("Running: " + " ".join(cmd[-8:])) print("Running: " + " ".join(cmd[-8:]))

View File

@@ -9,7 +9,6 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
; Change source and include directories to root of project since RNode places them here
include_dir = . include_dir = .
src_dir = . src_dir = .
@@ -19,32 +18,18 @@ monitor_speed = 115200
upload_speed = 460800 upload_speed = 460800
build_flags = build_flags =
-Wall -Wall
;-Wextra
-Wno-missing-field-initializers -Wno-missing-field-initializers
-Wno-format -Wno-format
-I. -I.
; CBA Define following to disable DEBUG build
;-DNDEBUG
; CBA Define following to include RNS stack
-DHAS_RNS -DHAS_RNS
-DRNS_USE_FS -DRNS_USE_FS
-DRNS_PERSIST_PATHS -DRNS_PERSIST_PATHS
-DMSGPACK_USE_BOOST=OFF -DMSGPACK_USE_BOOST=OFF
; CBA Define following to disable LFS asserts
;-DLFS_NO_ASSERT
; ???
;-DLFS_YES_TRACE
; ???
;-DCORE_DEBUG_LEVEL=5
; ??? NO
;-DLOG_LOCAL_LEVEL=5
;-DCONFIG_LOG_DEFAULT_LEVEL=5
lib_deps = lib_deps =
ArduinoJson@^7.4.2 ArduinoJson@^7.4.2
MsgPack@^0.4.2 MsgPack@^0.4.2
adafruit/Adafruit SSD1306@^2.5.9 adafruit/Adafruit SSD1306@^2.5.9
https://github.com/attermann/Crypto.git https://github.com/attermann/Crypto.git
; Exclude directories in root from sources
build_src_filter = +<*> -<variants/> build_src_filter = +<*> -<variants/>
extra_scripts = pre:extra_script.py extra_scripts = pre:extra_script.py
@@ -60,7 +45,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.12.0 adafruit/Adafruit NeoPixel@^1.15.4
[env:rnode-ng-21] [env:rnode-ng-21]
platform = espressif32 platform = espressif32
@@ -74,7 +59,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.12.0 adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-t-beam] [env:ttgo-t-beam]
platform = espressif32 platform = espressif32
@@ -88,6 +73,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-t-beam-sx1262] [env:ttgo-t-beam-sx1262]
platform = espressif32 platform = espressif32
@@ -102,6 +88,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-t-beam-supreme] [env:ttgo-t-beam-supreme]
platform = espressif32 platform = espressif32
@@ -118,6 +105,7 @@ lib_deps =
XPowersLib@^0.2.1 XPowersLib@^0.2.1
Adafruit_SH110X Adafruit_SH110X
adafruit/Adafruit SH110X@^2.1.14 adafruit/Adafruit SH110X@^2.1.14
adafruit/Adafruit NeoPixel@^1.15.4
[env:lilygo-t3-s3] [env:lilygo-t3-s3]
platform = espressif32 platform = espressif32
@@ -132,6 +120,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:lilygo-t3-s3-sx127x] [env:lilygo-t3-s3-sx127x]
platform = espressif32 platform = espressif32
@@ -146,6 +135,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:lilygo-t3-s3-sx1280-pa] [env:lilygo-t3-s3-sx1280-pa]
platform = espressif32 platform = espressif32
@@ -160,17 +150,16 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:lilygo-t-deck] [env:lilygo-t-deck]
platform = espressif32 platform = espressif32
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
custom_variant = tdeck custom_variant = tdeck
board_build.filesystem = littlefs board_build.filesystem = littlefs
; Flash / memory layout
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216 board_upload.maximum_size = 16777216
board_build.partitions = default_16MB.csv board_build.partitions = default_16MB.csv
; Enable PSRAM + correct flash mode
board_build.flash_mode = qio board_build.flash_mode = qio
board_build.psram_type = opi board_build.psram_type = opi
board_build.arduino.memory_type = qio_opi board_build.arduino.memory_type = qio_opi
@@ -180,13 +169,11 @@ build_flags =
-DBOARD_HAS_PSRAM=1 -DBOARD_HAS_PSRAM=1
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
-DCORE_DEBUG_LEVEL=1 -DCORE_DEBUG_LEVEL=1
; Enable UARDUINO_ USB_ CDC_ ON_ BOOT will start printing and wait for terminal access during startup
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
; Enable UARDUINO_USB_CDC_ON_BOOT will turn off printing and will not block when using the battery
; -UARDUINO_USB_CDC_ON_BOOT
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v1] [env:ttgo-lora32-v1]
platform = espressif32 platform = espressif32
@@ -200,6 +187,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v2] [env:ttgo-lora32-v2]
platform = espressif32 platform = espressif32
@@ -213,6 +201,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v2-extled] [env:ttgo-lora32-v2-extled]
platform = espressif32 platform = espressif32
@@ -227,6 +216,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v21] [env:ttgo-lora32-v21]
platform = espressif32 platform = espressif32
@@ -240,6 +230,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v21-extled] [env:ttgo-lora32-v21-extled]
platform = espressif32 platform = espressif32
@@ -254,6 +245,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-lora32-v21-tcxo] [env:ttgo-lora32-v21-tcxo]
platform = espressif32 platform = espressif32
@@ -268,6 +260,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:heltec_wifi_lora_32_V2] [env:heltec_wifi_lora_32_V2]
platform = espressif32 platform = espressif32
@@ -281,6 +274,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:heltec_wifi_lora_32_V2-extled] [env:heltec_wifi_lora_32_V2-extled]
platform = espressif32 platform = espressif32
@@ -295,6 +289,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:heltec_wifi_lora_32_V3] [env:heltec_wifi_lora_32_V3]
platform = espressif32 platform = espressif32
@@ -308,16 +303,13 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:heltec_V3_boundary] [env:heltec_V3_boundary]
platform = espressif32 platform = espressif32
board = heltec_wifi_lora_32_V3 board = heltec_wifi_lora_32_V3
custom_variant = heltec32v3 custom_variant = heltec32v3
board_build.filesystem = littlefs board_build.filesystem = littlefs
; Flash / memory layout for 8MB flash
; PSRAM: V3 ESP32-S3FN8 has NO PSRAM — firmware detects this at runtime
; and falls back to internal SRAM for TLSF pool.
; BOARD_HAS_PSRAM tells Arduino to *attempt* psramInit(); harmless if absent.
board_upload.flash_size = 8MB board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608 board_upload.maximum_size = 8388608
board_build.partitions = default_8MB.csv board_build.partitions = default_8MB.csv
@@ -330,17 +322,14 @@ build_flags =
-DBOARD_MODEL=BOARD_HELTEC32_V3 -DBOARD_MODEL=BOARD_HELTEC32_V3
-DBOARD_HAS_PSRAM=1 -DBOARD_HAS_PSRAM=1
-DBOUNDARY_MODE -DBOUNDARY_MODE
;-DNDEBUG
-DRNS_USE_TLSF=1 -DRNS_USE_TLSF=1
-DRNS_USE_ALLOCATOR=1 -DRNS_USE_ALLOCATOR=1
; --- Boundary mode defaults (override via EEPROM at runtime) ---
; TCP server mode (0=server, 1=client)
-DBOUNDARY_TCP_MODE=0 -DBOUNDARY_TCP_MODE=0
; TCP listen/connect port
-DBOUNDARY_TCP_PORT=4242 -DBOUNDARY_TCP_PORT=4242
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:heltec_wifi_lora_32_V4] [env:heltec_wifi_lora_32_V4]
@@ -356,13 +345,41 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:meshadventurer_S3_boundary]
platform = espressif32
board = esp32-s3-devkitc-1
custom_variant = meshadventurer_s3_boundary
board_build.filesystem = littlefs
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.partitions = default_16MB.csv
board_build.flash_mode = qio
board_build.psram_type = qio
board_build.arduino.memory_type = qio_qspi
monitor_speed = 921600
build_flags =
${env.build_flags}
-DBOARD_MODEL=BOARD_MESHADVENTURER_S3
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM=1
-DBOUNDARY_MODE
-DRNS_USE_TLSF=1
-DRNS_USE_ALLOCATOR=1
-DBOUNDARY_TCP_MODE=0
-DBOUNDARY_TCP_PORT=4242
lib_deps =
${env.lib_deps}
XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder
[env:heltec_V4_boundary] [env:heltec_V4_boundary]
platform = espressif32 platform = espressif32
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
custom_variant = heltec32v4_boundary custom_variant = heltec32v4_boundary
board_build.filesystem = littlefs board_build.filesystem = littlefs
; Flash / memory layout for 16MB flash + 2MB PSRAM
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216 board_upload.maximum_size = 16777216
board_build.partitions = default_16MB.csv board_build.partitions = default_16MB.csv
@@ -376,20 +393,14 @@ build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM=1 -DBOARD_HAS_PSRAM=1
-DBOUNDARY_MODE -DBOUNDARY_MODE
;-DNDEBUG
-DRNS_USE_TLSF=1 -DRNS_USE_TLSF=1
-DRNS_USE_ALLOCATOR=1 -DRNS_USE_ALLOCATOR=1
; --- Boundary mode defaults (override via EEPROM at runtime) ---
; TCP server mode (0=server, 1=client)
-DBOUNDARY_TCP_MODE=0 -DBOUNDARY_TCP_MODE=0
; TCP listen/connect port
-DBOUNDARY_TCP_PORT=4242 -DBOUNDARY_TCP_PORT=4242
; Backbone host for client mode (empty = server mode)
; -DBOUNDARY_BACKBONE_HOST=\"192.168.1.100\"
; -DBOUNDARY_BACKBONE_PORT=4242
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:heltec_V4_boundary-local] [env:heltec_V4_boundary-local]
@@ -414,6 +425,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:featheresp32] [env:featheresp32]
@@ -428,6 +440,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:seeed_xiao_esp32s3] [env:seeed_xiao_esp32s3]
platform = espressif32 platform = espressif32
@@ -441,6 +454,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:generic-esp32] [env:generic-esp32]
platform = espressif32 platform = espressif32
@@ -454,6 +468,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
[env:wiscore_rak4631] [env:wiscore_rak4631]
platform = nordicnrf52 platform = nordicnrf52
@@ -471,8 +486,7 @@ build_flags =
-DRNS_USE_ALLOCATOR=1 -DRNS_USE_ALLOCATOR=1
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.4
[env:ttgo-t-beam-local] [env:ttgo-t-beam-local]
platform = espressif32 platform = espressif32
@@ -485,12 +499,11 @@ build_flags =
${env.build_flags} ${env.build_flags}
-fexceptions -fexceptions
-DBOARD_MODEL=BOARD_TBEAM -DBOARD_MODEL=BOARD_TBEAM
; CBA TEST
;-DUSE_FLASHFS=1
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:ttgo-lora32-v21-local] [env:ttgo-lora32-v21-local]
@@ -506,6 +519,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:heltec_wifi_lora_32_V4-local] [env:heltec_wifi_lora_32_V4-local]
@@ -521,6 +535,7 @@ build_flags =
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
XPowersLib@^0.2.1 XPowersLib@^0.2.1
adafruit/Adafruit NeoPixel@^1.15.4
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
[env:wiscore_rak4631-local] [env:wiscore_rak4631-local]
@@ -535,17 +550,15 @@ build_flags =
-I variants/rak4630 -I variants/rak4630
-fexceptions -fexceptions
-DBOARD_MODEL=BOARD_RAK4631 -DBOARD_MODEL=BOARD_RAK4631
; CBA TEST
-DRNS_USE_TLSF=1 -DRNS_USE_TLSF=1
-DRNS_USE_ALLOCATOR=1 -DRNS_USE_ALLOCATOR=1
;-DUSE_FLASHFS=1
build_unflags = -fno-exceptions build_unflags = -fno-exceptions
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash Adafruit_SPIFlash=symlink://../Adafruit_SPIFlash
adafruit/Adafruit NeoPixel@^1.15.4
[env:heltec_t114_local] [env:heltec_t114_local]
;upload_port = /dev/cu.usbmodem1101
platform = nordicnrf52 platform = nordicnrf52
board = nrf52840_dk_adafruit board = nrf52840_dk_adafruit
custom_variant = heltec_t114_local custom_variant = heltec_t114_local
@@ -555,11 +568,10 @@ build_flags =
${env.build_flags} ${env.build_flags}
-fexceptions -fexceptions
-DBOARD_MODEL=BOARD_HELTEC_T114 -DBOARD_MODEL=BOARD_HELTEC_T114
; CBA TEST
-DRNS_USE_TLSF=1 -DRNS_USE_TLSF=1
-DRNS_USE_ALLOCATOR=1 -DRNS_USE_ALLOCATOR=1
build_unflags = -fno-exceptions build_unflags = -fno-exceptions
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95 https://github.com/liamcottle/esp8266-oled-ssd1306#e16cee124fe26490cb14880c679321ad8ac89c95
adafruit/Adafruit NeoPixel@^1.12.0 adafruit/Adafruit NeoPixel@^1.15.4

View File

@@ -126,7 +126,7 @@ bool sx126x::preInit() {
pinMode(_ss, OUTPUT); pinMode(_ss, OUTPUT);
digitalWrite(_ss, HIGH); digitalWrite(_ss, HIGH);
#if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_MESHADVENTURER_S3
SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs);
#elif BOARD_MODEL == BOARD_TECHO #elif BOARD_MODEL == BOARD_TECHO
SPI.setPins(pin_miso, pin_sclk, pin_mosi); SPI.setPins(pin_miso, pin_sclk, pin_mosi);
@@ -643,6 +643,8 @@ void sx126x::enableTCXO() {
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_HELTEC32_V4 #elif BOARD_MODEL == BOARD_HELTEC32_V4
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF};
#endif #endif
executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4); executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4);
#endif #endif