Fit Mini Repeater with approved optional service cuts

This commit is contained in:
mikecarper committed 2026-10-17 19:35:05 -07:00
1 parent 7a9d099039
commit 55f67f9062
30 files changed
+1259 -34

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
#pragma once
// Optional unmodulated transmit tools. Packet TX/RX and the independently
// controlled Morse receiver do not depend on these bench/CLI transmit modes.
#ifndef MESH_ENABLE_CARRIER_TOOLS
#define MESH_ENABLE_CARRIER_TOOLS 1
#endif
#if MESH_ENABLE_CARRIER_TOOLS != 0 && MESH_ENABLE_CARRIER_TOOLS != 1
#error "MESH_ENABLE_CARRIER_TOOLS must be 0 or 1"
#endif
+10
View File
@@ -1,4 +1,6 @@
#include <Arduino.h>
#include "CarrierTools.h"
#include "PacketFileLoggingConfig.h"
#include "CommonCLI.h"
#include "PrefsSaveReplyGuard.h"
#include "UsbLoggingWatchdog.h"
@@ -6,7 +8,9 @@
#include "HilStartupTrace.h"
#include <helpers/ui/DisplayPowerSettings.h>
#include <helpers/BatteryChargeCLI.h>
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
#include <helpers/MorseCLI.h>
#endif
#include <helpers/MorseLoopPolicy.h>
#if defined(RAK_4631)
#include <helpers/PeripheralModePrefs.h>
@@ -780,7 +784,9 @@ static void formatSnrDbX4Short(char* dest, size_t dest_len, int16_t snr_x4) {
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
mesh::hilStartupTrace("prefs_profiles_begin");
_radio_profiles.begin(fs, _callbacks->getProfileRadio(), _rtc, true);
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
mesh::loadMorseSettings(fs);
#endif
mesh::hilStartupTrace("prefs_profiles_ready");
_prefs->primary_radio_preamble = _radio_profiles.primaryPreamble();
#if !defined(WITH_MQTT_BRIDGE) && (defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM))
@@ -2973,9 +2979,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
_callbacks->wirelessCommandSource(sender_timestamp))) return;
// Infrastructure callers can already have reflected a three-byte CLI
// request prefix into the same 160-byte reply. Reserve it for bulk pages.
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
if (mesh::handleMorseReplyCommand(_morse_reply, _callbacks->getProfileRadio(),
command, reply, 160 - 3, millis())) return;
if (mesh::handleMorseCommand(_callbacks->getProfileRadio(), command, reply, 160 - 3)) return;
#endif
if (_radio_profiles.handle(command, reply, 160, sender_timestamp != 0)) return;
#if defined(ENABLE_OTA)
const auto* ota_context = mesh::ota::ota_context_if_active();
@@ -3583,6 +3591,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
_board->getShutdownReasonString(_board->getShutdownReason()));
sprintf(reply + strlen(reply), "\r\nLast boot voltage: %u mV", _board->getBootVoltage());
#endif
#if MESH_ENABLE_PACKET_FILE_LOGGING
} else if (memcmp(command, "log start", 9) == 0) {
_callbacks->setLoggingOn(true);
strcpy(reply, " logging on");
@@ -3595,6 +3604,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
} else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
_callbacks->dumpLogFile();
strcpy(reply, " EOF");
#endif
} else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
_callbacks->formatPacketStatsReply(reply);
} else if (sender_timestamp == 0 && memcmp(command, "stats-radio-diag", 16) == 0 && (command[16] == 0 || command[16] == ' ')) {
+53 -1
View File
@@ -1,10 +1,12 @@
#include "ManagementFeatureConfig.h"
#include "CommonCLI.h"
#include <new>
#if MESH_ENABLE_MANAGEMENT_REPORTING
#include "ManagementReporter.h"
#include "FileRead.h"
#include "FilePresence.h"
#include "PersistentStoreFormat.h"
#include "HilStartupTrace.h"
#include <new>
#include <stdlib.h>
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include "AtomicFileWriter.h"
@@ -258,19 +260,23 @@ static bool resolveDataScope(DataRouteState& route, TransportKey& scope,
return true;
}
} // namespace mesh
#endif
void CommonCLI::beginManagement(mesh::Mesh& mesh, FILESYSTEM* fs) {
_management_mesh = &mesh; _management_fs = fs;
#if MESH_ENABLE_TX_BEACONS
beginBeacons();
#endif
#if MESH_ENABLE_MANAGEMENT_REPORTING
_management_last_ms = millis(); _management_uptime_ms = _management_last_ms;
#endif
#if MESH_ENABLE_FLEET_CONTROL
if (_callbacks->supportsFleetControl()
&& (fs->exists("/fleet_channel") || fs->exists("/fleet_channel.bak"))) {
_fleet_channel = new (std::nothrow) mesh::FleetChannel(fs, _callbacks, _region_map);
}
#endif
#if MESH_ENABLE_MANAGEMENT_REPORTING
if (!_data_route) {
mesh::hilStartupTrace("management_route_begin");
_data_route = new (std::nothrow) mesh::DataRouteState;
@@ -289,6 +295,7 @@ void CommonCLI::beginManagement(mesh::Mesh& mesh, FILESYSTEM* fs) {
mesh::hilStartupTrace("management_reporter_ready");
}
mesh::hilStartupTrace("management_probe_ready");
#endif
}
#if MESH_ENABLE_FLEET_CONTROL
@@ -310,6 +317,7 @@ bool CommonCLI::handleFleetCommand(const char* command, char* reply) {
}
#endif
#if MESH_ENABLE_MANAGEMENT_REPORTING
void CommonCLI::loopManagement() {
const uint32_t now = millis();
_management_uptime_ms += uint32_t(now - _management_last_ms); _management_last_ms = now;
@@ -456,3 +464,47 @@ bool CommonCLI::handleManagementCommand(char* command, char* reply) {
if (!_management->command(command, reply, 160)) strcpy(reply, "ERR: unknown management setting");
return true;
}
#else
void CommonCLI::loopManagement() { }
bool CommonCLI::getDataTxPath(const uint8_t*& path, uint8_t& path_len) const {
path = nullptr; path_len = OUT_PATH_UNKNOWN;
return false;
}
bool CommonCLI::resolveDataTxScope(TransportKey&, char* name,
size_t name_size, bool* ambiguous) const {
if (name && name_size) name[0] = 0;
if (ambiguous) *ambiguous = false;
return false;
}
bool CommonCLI::adoptLegacyDataTxPath(const uint8_t*, uint8_t) {
return false;
}
bool CommonCLI::setDataTxPath(const char*, char* reply, size_t reply_size) {
snprintf(reply, reply_size, "Err - data.tx unavailable");
return false;
}
bool CommonCLI::handleDataTxCommand(char* command, char* reply) {
if (strncmp(command, "get data.tx", 11) && strncmp(command, "set data.tx", 11)) return false;
if (command[11] && command[11] != ' ' && command[11] != '.') return false;
strcpy(reply, "Err - data.tx unavailable");
return true;
}
bool CommonCLI::handleManagementCommand(char* command, char* reply) {
if (handleDataTxCommand(command, reply)) return true;
if (strcmp(command, "get mgmt") && strncmp(command, "get mgmt.", 9)
&& strncmp(command, "set mgmt.", 9)) return false;
if (!strncmp(command, "set mgmt.password ", 18)) {
// Retain recognized-password hygiene without pulling in report crypto.
volatile char* value = command + 18;
while (*value) *value++ = 0;
}
strcpy(reply, "ERR: management unavailable");
return true;
}
#endif
+11
View File
@@ -0,0 +1,11 @@
#pragma once
// Management reporting and its shared data.tx delivery route are included by
// default. Fixed-capacity builds can omit them without changing saved layouts.
#ifndef MESH_ENABLE_MANAGEMENT_REPORTING
#define MESH_ENABLE_MANAGEMENT_REPORTING 1
#endif
#if MESH_ENABLE_MANAGEMENT_REPORTING != 0 && MESH_ENABLE_MANAGEMENT_REPORTING != 1
#error "MESH_ENABLE_MANAGEMENT_REPORTING must be 0 or 1"
#endif
+3
View File
@@ -1,3 +1,5 @@
#include "ManagementFeatureConfig.h"
#if MESH_ENABLE_MANAGEMENT_REPORTING
#include "ManagementReporter.h"
#include "CommonCLI.h"
#include "FileRead.h"
@@ -441,3 +443,4 @@ bool ManagementReporter::command(char* command, char* reply, size_t size) {
snprintf(reply, size, "OK"); return true;
}
}
#endif
+11
View File
@@ -0,0 +1,11 @@
#pragma once
// Persistent packet-log files are independent of live USB and bridge logging.
// Normal boards retain file logging; constrained builds may omit it.
#ifndef MESH_ENABLE_PACKET_FILE_LOGGING
#define MESH_ENABLE_PACKET_FILE_LOGGING 1
#endif
#if MESH_ENABLE_PACKET_FILE_LOGGING != 0 && MESH_ENABLE_PACKET_FILE_LOGGING != 1
#error "MESH_ENABLE_PACKET_FILE_LOGGING must be 0 or 1"
#endif
+5
View File
@@ -1,5 +1,8 @@
#include "RadioProfileCLI.h"
#include "CarrierTools.h"
#if MESH_ENABLE_CARRIER_TOOLS
#include "CarrierWaveCLI.h"
#endif
#include "RadioProfileCommandUtils.h"
#include "FilePresence.h"
#include "radiolib/RXPowerSaving.h"
@@ -694,7 +697,9 @@ bool RadioProfileCLI::handle(const char* command, char* reply, size_t capacity,
#if defined(MESH_HIL_TX_FREQ_MHZ)
if (handleHilTxFrequencyCommand(command, reply, capacity)) return true;
#endif
#if MESH_ENABLE_CARRIER_TOOLS
if (handleCarrierWaveCommand(radio_, command, reply, capacity)) return true;
#endif
const char* text = command;
enum { Get, Set, Delete } verb = Get;
if (!strncmp(text, "get ", 4)) text += 4;
@@ -4,8 +4,12 @@
#include "CustomSTM32WLx.h"
#include "RadioLibWrappers.h"
#include "SX126xReset.h"
#if MESH_ENABLE_CARRIER_TOOLS
#include "SX126xMorseRamp.h"
#endif
#if MESH_ENABLE_MORSE_RX
#include "SX126xMorseReceiver.h"
#endif
#include <math.h>
class CustomSTM32WLxWrapper : public RadioLibWrapper {
@@ -17,7 +21,9 @@ protected:
public:
~CustomSTM32WLxWrapper() { unregisterInterruptAction(); }
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
#if MESH_ENABLE_CARRIER_TOOLS
bool supportsMorseTx() const override { return true; }
#endif
#if MESH_ENABLE_MESHTEXT_WIRE
bool supportsMeshtext() const override { return true; }
#endif
@@ -54,6 +60,7 @@ protected:
#if MESH_ENABLE_MORSE_RX
float readMorseRssi() override { return static_cast<CustomSTM32WLx*>(_radio)->getMorseRssi(); }
#endif
#if MESH_ENABLE_CARRIER_TOOLS
int16_t prepareMorseCarrier(float frequency_mhz) override {
const int16_t status = RadioLibWrapper::prepareMorseCarrier(frequency_mhz);
return status == RADIOLIB_ERR_NONE
@@ -62,6 +69,8 @@ protected:
int16_t leaveMorseCarrier() override {
return leaveSX126xMorseCarrier(static_cast<CustomSTM32WLx*>(_radio));
}
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
bool restoreCarrierWaveModem() override {
#if MESH_ENABLE_MORSE_RX
if (_morse_rx_fsk_mode) {
@@ -70,9 +79,14 @@ protected:
return restored;
}
#endif
#if MESH_ENABLE_CARRIER_TOOLS
return restoreSX126xPacketRamp(static_cast<CustomSTM32WLx*>(_radio)) == RADIOLIB_ERR_NONE
&& RadioLibWrapper::restoreCarrierWaveModem();
#else
return RadioLibWrapper::restoreCarrierWaveModem();
#endif
}
#endif
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
return ((CustomSTM32WLx *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
&& ((CustomSTM32WLx *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
+33 -5
View File
@@ -251,6 +251,7 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return false;
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
// A CW power-meter sweep may re-key the carrier. During Morse that would
// turn an inter-symbol gap into RF and corrupt the text, so keep the whole
// message exclusive (including cleanup retries).
@@ -259,6 +260,7 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
|| _morse_rx_owned
#endif
) && _cw_active) return false;
#if MESH_ENABLE_CARRIER_TOOLS
if (_cw_active) {
if (_cw_stopping) return false;
#if defined(MESH_HIL_TX_FREQ_MHZ)
@@ -280,6 +282,10 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
_dbm_valid = true;
return true;
}
#else
if (_cw_active) return false;
#endif
#endif
const uint8_t resume_rx = beginReconfigure();
if (resume_rx > 1) return false;
@@ -1344,6 +1350,7 @@ void RadioLibWrapper::checkReceiveMode(uint32_t now) {
recoverRadio(_rx_mode_failures >= 3);
}
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
uint32_t RadioLibWrapper::carrierWaveRemainingMillis() const {
if (!_cw_active || _cw_stopping) return 0;
if (_morse_owned) return morseTxRemainingMillis();
@@ -1356,6 +1363,7 @@ uint32_t RadioLibWrapper::carrierWaveRemainingMillis() const {
mesh::RadioParamApplyResult RadioLibWrapper::setCarrierWave(uint8_t profile, uint32_t duration_ms) {
using Result = mesh::RadioParamApplyResult;
#if MESH_ENABLE_CARRIER_TOOLS
// The shared emergency stop also covers chips exposing Morse without the
// older profile-CW CLI capability.
if (!duration_ms && (_morse_owned
@@ -1418,8 +1426,16 @@ mesh::RadioParamApplyResult RadioLibWrapper::setCarrierWave(uint8_t profile, uin
}
_cw_until = static_cast<uint32_t>(millis()) + duration_ms;
return Result::APPLIED;
#else
(void)profile;
// Keep the shared emergency stop available to an independently enabled RX.
return !duration_ms && _morse_rx_owned
? (stopCarrierWave() ? Result::APPLIED : Result::FAILED) : Result::FAILED;
#endif
}
#endif
#if MESH_ENABLE_CARRIER_TOOLS
bool RadioLibWrapper::supportsMorseTx() const {
return supportsCarrierWave();
}
@@ -1532,6 +1548,7 @@ bool RadioLibWrapper::serviceMorseTx() {
}
return true;
}
#endif
#if MESH_ENABLE_MORSE_RX
bool RadioLibWrapper::canAllocateMorseReceiver() const {
@@ -1619,7 +1636,10 @@ mesh::MorseRxResult RadioLibWrapper::startMorseRx(float frequency_mhz,
}
_radio->clearPacketReceivedAction();
_cw_active = _morse_rx_owned = true;
_morse_owned = _cw_stopping = false;
#if MESH_ENABLE_CARRIER_TOOLS
_morse_owned = false;
#endif
_cw_stopping = false;
_cw_profile = _active_profile;
_morse_generation[0] = _profiles.generation[0];
_morse_generation[1] = _profiles.generation[1];
@@ -1685,10 +1705,8 @@ void RadioLibWrapper::clearMorseCapture() {
}
#endif
#if MESH_ENABLE_MORSE_RX
bool RadioLibWrapper::serviceMorseRx() {
#if !MESH_ENABLE_MORSE_RX
return false;
#else
if (!_morse_rx_owned || !_cw_active) return false;
const uint32_t now = millis();
if (_cw_stopping) {
@@ -1714,11 +1732,14 @@ bool RadioLibWrapper::serviceMorseRx() {
}
}
return true;
#endif
}
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
bool RadioLibWrapper::stopCarrierWave() {
#if MESH_ENABLE_CARRIER_TOOLS
_morse.stop();
#endif
#if MESH_ENABLE_MORSE_RX
if (_morse_rx_owned && _morse_rx) _morse_rx->stop(static_cast<uint32_t>(millis()));
#endif
@@ -1770,7 +1791,9 @@ bool RadioLibWrapper::stopCarrierWave() {
_cw_retry_at = static_cast<uint32_t>(millis()) + 100;
return false;
}
#if MESH_ENABLE_CARRIER_TOOLS
_morse_owned = false;
#endif
#if MESH_ENABLE_MORSE_RX
_morse_rx_owned = false;
#endif
@@ -1782,6 +1805,7 @@ bool RadioLibWrapper::serviceCarrierWave() {
#if MESH_ENABLE_MORSE_RX
if (serviceMorseRx()) return true;
#endif
#if MESH_ENABLE_CARRIER_TOOLS
if (serviceMorseTx()) return true;
if (_cw_active) {
#if defined(MESH_HIL_TX_FREQ_MHZ)
@@ -1799,15 +1823,19 @@ bool RadioLibWrapper::serviceCarrierWave() {
}
return true;
}
#endif
return false;
}
#endif
void RadioLibWrapper::loop() {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return;
#endif
syncProfileClock(_profiles.enabled());
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
if (serviceCarrierWave()) return;
#endif
if (_profiles.enabled()) {
const unsigned long now = millis();
checkReceiveMode(static_cast<uint32_t>(now));
+29
View File
@@ -1,6 +1,7 @@
#pragma once
#include "NoiseFloorEstimator.h"
#include "../CarrierTools.h"
#include <Mesh.h>
#include <RadioLib.h>
@@ -139,16 +140,26 @@ protected:
#endif
void cacheBeaconRestoreParams();
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
bool _cw_active = false;
bool _cw_stopping = false;
bool _cw_board_tx = false;
uint8_t _cw_profile = 0, _cw_restore_profile = 0;
uint32_t _cw_until = 0, _cw_retry_at = 0, _cw_generation = 0;
#else
static constexpr bool _cw_active = false;
#endif
#if MESH_ENABLE_CARRIER_TOOLS
mesh::MorseTx _morse;
bool _morse_owned = false;
float _morse_frequency = mesh::MorseTx::DefaultFrequencyMHz;
uint8_t _morse_wpm = mesh::MorseTx::DefaultWpm;
#else
static constexpr bool _morse_owned = false;
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
uint32_t _morse_generation[2] = {};
#endif
// Allocate the bounded capture only on explicit RX admission. Permanently
// reserving 4096 characters would break small nRF52 Companion RAM budgets.
#if MESH_ENABLE_MORSE_RX
@@ -167,10 +178,16 @@ protected:
bool _morse_rx_fsk_mode = false;
#endif
// End Morse RX state.
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
bool stopCarrierWave();
bool serviceCarrierWave();
#endif
#if MESH_ENABLE_CARRIER_TOOLS
bool serviceMorseTx();
#endif
#if MESH_ENABLE_MORSE_RX
bool serviceMorseRx();
#endif
mesh::RadioParamApplyResult tuneProfile(uint8_t profile);
void serviceProfileScan();
@@ -297,10 +314,15 @@ protected:
virtual bool checkBeaconOutputPower(float frequency_mhz, int8_t dbm) const;
#endif
virtual bool applyRxBoostedGainMode(bool) { return false; }
#if MESH_ENABLE_CARRIER_TOOLS || !defined(STM32_PLATFORM)
virtual int16_t enterCarrierWave() { return _radio->transmitDirect(); }
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX || !defined(STM32_PLATFORM)
virtual bool validateMorseFrequency(float frequency_mhz) const {
return isfinite(frequency_mhz) && frequency_mhz >= 150.0f && frequency_mhz <= 960.0f;
}
#endif
#if MESH_ENABLE_CARRIER_TOOLS || !defined(STM32_PLATFORM)
// Configure the oscillator/modem once, before the first mark. Subsequent
// marks only re-enter direct TX; in particular SX127x must not rebuild FSK
// between dots. The cached LoRa tuple is deliberately left untouched.
@@ -312,6 +334,7 @@ protected:
}
virtual int16_t enterMorseCarrier() { return _radio->transmitDirect(); }
virtual int16_t leaveMorseCarrier() { return _radio->standby(); }
#endif
#if MESH_ENABLE_MORSE_RX || !defined(STM32_PLATFORM)
// Compact integrated-STM builds cannot receive Morse. Do not pin unused
// native RX thunks and vtable slots in their already full application slot.
@@ -330,9 +353,11 @@ protected:
#endif
// Native-CW radios keep their modem. Reapply cached power in case a meter
// sweep failed halfway through; SX127x also rebuilds its LoRa configuration.
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX || !defined(STM32_PLATFORM)
virtual bool restoreCarrierWaveModem() {
return !_dbm_valid || applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
}
#endif
// Dual-profile scanning already suspends RXPS. Supported radios can also
// retain their oscillator across the intervening standby/reconfigure steps.
// Called only at the safe scan-mode entry/exit boundary, not during a packet.
@@ -383,10 +408,13 @@ protected:
public:
void begin() override;
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
mesh::RadioParamApplyResult setCarrierWave(uint8_t profile, uint32_t duration_ms) override;
bool isCarrierWaveActive() const override { return _cw_active; }
uint8_t carrierWaveProfile() const override { return _cw_profile; }
uint32_t carrierWaveRemainingMillis() const override;
#endif
#if MESH_ENABLE_CARRIER_TOOLS
bool supportsMorseTx() const override;
mesh::MorseTxResult startMorseTx(float frequency_mhz, uint8_t wpm, const char* text) override;
mesh::MorseTxResult stopMorseTx() override;
@@ -394,6 +422,7 @@ public:
float morseTxFrequency() const override { return _morse_frequency; }
uint8_t morseTxWpm() const override { return _morse_wpm; }
uint32_t morseTxRemainingMillis() const override { return _morse.remainingMillis(millis()); }
#endif
#if MESH_ENABLE_MORSE_RX
bool supportsMorseRx() const override { return false; }
mesh::MorseRxResult startMorseRx(float frequency_mhz, uint8_t wpm, uint32_t duration_ms) override;