diff --git a/BoundaryConfig.h b/BoundaryConfig.h index c4bc237..2b0bca4 100755 --- a/BoundaryConfig.h +++ b/BoundaryConfig.h @@ -1,7 +1,10 @@ // Copyright (C) 2026, Boundary Mode Extension // 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), // the device starts a WiFi AP with a web form for all settings: // 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 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 ──────────────────────────────────────────────────── static void config_send_html() { @@ -304,6 +317,11 @@ static void config_send_html() { 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 char* blank_labels[] = { "Never", "1 minute", "5 minutes", "10 minutes", "30 minutes", "60 minutes" }; static const int blank_count = 6; @@ -320,6 +338,23 @@ static void config_send_html() { html += F(""); html += F("
Turn off display after inactivity to save power
"); + html += F(""); + html += F("Choose the orientation that matches your OLED mounting. " + "Landscape modes place the two status panes side by side; portrait modes stack them.
"); + // ── Submit ── html += F( "" @@ -353,6 +388,14 @@ static void config_handle_save() { // Set WiFi mode to 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 ── 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); } + 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 ── 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; @@ -481,6 +530,14 @@ static void config_handle_redirect() { // ─── Check if config is needed ─────────────────────────────────────────────── 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 char ssid[33]; for (int i = 0; i < 32; i++) { diff --git a/BoundaryMode.h b/BoundaryMode.h index c77b987..3934dfd 100755 --- a/BoundaryMode.h +++ b/BoundaryMode.h @@ -1,9 +1,11 @@ // Copyright (C) 2026, Boundary Mode Extension // Based on microReticulum_Firmware by Mark Qvist // -// BoundaryMode.h — Configuration and runtime state for the Boundary Mode -// firmware variant. This header defines the WiFi backbone connection -// parameters and boundary-specific operational settings. +// BoundaryMode.h — Configuration and runtime state for the legacy +// "Boundary Mode" firmware variant. Going forward this should be renamed +// "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 // it under the terms of the GNU General Public License as published by @@ -15,8 +17,19 @@ #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 ──────────────────────────────────────────── // +// 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: // // 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_NAME 0xD7 // Network name (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) #define BOUNDARY_ENABLE_BYTE 0x73 +#define BOUNDARY_APP_MARKER0 0x52 +#define BOUNDARY_APP_MARKER1 0x54 +#define BOUNDARY_APP_VERSION 0x01 // ─── Boundary Mode Runtime State ───────────────────────────────────────────── #ifndef BOUNDARY_STATE_DEFINED @@ -115,6 +134,24 @@ extern BoundaryState boundary_state; // ─── 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() { // Check if boundary mode is configured 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 + 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(); } diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 89e5154..00ec5f4 100755 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -28,6 +28,9 @@ #include "Utilities.h" // 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 #include "BoundaryMode.h" #include "TcpInterface.h" @@ -525,6 +528,7 @@ void setup() { // Enter config mode if: first boot with no config, OR button-triggered reboot, // OR bootloop detected + bool app_marker_missing = !boundary_app_marker_valid(); bool need_config = boundary_needs_config(); bool config_requested = (boundary_config_request == BOUNDARY_CONFIG_MAGIC); bool skip_config = (boundary_skip_config == BOUNDARY_SKIP_MAGIC); @@ -543,6 +547,9 @@ void setup() { Serial.println("[Boundary] Entering config portal due to bootloop recovery"); } else if (config_requested) { 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 { Serial.println("[Boundary] No configuration found — starting config portal"); } @@ -1734,6 +1741,9 @@ void serial_callback(uint8_t sbyte) { eeprom_conf_save(); } else if (command == CMD_CONF_DELETE) { eeprom_conf_delete(); + #ifdef BOUNDARY_MODE + boundary_clear_app_marker(); + #endif } else if (command == CMD_FB_EXT) { #if HAS_DISPLAY == true if (sbyte == 0xFF) { diff --git a/Release/rnode_firmware_heltec32v3.bin b/Release/rnode_firmware_heltec32v3.bin old mode 100644 new mode 100755 index b88f4cd..22d7d56 Binary files a/Release/rnode_firmware_heltec32v3.bin and b/Release/rnode_firmware_heltec32v3.bin differ diff --git a/Release/rnode_firmware_heltec32v4_boundary.bin b/Release/rnode_firmware_heltec32v4_boundary.bin index c3073f9..8dd627b 100755 Binary files a/Release/rnode_firmware_heltec32v4_boundary.bin and b/Release/rnode_firmware_heltec32v4_boundary.bin differ diff --git a/Remote.h b/Remote.h index d8a0fae..650f2b3 100755 --- a/Remote.h +++ b/Remote.h @@ -78,6 +78,13 @@ void wifi_remote_start_ap() { void wifi_remote_start_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; 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; } @@ -97,6 +104,7 @@ void wifi_remote_start_sta() { IPAddress dns2(1, 1, 1, 1); WiFi.config(sta_ip, sta_gw, sta_nm, dns1, dns2); } +#endif delay(100); if (wr_ssid[0] != 0x00) {