Compare commits

...

22 Commits

Author SHA1 Message Date
4f10ee8329 Switch to espressif32@6.12.0 2026-04-26 22:02:55 +03:00
17a99ddd54 Pinned espressif32 platform to 6.5.0 as a workaround for compilation issues 2026-04-26 15:11:43 +03:00
eb827f5a60 Added Heltec V4.3 support/FEM autodetection 2026-04-26 15:10:54 +03:00
bc60028852 Added Heltec V4.3 support/FEM autodetection 2026-04-26 14:08:23 +03:00
0ef379595f Updated version 2026-04-26 14:07:17 +03:00
185d15cbaf Removed log 2026-04-26 13:30:38 +03:00
b728ca3c75 Add TXEN pin control 2026-04-26 13:29:54 +03:00
c15afa9819 Add TXEN pin control 2026-04-26 13:29:54 +03:00
192a008150 I2C pin change for beta board 2026-04-26 13:29:54 +03:00
c827989f43 Ignore built firmware binaries 2026-04-26 13:29:54 +03:00
6115cd6b84 Merge stashed changes; use upstream flash.py 2026-04-26 13:29:50 +03:00
James LaFarge
c84006da8a chore: checkpoint current changes 2026-04-16 12:01:05 -04:00
James L
3157b8e4a8 v1.0.23: Show node public hash in WiFi config portal
Add Reticulum destination hash indicator at the top of the captive-portal
config page so users can identify the device.

- Store the 32-char hex destination hash in RTC_NOINIT memory after RNS
  starts on a normal boot (survives software reboots into config mode)
- BoundaryConfig.h reads the RTC value and renders a styled hash box at
  the top of the HTML page, above the config form
- Falls back to a friendly placeholder if the device has never completed
  a normal boot (hash not yet assigned)
- Rebuild precompiled firmware for Heltec V3 and V4 boundary variants
2026-03-20 23:08:03 -04: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
24 changed files with 719 additions and 3018 deletions

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ Console/build
build/* build/*
.pio/* .pio/*
.vscode/* .vscode/*
Release/*.bin

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
@@ -149,6 +151,10 @@
#endif #endif
#endif #endif
#define LORA_PA_UNKNOWN 0x00
#define LORA_PA_GC1109 0x01
#define LORA_PA_KCT8103L 0x02
#define HAS_DISPLAY false #define HAS_DISPLAY false
#define HAS_BLUETOOTH false #define HAS_BLUETOOTH false
#define HAS_BLE false #define HAS_BLE false
@@ -361,7 +367,7 @@
#define HAS_SLEEP true #define HAS_SLEEP true
#define PIN_WAKEUP GPIO_NUM_0 #define PIN_WAKEUP GPIO_NUM_0
#define WAKEUP_LEVEL 0 #define WAKEUP_LEVEL 0
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
const int pin_btn_usr1 = 0; const int pin_btn_usr1 = 0;
@@ -409,8 +415,9 @@
#define HAS_LORA_LNA true #define HAS_LORA_LNA true
#define PIN_WAKEUP GPIO_NUM_0 #define PIN_WAKEUP GPIO_NUM_0
#define WAKEUP_LEVEL 0 #define WAKEUP_LEVEL 0
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
#define Vext GPIO_NUM_36 #define Vext GPIO_NUM_36
#define LORA_PA_MODEL LORA_PA_UNKNOWN;
const int pin_btn_usr1 = 0; const int pin_btn_usr1 = 0;
@@ -432,14 +439,17 @@
#define LORA_LNA_GAIN 17 #define LORA_LNA_GAIN 17
#define LORA_LNA_GVT 12 #define LORA_LNA_GVT 12
#define LORA_PA_GC1109 true
#define LORA_PA_PWR_EN 7 #define LORA_PA_PWR_EN 7
#define LORA_PA_CSD 2 #define LORA_PA_CSD 2 // Same pin on GC1109
#define LORA_PA_CPS 46 #define LORA_PA_CPS 46 // Same pin on GC1109
#define LORA_PA_CTX 5 // Only used on KCT8103
#define PA_MAX_OUTPUT 28 #define PA_MAX_OUTPUT 28
#define PA_GAIN_POINTS 22 #define PA_GAIN_POINTS 22
#define PA_GAIN_VALUES 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#define LORA_LNA_KCT8103L_GAIN 21
const int PA_GC1109_VALUES[PA_GAIN_POINTS] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
const int PA_KCT8103L_VALUES[PA_GAIN_POINTS] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
const int pin_cs = 8; const int pin_cs = 8;
const int pin_busy = 13; const int pin_busy = 13;
@@ -449,6 +459,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
@@ -483,13 +537,10 @@
const int pin_np = 12; const int pin_np = 12;
const int pin_dac = 25; const int pin_dac = 25;
const int pin_adc = 34; const int pin_adc = 34;
// CBA already defined by framework const int SD_MISO = 2;
//const int SD_MISO = 2; const int SD_MOSI = 15;
// CBA already defined by framework
//const int SD_MOSI = 15;
const int SD_CLK = 14; const int SD_CLK = 14;
// CBA already defined by framework const int SD_CS = 13;
//const int SD_CS = 13;
#if HAS_NP == false #if HAS_NP == false
#if defined(EXTERNAL_LEDS) #if defined(EXTERNAL_LEDS)
const int pin_led_rx = 12; const int pin_led_rx = 12;
@@ -934,7 +985,7 @@
// Default OCP value if not specified // Default OCP value if not specified
// in board configuration // in board configuration
#ifndef OCP_TUNED #ifndef OCP_TUNED
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
#endif #endif
#ifndef PA_MAX_OUTPUT #ifndef PA_MAX_OUTPUT

97
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,
@@ -21,6 +24,11 @@
#include <WebServer.h> #include <WebServer.h>
#include <DNSServer.h> #include <DNSServer.h>
// ─── Node hash (cached in RTC by normal boot, read here without starting RNS) ─
#define NODE_HASH_RTC_MAGIC 0x504B4841UL
extern uint32_t rtc_node_hash_magic;
extern char rtc_node_hash_hex[33];
// ─── Config Portal State ───────────────────────────────────────────────────── // ─── Config Portal State ─────────────────────────────────────────────────────
static bool config_portal_active = false; static bool config_portal_active = false;
static WebServer* config_server = nullptr; static WebServer* config_server = nullptr;
@@ -59,6 +67,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 +105,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(
@@ -113,12 +131,25 @@ static void config_send_html() {
"button:hover{background:#c73e54;}" "button:hover{background:#c73e54;}"
".ok{background:#16213e;padding:20px;border-radius:8px;text-align:center;}" ".ok{background:#16213e;padding:20px;border-radius:8px;text-align:center;}"
".ok h1{color:#0f0;}" ".ok h1{color:#0f0;}"
".node-hash{background:#0f1a30;border:1px solid #0f3460;border-radius:6px;"
"padding:10px 14px;margin:0 0 16px;}"
".node-hash .nh-label{display:block;font-size:0.75em;color:#888;margin-bottom:4px;}"
".node-hash code{font-family:monospace;font-size:0.95em;color:#7ecfff;"
"word-break:break-all;letter-spacing:0.05em;}"
"</style></head><body>" "</style></head><body>"
"<h1>&#x1f4e1; RNode Boundary Node</h1>" "<h1>&#x1f4e1; RNode Boundary Node</h1>"
"<form method='POST' action='/save'>"
); );
// ── WiFi STA Section ── // ── Node public hash ──
html += F("<div class='node-hash'><span class='nh-label'>&#x1f511; Node Hash (Reticulum destination)</span><code>");
if (rtc_node_hash_magic == NODE_HASH_RTC_MAGIC && rtc_node_hash_hex[0] != '\0') {
html += String(rtc_node_hash_hex);
} else {
html += F("<span style='color:#888;font-style:italic;'>Not yet assigned &mdash; will be set on first normal boot</span>");
}
html += F("</code></div>");
html += F("<form method='POST' action='/save'>");
html += F( html += F(
"<h2>&#x1f4f6; WiFi Network</h2>" "<h2>&#x1f4f6; WiFi Network</h2>"
"<label>WiFi</label>" "<label>WiFi</label>"
@@ -221,9 +252,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 +330,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 +356,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 +406,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 +431,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 +488,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 +548,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 +632,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

@@ -20,7 +20,7 @@
#define CONFIG_H #define CONFIG_H
#define MAJ_VERS 0x01 #define MAJ_VERS 0x01
#define MIN_VERS 0x55 #define MIN_VERS 0x56
#define MODE_HOST 0x11 #define MODE_HOST 0x11
#define MODE_TNC 0x12 #define MODE_TNC 0x12
@@ -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
#define CSMA_INFR_THRESHOLD_DB 11
// Increase threshold for specific boards
#if BOARD_MODEL == BOARD_MESHADVENTURER_S3
#define CSMA_INFR_THRESHOLD_DB 14
#else
#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 0
#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,
@@ -253,6 +259,13 @@ RTC_NOINIT_ATTR uint32_t boundary_config_request;
RTC_NOINIT_ATTR uint32_t bootloop_magic; RTC_NOINIT_ATTR uint32_t bootloop_magic;
RTC_NOINIT_ATTR uint32_t bootloop_count; RTC_NOINIT_ATTR uint32_t bootloop_count;
RTC_NOINIT_ATTR uint32_t bootloop_first_boot_ms; RTC_NOINIT_ATTR uint32_t bootloop_first_boot_ms;
// Node public hash — cached in RTC so the config portal can display it without
// needing to start RNS. Populated after the transport destination is created
// on a normal boot; survives software reboots into the captive portal.
#define NODE_HASH_RTC_MAGIC 0x504B4841UL // "PKHA"
RTC_NOINIT_ATTR uint32_t rtc_node_hash_magic;
RTC_NOINIT_ATTR char rtc_node_hash_hex[33]; // 32 hex chars + NUL
#endif #endif
#endif // HAS_RNS #endif // HAS_RNS
@@ -350,7 +363,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
@@ -398,7 +411,7 @@ void setup() {
#if MODEM == SX1276 || MODEM == SX1278 #if MODEM == SX1276 || MODEM == SX1278
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy); LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy);
#elif MODEM == SX1262 #elif MODEM == SX1262
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen); LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen);
#elif MODEM == SX1280 #elif MODEM == SX1280
LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen); LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen);
#endif #endif
@@ -473,7 +486,17 @@ void setup() {
display_unblank(); display_unblank();
disp_ready = display_init(); disp_ready = display_init();
update_display(); if (disp_ready) {
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 +535,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
@@ -867,6 +934,17 @@ void setup() {
*/ */
RNS::Destination destination(RNS::Transport::identity(), RNS::Type::Destination::IN, RNS::Type::Destination::SINGLE, "rnstransport", "local"); RNS::Destination destination(RNS::Transport::identity(), RNS::Type::Destination::IN, RNS::Type::Destination::SINGLE, "rnstransport", "local");
// Cache this node's destination hash in RTC memory so the captive-portal
// config page can show it without needing RNS to be running.
{
std::string h = destination.hash().toHex();
size_t len = h.length();
if (len > 32) len = 32;
memcpy(rtc_node_hash_hex, h.c_str(), len);
rtc_node_hash_hex[len] = '\0';
rtc_node_hash_magic = NODE_HASH_RTC_MAGIC;
}
HEAD("RNS is READY!", RNS::LOG_TRACE); HEAD("RNS is READY!", RNS::LOG_TRACE);
#ifdef BOUNDARY_MODE #ifdef BOUNDARY_MODE
HEAD("*** BOUNDARY MODE ACTIVE ***", RNS::LOG_TRACE); HEAD("*** BOUNDARY MODE ACTIVE ***", RNS::LOG_TRACE);
@@ -1508,7 +1586,7 @@ void serial_callback(uint8_t sbyte) {
if (txp > 13) txp = 13; if (txp > 13) txp = 13;
#endif #endif
#else #else
if (txp > 17) txp = 17; if (txp > 20) txp = 20;
#endif #endif
lora_txp = txp; lora_txp = txp;
@@ -1681,6 +1759,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 +2592,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,7 +2646,15 @@ void sleep_now() {
#endif #endif
#endif #endif
#if BOARD_MODEL == BOARD_HELTEC32_V4 #if BOARD_MODEL == BOARD_HELTEC32_V4
digitalWrite(LORA_PA_CPS, LOW); headless_led_off();
headless_led_detach_pwm();
#if LORA_PA_AUTO_DETECT
if (sx126x_modem.isKCT8103L()) {
digitalWrite(LORA_PA_CTX, LOW);
} else {
digitalWrite(LORA_PA_CPS, LOW);
}
#endif
digitalWrite(LORA_PA_CSD, LOW); digitalWrite(LORA_PA_CSD, LOW);
digitalWrite(LORA_PA_PWR_EN, LOW); digitalWrite(LORA_PA_PWR_EN, LOW);
digitalWrite(Vext, HIGH); digitalWrite(Vext, HIGH);

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);
@@ -1299,11 +1383,33 @@ int getTxPower() {
} }
#if HAS_LORA_PA #if HAS_LORA_PA
#if BOARD_MODEL == BOARD_HELTEC32_V4
bool pa_values_determined = false;
int tx_gain[PA_GAIN_POINTS] = {100};
#else
bool pa_values_determined = true;
const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES}; const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES};
#endif
#endif #endif
extern uint8_t lora_pa_model;
void determine_pa_values() {
#if BOARD_MODEL == BOARD_HELTEC32_V4
if (lora_pa_model == LORA_PA_GC1109) {
for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_GC1109_VALUES[i]; }
pa_values_determined = true;
for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); }
} else if (lora_pa_model == LORA_PA_KCT8103L) {
for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_KCT8103L_VALUES[i]; }
pa_values_determined = true;
for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); }
}
#endif
}
int map_target_power_to_modem_output(int target_tx_power) { int map_target_power_to_modem_output(int target_tx_power) {
#if HAS_LORA_PA #if HAS_LORA_PA
if (!pa_values_determined) { determine_pa_values(); }
int modem_output_dbm = -9; int modem_output_dbm = -9;
for (int i = 0; i < PA_GAIN_POINTS; i++) { for (int i = 0; i < PA_GAIN_POINTS; i++) {
int gain = tx_gain[i]; int gain = tx_gain[i];
@@ -1696,10 +1802,12 @@ 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_HELTEC_T114 #elif BOARD_MODEL == BOARD_MESHADVENTURER_S3
if (model == MODEL_C6 || model == MODEL_C7) { if (model == MODEL_C8) {
#elif BOARD_MODEL == BOARD_RAK4631 #elif BOARD_MODEL == BOARD_HELTEC_T114
if (model == MODEL_11 || model == MODEL_12) { if (model == MODEL_C6 || model == MODEL_C7) {
#elif BOARD_MODEL == BOARD_RAK4631
if (model == MODEL_11 || model == MODEL_12) {
#elif BOARD_MODEL == BOARD_HUZZAH32 #elif BOARD_MODEL == BOARD_HUZZAH32
if (model == MODEL_FF) { if (model == MODEL_FF) {
#elif BOARD_MODEL == BOARD_GENERIC_ESP32 #elif BOARD_MODEL == BOARD_GENERIC_ESP32

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:]))

0
lib/microReticulum/library.properties Normal file → Executable file
View File

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,37 +18,23 @@ 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
[env:rnode-ng-20] [env:rnode-ng-20]
platform = espressif32 platform = espressif32@6.12.0
board = ttgo-lora32-v2 board = ttgo-lora32-v2
custom_variant = ng20 custom_variant = ng20
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -60,10 +45,10 @@ 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@6.12.0
board = ttgo-lora32-v21 board = ttgo-lora32-v21
custom_variant = ng21 custom_variant = ng21
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -74,10 +59,10 @@ 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@6.12.0
board = ttgo-t-beam board = ttgo-t-beam
custom_variant = tbeam custom_variant = tbeam
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -88,9 +73,10 @@ 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@6.12.0
board = ttgo-t-beam board = ttgo-t-beam
custom_variant = tbeam_sx1262 custom_variant = tbeam_sx1262
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -102,9 +88,10 @@ 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@6.12.0
board = ttgo-t-beam board = ttgo-t-beam
custom_variant = tbeam_supreme custom_variant = tbeam_supreme
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -118,9 +105,10 @@ 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@6.12.0
board = lilygo-t3-s3 board = lilygo-t3-s3
custom_variant = t3s3 custom_variant = t3s3
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -132,9 +120,10 @@ 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@6.12.0
board = lilygo-t3-s3 board = lilygo-t3-s3
custom_variant = t3s3_sx127x custom_variant = t3s3_sx127x
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -146,9 +135,10 @@ 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@6.12.0
board = lilygo-t3-s3 board = lilygo-t3-s3
custom_variant = t3s3_sx1280_pa custom_variant = t3s3_sx1280_pa
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -160,36 +150,33 @@ 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@6.12.0
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
build_flags = build_flags =
${env.build_flags} ${env.build_flags}
-DBOARD_MODEL=BOARD_TDECK -DBOARD_MODEL=BOARD_TDECK
-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@6.12.0
board = ttgo-lora32-v1 board = ttgo-lora32-v1
custom_variant = lora32v10 custom_variant = lora32v10
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -200,9 +187,10 @@ 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@6.12.0
board = ttgo-lora32-v2 board = ttgo-lora32-v2
custom_variant = lora32v20 custom_variant = lora32v20
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -213,9 +201,10 @@ 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@6.12.0
board = ttgo-lora32-v2 board = ttgo-lora32-v2
custom_variant = lora32v20_extled custom_variant = lora32v20_extled
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -227,9 +216,10 @@ 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@6.12.0
board = ttgo-lora32-v21 board = ttgo-lora32-v21
custom_variant = lora32v21 custom_variant = lora32v21
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -240,9 +230,10 @@ 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@6.12.0
board = ttgo-lora32-v21 board = ttgo-lora32-v21
custom_variant = lora32v21_extled custom_variant = lora32v21_extled
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -254,9 +245,10 @@ 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@6.12.0
board = ttgo-lora32-v21 board = ttgo-lora32-v21
custom_variant = lora32v21_extled custom_variant = lora32v21_extled
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -268,9 +260,10 @@ 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@6.12.0
board = heltec_wifi_lora_32_V2 board = heltec_wifi_lora_32_V2
custom_variant = heltec32v2 custom_variant = heltec32v2
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -281,9 +274,10 @@ 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@6.12.0
board = heltec_wifi_lora_32_V2 board = heltec_wifi_lora_32_V2
custom_variant = heltec32v2_extled custom_variant = heltec32v2_extled
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -295,9 +289,10 @@ 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@6.12.0
board = heltec_wifi_lora_32_V3 board = heltec_wifi_lora_32_V3
custom_variant = heltec32v3 custom_variant = heltec32v3
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -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@6.12.0
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,21 +322,18 @@ 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]
platform = espressif32 platform = espressif32@6.12.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
custom_variant = heltec32v4 custom_variant = heltec32v4
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -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@6.12.0
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@6.12.0
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,24 +393,18 @@ 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]
platform = espressif32 platform = espressif32@6.12.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
custom_variant = heltec32v4_boundary_local custom_variant = heltec32v4_boundary_local
board_build.filesystem = littlefs board_build.filesystem = littlefs
@@ -414,10 +425,11 @@ 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]
platform = espressif32 platform = espressif32@6.12.0
board = featheresp32 board = featheresp32
custom_variant = featheresp32 custom_variant = featheresp32
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -428,9 +440,10 @@ 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@6.12.0
board = seeed_xiao_esp32s3 board = seeed_xiao_esp32s3
custom_variant = xiao_esp32s3 custom_variant = xiao_esp32s3
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -441,9 +454,10 @@ 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@6.12.0
board = esp32dev board = esp32dev
custom_variant = esp32_generic custom_variant = esp32_generic
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -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,11 +486,10 @@ 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@6.12.0
board = ttgo-t-beam board = ttgo-t-beam
upload_speed = 460800 upload_speed = 460800
custom_variant = tbeam_local custom_variant = tbeam_local
@@ -485,16 +499,15 @@ 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]
platform = espressif32 platform = espressif32@6.12.0
board = ttgo-lora32-v21 board = ttgo-lora32-v21
upload_speed = 460800 upload_speed = 460800
custom_variant = lora32v21_local custom_variant = lora32v21_local
@@ -506,10 +519,11 @@ 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]
platform = espressif32 platform = espressif32@6.12.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
custom_variant = heltec32v4_local custom_variant = heltec32v4_local
board_build.partitions = no_ota.csv board_build.partitions = no_ota.csv
@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -96,13 +96,21 @@
#define SPI spiModem #define SPI spiModem
#endif #endif
#if HAS_LORA_PA
uint8_t lora_pa_model = LORA_PA_MODEL;
#endif
#if HAS_LORA_LNA
int lora_lna_gain = LORA_LNA_GAIN;
#endif
extern SPIClass SPI; extern SPIClass SPI;
#define MAX_PKT_LENGTH 255 #define MAX_PKT_LENGTH 255
sx126x::sx126x() : sx126x::sx126x() :
_spiSettings(16E6, MSBFIRST, SPI_MODE0), _spiSettings(16E6, MSBFIRST, SPI_MODE0),
_ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _busy(LORA_DEFAULT_BUSY_PIN), _rxen(LORA_DEFAULT_RXEN_PIN), _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _busy(LORA_DEFAULT_BUSY_PIN), _rxen(LORA_DEFAULT_RXEN_PIN), _txen(LORA_DEFAULT_TXEN_PIN),
_frequency(0), _frequency(0),
_txp(0), _txp(0),
_sf(0x07), _sf(0x07),
@@ -126,7 +134,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);
@@ -184,6 +192,7 @@ uint8_t ISR_VECT sx126x::singleTransfer(uint8_t opcode, uint16_t address, uint8_
void sx126x::rxAntEnable() { void sx126x::rxAntEnable() {
if (_rxen != -1) { digitalWrite(_rxen, HIGH); } if (_rxen != -1) { digitalWrite(_rxen, HIGH); }
if (_txen != -1) { digitalWrite(_txen, LOW); }
} }
void sx126x::loraMode() { void sx126x::loraMode() {
@@ -274,6 +283,21 @@ void sx126x::setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t
buf[7] = 0x00; buf[7] = 0x00;
buf[8] = 0x00; buf[8] = 0x00;
executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); executeOpcode(OP_PACKET_PARAMS_6X, buf, 9);
// SX1262 errata section 15.4: IQ polarity is inverted compared to
// SX1276. The SetPacketParams command resets register 0x0736 to an
// incorrect default. For standard IQ (no inversion), bit 2 must be
// SET after every SetPacketParams call. For inverted IQ, bit 2 must
// be CLEARED. Without this fix, LoRa RX demodulation fails silently
// while TX continues to work.
uint8_t iqreg = readRegister(0x0736);
if (buf[5] == 0x00) {
// Standard IQ: set bit 2
writeRegister(0x0736, iqreg | 0x04);
} else {
// Inverted IQ: clear bit 2
writeRegister(0x0736, iqreg & ~0x04);
}
} }
void sx126x::reset(void) { void sx126x::reset(void) {
@@ -286,6 +310,22 @@ void sx126x::reset(void) {
} }
} }
void sx126x::setDCDCRegulator(void) {
// Documentation
// 5. Power Distribution -> 5.1 Selecting DC-DC Converter or LDO Regulation
// 13.1.11 SetRegulatorMode
uint8_t mode_byte = MODE_STDBY_RC_6X;
executeOpcode(OP_STANDBY_6X, &mode_byte, 1);
// Enable DC-DC regulator for high power operation
uint8_t reg_mode = 0x01; // 0x00 = LDO, 0x01 = DC-DC
executeOpcode(OP_REGULATOR_MODE_6X, &reg_mode, 1);
delay(5);
waitOnBusy();
}
void sx126x::calibrate(void) { void sx126x::calibrate(void) {
// Put in STDBY_RC mode before calibration // Put in STDBY_RC mode before calibration
uint8_t mode_byte = MODE_STDBY_RC_6X; uint8_t mode_byte = MODE_STDBY_RC_6X;
@@ -316,10 +356,21 @@ int sx126x::begin(long frequency) {
if (_busy != -1) { pinMode(_busy, INPUT); } if (_busy != -1) { pinMode(_busy, INPUT); }
if (!_preinit_done) { if (!preInit()) { return false; } } if (!_preinit_done) { if (!preInit()) { return false; } }
if (_rxen != -1) { pinMode(_rxen, OUTPUT); } if (_rxen != -1) { pinMode(_rxen, OUTPUT); }
if (_txen != -1) { pinMode(_txen, OUTPUT); }
//TODO: if it works, make it optional
//#ifdef SX1262_USE_DCDC_REGULATOR
setDCDCRegulator();
//#endif
calibrate(); calibrate();
calibrate_image(frequency); calibrate_image(frequency);
enableTCXO(); #if HAS_TCXO
enableTCXO();
//13.1.15 SetRxTxFallbackMode to STDBY_XOSC
uint8_t fallback_mode = 0x30; // STDBY_XOSC after TX/RX
executeOpcode(OP_RX_TX_FALLBACK_MODE_6X, &fallback_mode, 1);
#endif
loraMode(); loraMode();
standby(); standby();
@@ -336,7 +387,9 @@ int sx126x::begin(long frequency) {
setFrequency(frequency); setFrequency(frequency);
setTxPower(2); setTxPower(2);
enableCrc(); enableCrc();
writeRegister(REG_LNA_6X, 0x96); // Set LNA boost writeRegister(REG_LNA_6X, 0x96); // Set LNA boosted gain mode
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity.
writeRegister(0x08B5, readRegister(0x08B5) | 0x01);
uint8_t basebuf[2] = {0}; // Set base addresses uint8_t basebuf[2] = {0}; // Set base addresses
executeOpcode(OP_BUFFER_BASE_ADDR_6X, basebuf, 2); executeOpcode(OP_BUFFER_BASE_ADDR_6X, basebuf, 2);
@@ -344,7 +397,22 @@ int sx126x::begin(long frequency) {
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_GC1109 if (lora_pa_model == LORA_PA_UNKNOWN) {
#if BOARD_MODEL == BOARD_HELTEC32_V4
pinMode(LORA_PA_PWR_EN, OUTPUT);
pinMode(LORA_PA_CSD, INPUT);
digitalWrite(LORA_PA_PWR_EN, HIGH); delay(5);
if (digitalRead(LORA_PA_CSD) == HIGH) {
lora_pa_model = LORA_PA_KCT8103L;
lora_lna_gain = LORA_LNA_KCT8103L_GAIN;
} else {
lora_pa_model = LORA_PA_GC1109;
}
#endif
}
if (lora_pa_model == LORA_PA_GC1109) {
// Enable Vfem_ctl for supply to // Enable Vfem_ctl for supply to
// PA power net. // PA power net.
pinMode(LORA_PA_PWR_EN, OUTPUT); pinMode(LORA_PA_PWR_EN, OUTPUT);
@@ -369,7 +437,26 @@ int sx126x::begin(long frequency) {
// is driven by the SX1262 DIO2 // is driven by the SX1262 DIO2
// pin directly, so we do not // pin directly, so we do not
// need to manually raise this. // need to manually raise this.
#endif
} else if (lora_pa_model == LORA_PA_KCT8103L) {
// Enable Vfem_ctl for supply to
// PA power net.
pinMode(LORA_PA_PWR_EN, OUTPUT);
digitalWrite(LORA_PA_PWR_EN, HIGH);
// Enable KCT8103L chip
pinMode(LORA_PA_CSD, OUTPUT);
digitalWrite(LORA_PA_CSD, HIGH);
// Enable receive LNA
pinMode(LORA_PA_CTX, OUTPUT);
digitalWrite(LORA_PA_CTX, LOW);
// On Heltec V4.3, the PA CPS pin
// is driven by the SX1262 DIO2
// pin directly, so we do not
// need to manually raise this.
}
#endif #endif
return 1; return 1;
@@ -379,15 +466,19 @@ void sx126x::end() { sleep(); SPI.end(); _preinit_done = false; }
int sx126x::beginPacket(int implicitHeader) { int sx126x::beginPacket(int implicitHeader) {
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_GC1109 if (lora_pa_model == LORA_PA_GC1109) {
// Enable PA CPS for transmit // Enable PA CPS for transmit
// digitalWrite(LORA_PA_CPS, HIGH); // digitalWrite(LORA_PA_CPS, HIGH);
// Disabled since we're keeping it // Disabled since we're keeping it
// on permanently as long as the // on permanently as long as the
// radio is powered up. // radio is powered up.
#endif } else if (lora_pa_model == LORA_PA_KCT8103L) {
digitalWrite(LORA_PA_CTX, HIGH);
}
#endif #endif
if (_txen != -1) { digitalWrite(_txen, HIGH); } //Set TXen high when transmitting
standby(); standby();
if (implicitHeader) { implicitHeaderMode(); } if (implicitHeader) { implicitHeaderMode(); }
else { explicitHeaderMode(); } else { explicitHeaderMode(); }
@@ -401,6 +492,9 @@ int sx126x::beginPacket(int implicitHeader) {
int sx126x::endPacket() { int sx126x::endPacket() {
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
if (_rxen != -1) { digitalWrite(_rxen, LOW); } //Set RXen low when transmitting
uint8_t timeout[3] = {0}; // Put in single TX mode uint8_t timeout[3] = {0}; // Put in single TX mode
executeOpcode(OP_TX_6X, timeout, 3); executeOpcode(OP_TX_6X, timeout, 3);
@@ -473,7 +567,7 @@ int ISR_VECT sx126x::currentRssi() {
executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1);
int rssi = -(int(byte)) / 2; int rssi = -(int(byte)) / 2;
#if HAS_LORA_LNA #if HAS_LORA_LNA
rssi -= LORA_LNA_GAIN; rssi -= lora_lna_gain;
#endif #endif
return rssi; return rssi;
} }
@@ -489,7 +583,7 @@ int ISR_VECT sx126x::packetRssi() {
executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3);
int pkt_rssi = -buf[0] / 2; int pkt_rssi = -buf[0] / 2;
#if HAS_LORA_LNA #if HAS_LORA_LNA
pkt_rssi -= LORA_LNA_GAIN; pkt_rssi -= lora_lna_gain;
#endif #endif
return pkt_rssi; return pkt_rssi;
} }
@@ -596,7 +690,7 @@ void sx126x::onReceive(void(*callback)(int)){
void sx126x::receive(int size) { void sx126x::receive(int size) {
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_GC1109 if (lora_pa_model == LORA_PA_GC1109) {
// Disable PA CPS for receive // Disable PA CPS for receive
// digitalWrite(LORA_PA_CPS, LOW); // digitalWrite(LORA_PA_CPS, LOW);
// That turned out to be a bad idea. // That turned out to be a bad idea.
@@ -604,7 +698,9 @@ void sx126x::receive(int size) {
// on and off too quickly. We'll keep // on and off too quickly. We'll keep
// it on permanently, as long as the // it on permanently, as long as the
// radio is powered up. // radio is powered up.
#endif } else if (lora_pa_model == LORA_PA_KCT8103L) {
digitalWrite(LORA_PA_CTX, LOW);
}
#endif #endif
if (size > 0) { if (size > 0) {
@@ -643,6 +739,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
@@ -770,12 +868,13 @@ void sx126x::setSyncWord(uint16_t sw) {
writeRegister(REG_SYNC_WORD_LSB_6X, 0x24); writeRegister(REG_SYNC_WORD_LSB_6X, 0x24);
} }
void sx126x::setPins(int ss, int reset, int dio0, int busy, int rxen) { void sx126x::setPins(int ss, int reset, int dio0, int busy, int rxen, int txen) {
_ss = ss; _ss = ss;
_reset = reset; _reset = reset;
_dio0 = dio0; _dio0 = dio0;
_busy = busy; _busy = busy;
_rxen = rxen; _rxen = rxen;
_txen = txen;
} }
void sx126x::dumpRegisters(Stream& out) { void sx126x::dumpRegisters(Stream& out) {

View File

@@ -74,6 +74,7 @@ public:
void disableCrc(); void disableCrc();
void enableTCXO(); void enableTCXO();
void disableTCXO(); void disableTCXO();
void setDCDCRegulator();
void rxAntEnable(); void rxAntEnable();
void loraMode(); void loraMode();
@@ -92,11 +93,13 @@ public:
byte random(); byte random();
void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN); void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN, int txen = LORA_DEFAULT_TXEN_PIN);
void setSPIFrequency(uint32_t frequency); void setSPIFrequency(uint32_t frequency);
void dumpRegisters(Stream& out); void dumpRegisters(Stream& out);
bool isKCT8103L() { return _kct8103l; }
private: private:
void explicitHeaderMode(); void explicitHeaderMode();
void implicitHeaderMode(); void implicitHeaderMode();
@@ -107,7 +110,8 @@ public:
// Poll for deferred DIO0 interrupt (call from main loop) // Poll for deferred DIO0 interrupt (call from main loop)
void pollDio0(); void pollDio0();
private: uint8_t readRegister(uint16_t address); private:
uint8_t readRegister(uint16_t address);
void writeRegister(uint16_t address, uint8_t value); void writeRegister(uint16_t address, uint8_t value);
uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value);
@@ -125,6 +129,7 @@ private:
int _reset; int _reset;
int _dio0; int _dio0;
int _rxen; int _rxen;
int _txen;
int _busy; int _busy;
long _frequency; long _frequency;
int _txp; int _txp;
@@ -141,6 +146,7 @@ private:
int _fifo_rx_addr_ptr; int _fifo_rx_addr_ptr;
uint8_t _packet[255]; uint8_t _packet[255];
bool _preinit_done; bool _preinit_done;
bool _kct8103l;
volatile bool _dio0_risen; volatile bool _dio0_risen;
void (*_onReceive)(int); void (*_onReceive)(int);
}; };