Files
firmware/src/mesh/SX128xInterface.cpp
T
Ben Meadors 585ce17f59 fix(nrf52): stop concurrent flash writers corrupting LittleFS, and stop a failed save formatting it (#11872)
* fix(nrf52): serialise the warm-node ring against LittleFS on the shared flash cache

On nRF52840 the warm-node store writes its 3-page record ring straight through
flash_nrf5x_write/erase/flush, holding only spiLock. Every LittleFS writer instead
holds Adafruit_LittleFS's own mutex, and two of them run on other tasks entirely:
Bluefruit's bond saves on the callback task, and - since phone config writes moved
into BLE context - a whole saveToDisk on the BLE task. Neither takes spiLock.

Both writers share one 4 KB page cache, one SoftDevice flash semaphore and one
result word. flash_cache_write repoints that cache when the requested page differs
from the cached one, so a second writer arriving mid-write flushes the first
writer's page and re-points the buffer; the first writer's remaining memcpy then
lands in the wrong page's image. Ring records end up inside LittleFS metadata, or
the reverse. The collision also exhausts the flash layer's 20 x 1 ms busy-retry
budget against an 85 ms page erase, and flash_cache_flush discards the failure, so
32 LittleFS blocks vanish with no error reaching the filesystem. What the user sees
is a torn directory pair on the next mount, a format, and critical error 13.

Take the filesystem mutex in the five ring entry points that reach flash, after
spiLock and never before - the order every existing path already uses. The ring
touches no LittleFS call itself, so the non-recursive mutex is never re-entered.

Longest new hold is a page rotation at roughly half a second, against a 2 s
supervision timeout and a 90 s watchdog. Non-nRF52840 backends are untouched.

* fix(nodedb): make saveProto report a failed readback or rename

SafeFile::close() already verifies the .tmp by hash and renames it over the
live file, and saveProto captured that result, logged it, and then returned
the pb_encode status alone. A torn or half-programmed page therefore counted
as a successful save: for the fullAtomic files the old contents silently
survived, for nodes.proto (written in place) the file was simply gone, and
saveToDisk's recovery path never fired for the one failure it exists for.

* fix(nodedb): retry a failed save before formatting, and never format on a low rail

saveToDisk answered any failed write with an immediate fsFormat(), which is
where most "critical error 12/13" reports and the total config wipe behind
them come from. A write that fails once is far more often a busy SoftDevice
or a VDD dip mid-save than a corrupt filesystem, so:

- retry twice, 150 ms apart, re-checking powerHAL_isPowerLevelSafe() before
  each attempt and before the format; on a low rail return false and leave
  the filesystem alone (the next save lands once the rail recovers, and boot
  already waits for a safe level)
- check fsFormat()'s result instead of assuming it worked
- after a successful format rewrite every segment, not only the ones this
  call asked for: the format took config.proto and the node identity with it,
  so a nodes-only save that ended in a format used to come back up as a new
  node
- with encrypted storage a format also destroys the DEK; skip the resave
  rather than land the private key and PSKs on flash in plaintext

RP2040 feeds its watchdog across the delays, as the neighbouring code does.

* fix(nrf52): quiesce flash before every software reset and power-off

The Adafruit flash layer keeps one 4 KB page image and one SoftDevice flash
semaphore for the whole chip. Every reset path we own - Power::reboot(),
enterDfuMode() (admin enter_dfu_mode_request, which arrives on the BLE task
since #10967), cpuDeepSleep()'s reset and system-off arms, and the
wio-t1000-s secure DFU handler - went straight to NVIC_SystemReset or
sd_power_system_off while another task could be half-way through a page
program or erase. A reset in that window leaves the page erased or partly
programmed; LittleFS finds the torn metadata on the next mount and the
corruption handler formats the filesystem.

nrf52FlashQuiesce() takes spiLock and the LittleFS mutex, waits out whatever
write is in flight, flushes the page cache, and keeps both locks because the
caller resets next. The corruption-reboot handler and __assert_func are left
alone: they run inside the filesystem call stack or a fault, where taking the
mutex would deadlock.

nRF54L is a second copy of these paths since #11867 and still defines
ARCH_NRF52, so it gets the same function on the same core flash layer.

* fix(nrf52): quiesce flash before the library BLE DFU handler jumps to the bootloader

On every board except wio-t1000-s the Nordic DFU service is the framework's
BLEDfu, whose START_DFU handler runs on the callback task and jumps to the
bootloader with no regard for a flash write in progress on the loop task.
That is the OTA path the Apple app and nRF Connect use (Android sends
enter_dfu_mode_request instead, which the previous commit covers).

QuiescingBLEDfu re-installs the control-point write callback after
BLEDfu::begin() and wraps the library's: flush under both locks, then drop
the LittleFS mutex before handing over, because the library reloads the bond
keys through LittleFS on its way to the jump and the mutex is not recursive.
spiLock stays held across the handler: every LittleFS writer on the BLE task
takes it first, the loop task cannot preempt the callback task, and the
handler never blocks after the flush, so nothing can dirty flash before
bootloader_util_app_start(). If the handler returns, nothing jumped, and the
lock is released.

The library callback is a file-static, so it is read back out of the
characteristic through a pointer-to-member obtained via a using-declaration;
that is well-formed C++ and compiles under the pinned GCC 9.3 with LTO.

* test(nodedb): pin the save-failure contract of saveProto and saveToDisk

A failed rename must come back as false from saveProto, a one-off unsafe
rail reading during a write must be retried and land, and a rail still
unsafe at the retry gate must make saveToDisk return false with the
filesystem untouched. The rail is scripted through a strong
powerHAL_isPowerLevelSafe() over the weak native default; on Windows the
default is strong, so only the rename case runs there. The format branch
itself is unreachable natively (a FLASH_CORRUPTION critical error exits
the portduino process), which is what the survival assertions pin.

* fix(nodedb): only format when the filesystem itself is unreadable

Making saveProto honest about write failures gave the recovery path a new way in:
any persistent write failure now reached fsFormat(), which takes every file with
it. A busy or lock-protected nRF52 flash fails every write for as long as it lasts,
so two retries are not enough to tell that apart from a corrupt filesystem, and
guessing wrong costs the node its config, keys and bonds.

Reads settle it. They never touch the SoftDevice write path that a busy flash
fails on, so if /prefs still walks and a stored proto still opens and reads, the
metadata chain is intact and the write failure was transient - return false and
let the caller try again later. Genuine corruption is not silently tolerated: lfs
asserts on it, and the nRF52 handler reboots and formats on the way back up.

Covered by a test that fails without this: a save whose rename cannot succeed,
against an otherwise healthy filesystem, must leave devicestate untouched.

* trunk: exempt Unity test entry points from trufflehog

trufflehog's Lob detector matches "test_" followed by alphanumerics, which
describes every Unity test function name. It fired on a new test in
test_nodedb_save_retry and will fire again on the next suite added. Scoped to
test/**/test_main.cpp, alongside the existing gitleaks exemption for the
synthetic node-DB fixtures.

* fix(nodedb): feed the RP2040 watchdog around the format and the resave

saveToDisk() only feeds the watchdog at the top of each retry. The last
retry, the readable probe, fsFormat() and the five-segment resave then
share one 8 s budget (watchdog_enable in main-rp2xx0.cpp) with no loop
left to feed it. A timeout during the resave leaves the filesystem empty
and the node boots on defaults with a new identity - the exact outcome
this PR exists to prevent, reached by a different road.

Feed once before the probe and again before the resave. Both feeds sit
outside any lock: filesystemStillReadable() takes spiLock itself, and
the format has already released it. ARCH_RP2040 covers rp2040 and rp2350
alike, and the blocks compile out everywhere else, so no other platform
and no native test changes.

Raised by @caveman99 in review.

* fix(nodedb): narrow the save-probe comment and name the full-filesystem case

The comment on filesystemStillReadable() claimed "real corruption asserts
in lfs and formats on reboot". That does hold on nRF52 - nrf52.ini builds
with -DLFS_NO_ASSERT and force-includes cpp_overrides/lfs_util.h, whose
LFS_NO_ASSERT arm routes LFS_ASSERT to the lfs_assert() in main-nrf52.cpp,
which stamps NRF52_MAGIC_LFS_IS_CORRUPT and resets into the format - but
NodeDB.cpp compiles for ESP32, RP2040 and portduino too, where nothing of
the sort is wired up. It is also not true on nRF52 under POFWARN, where
lfs_assert() deliberately skips the stamp. Drop the claim rather than
qualify it three ways.

The log line now names what a field log actually needs to tell apart: a
filesystem that still reads but cannot be written is either busy or full.

Raised by @caveman99 in review.

* fix(sx128x): quiesce flash before the 2.4GHz region reset

reinitChip() saves the region, waits 2 s and resets. On nRF52 that was
the last software reset still going straight to NVIC_SystemReset with a
page program possibly in flight, so "every software reset" in the earlier
commit did not quite hold.

The quiesce stays inside the ARCH_NRF52 arm on purpose. The #else arm
logs and falls through to lora.setCRC() further down, which re-enters
spiBeginTransaction(); a quiesce hoisted above the #if would take spiLock
and never give it back, self-deadlocking portduino and stm32wl. Routing
this through Power::reboot() is wrong for the same class of reason:
setupModules() runs before initLoRa, so its notifyReboot observers and
waypointStore.saveToFlash() are live and would add a flash write to an
aborted radio init.

Raised by @caveman99 in review.

* fix(nrf52): only quiesce on the DFU control write that actually resets

QuiescingBLEDfu wrapped every control-point write, so a write that was
never going to reset still blocked the Bluefruit callback task on spiLock,
forced an early page-cache commit and held back the GATT authorize reply.
Only START_DFU resets; gate on that.

Deliberately no "request->len &&" term. The library's own test is
`request->data[0] == START_DFU` with no length check (BLEDfu.cpp:110 in
both the nRF52 and nRF54 cores), and Bluefruit hands the callback a copy
of a reused event buffer, so a zero-length write carrying a stale 0x01
still resets inside the library. A len term here would let exactly that
reset run unquiesced, which is the case this wrapper exists for. Reading
data[0] is always in bounds: ble_gatts_evt_write_t declares uint8_t
data[1] and the copy covers it.

Raised by @caveman99 in review.
2026-09-17 22:31:32 +00:00

407 lines
14 KiB
C++

#if RADIOLIB_EXCLUDE_SX128X != 1
#include "SX128xInterface.h"
#include "Throttle.h"
#include "configuration.h"
#include "error.h"
#include "main.h"
#include "mesh/NodeDB.h"
#if ARCH_PORTDUINO
#include "PortduinoGlue.h"
#endif
// Particular boards might define a different max power based on what their hardware can do
#if ARCH_PORTDUINO
#define SX128X_MAX_POWER portduino_config.sx128x_max_power
#endif
#ifndef SX128X_MAX_POWER
#define SX128X_MAX_POWER 13
#endif
template <typename T>
SX128xInterface<T>::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
template <typename T> bool SX128xInterface<T>::init()
{
#ifdef SX128X_POWER_EN
pinMode(SX128X_POWER_EN, OUTPUT);
digitalWrite(SX128X_POWER_EN, HIGH);
#endif
#ifdef RF95_FAN_EN
pinMode(RF95_FAN_EN, OUTPUT);
digitalWrite(RF95_FAN_EN, 1);
#endif
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); // Set low before becoming an output
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_txen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_txen_pin.pin, LOW); // Set low before becoming an output
}
#else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode
pinMode(SX128X_RXEN, OUTPUT);
digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output
#endif
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
pinMode(SX128X_TXEN, OUTPUT);
digitalWrite(SX128X_TXEN, LOW);
#endif
#endif
RadioLibInterface::init();
if (!reinitChip(/*fromInit=*/true))
return false;
startReceive(); // start receiving
return true;
}
// begin() and the chip-side setup that a reset chip loses. Shared by init() and by reconfigure()'s
// recovery of a chip that lost its state.
template <typename T> bool SX128xInterface<T>::reinitChip(bool fromInit)
{
// Clamp here, not just in programModemParams(): applyModemConfig() resets `power` to the raw
// config value, and the recovery path reaches begin() without passing through the params clamp
limitPower(SX128X_MAX_POWER);
preambleLength = 12; // 12 is the default for this chip, 32 does not RX at all
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
// \todo Display actual typename of the adapter, not just `SX128x`
LOG_INFO("SX128x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
// Boot-time only: rebooting out of a runtime recovery would reintroduce exactly the crash this
// recovery path exists to avoid, and would do it while a config save is still pending.
if (!fromInit) {
LOG_ERROR("SX128x rejected the frequency during recovery; leaving region alone");
return false;
}
LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24;
nodeDB->saveToDisk(SEGMENT_CONFIG);
delay(2000);
#if defined(ARCH_ESP32)
ESP.restart();
#elif defined(ARCH_NRF52)
nrf52FlashQuiesce(); // reset with the flash layer quiesced, like every other nRF52 reset path
NVIC_SystemReset();
#else
LOG_ERROR("FIXME implement reboot for this platform. Skip for now");
#endif
}
LOG_INFO("Frequency set to %f", getFreq());
LOG_INFO("Bandwidth set to %f", bw);
LOG_INFO("Power output set to %d", power);
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) && defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC)
if (res == RADIOLIB_ERR_NONE) {
lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN);
}
#elif ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE && portduino_config.lora_rxen_pin.pin != RADIOLIB_NC &&
portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
}
#endif
if (res == RADIOLIB_ERR_NONE)
res = lora.setCRC(2);
if (res != RADIOLIB_ERR_NONE)
LOG_ERROR("SX128x re-init failed %s%d", radioLibErr, res);
return res == RADIOLIB_ERR_NONE;
}
template <typename T> int16_t SX128xInterface<T>::programModemParams()
{
// configure publicly accessible settings
int16_t err = lora.setSpreadingFactor(sf);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setSpreadingFactor(%u) %s%d", sf, radioLibErr, err);
return err;
}
err = lora.setBandwidth(bw);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setBandwidth(%.1f) %s%d", bw, radioLibErr, err);
return err;
}
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setCodingRate(%u) %s%d", cr, radioLibErr, err);
return err;
}
err = lora.setSyncWord(syncWord);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setSyncWord %s%d", radioLibErr, err);
return err;
}
err = lora.setPreambleLength(preambleLength);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setPreambleLength(%u) %s%d", preambleLength, radioLibErr, err);
return err;
}
err = lora.setFrequency(getFreq());
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setFrequency(%.3f) %s%d", getFreq(), radioLibErr, err);
return err;
}
limitPower(SX128X_MAX_POWER);
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X setOutputPower(%d) %s%d", power, radioLibErr, err);
return err;
}
return RADIOLIB_ERR_NONE;
}
template <typename T> bool SX128xInterface<T>::reconfigure()
{
RadioLibInterface::reconfigure();
// set mode to standby - a chip that lost its state to a reset/brownout can time out here,
// so don't let setStandby()'s assert fire before the recovery below gets a chance
int16_t err = trySetStandby();
if (err == RADIOLIB_ERR_NONE)
err = programModemParams();
if (err != RADIOLIB_ERR_NONE) {
// A chip that fails standby or rejects parameter programming (typically WRONG_MODEM, -20) has
// lost its runtime configuration - packet type included - to a chip-internal reset or brownout.
// Recover in place: begin() hardware-resets the chip and restores the LoRa packet type. Crashing
// here instead would reboot before MeshService persists the config change that triggered us.
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
LOG_ERROR("SX128x rejected modem params, chip state lost? Full re-init");
if (!reinitChip() || (err = programModemParams()) != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128x unrecoverable %s%d, radio down until reboot", radioLibErr, err);
return false;
}
LOG_INFO("SX128x recovered after re-init");
}
startReceive(); // restart receiving
return true;
}
template <typename T> void SX128xInterface<T>::clearRadioIsr()
{
lora.clearDio1Action();
}
template <typename T> bool SX128xInterface<T>::wideLora()
{
return true;
}
template <typename T> int16_t SX128xInterface<T>::trySetStandby()
{
checkNotification(); // handle any pending interrupts before we force standby
int16_t err = lora.standby();
if (err != RADIOLIB_ERR_NONE)
LOG_ERROR("SX128x standby %s%d", radioLibErr, err);
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
}
#else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power
digitalWrite(SX128X_RXEN, LOW);
#endif
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
digitalWrite(SX128X_TXEN, LOW);
#endif
#endif
isReceiving = false; // If we were receiving, not any more
activeReceiveStart = 0;
disableInterrupt();
completeSending(); // If we were sending, not anymore
RadioLibInterface::setStandby();
return err;
}
template <typename T> void SX128xInterface<T>::setStandby()
{
int16_t err = trySetStandby();
assert(err == RADIOLIB_ERR_NONE);
}
/**
* Add SNR data to received messages
*/
template <typename T> void SX128xInterface<T>::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
mp->has_rx_rssi = true; // rx_rssi has explicit presence - a genuine reading must be marked present to survive encoding
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
}
/** We override to turn on transmitter power as needed.
*/
template <typename T> void SX128xInterface<T>::configHardwareForSend()
{
#if ARCH_PORTDUINO
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, HIGH);
}
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
}
#else
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on TX power / off RX power
digitalWrite(SX128X_TXEN, HIGH);
#endif
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC)
digitalWrite(SX128X_RXEN, LOW);
#endif
#endif
RadioLibInterface::configHardwareForSend();
}
// For power draw measurements, helpful to force radio to stay sleeping
// #define SLEEP_ONLY
template <typename T> void SX128xInterface<T>::startReceive()
{
#ifdef SLEEP_ONLY
sleep();
#else
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, HIGH);
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
}
#else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on RX power / off TX power
digitalWrite(SX128X_RXEN, HIGH);
#endif
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
digitalWrite(SX128X_TXEN, LOW);
#endif
#endif
int16_t err = trySetStandby();
if (err == RADIOLIB_ERR_NONE)
err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX128X startReceive %s%d", radioLibErr, err);
if (maybeRecoverChipStateLoss())
err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
}
if (err != RADIOLIB_ERR_NONE) {
// No assert: leave RX off rather than reboot; periodicRadioMaintenance() re-arms it, throttled
LOG_ERROR("SX128X RX offline %s%d", radioLibErr, err);
rxOffline = true;
return;
}
RadioLibInterface::startReceive();
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);
checkRxDoneIrqFlag();
#endif
}
/** Is the channel currently active? */
template <typename T> bool SX128xInterface<T>::isChannelActive()
{
// check if we can detect a LoRa preamble on the current channel
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD_24GHZ,
.detPeak = 0,
.detMin = 0,
.exitMode = 0,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result = trySetStandby();
if (result == RADIOLIB_ERR_NONE) {
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
return true;
if (result != RADIOLIB_CHANNEL_FREE)
LOG_ERROR("SX128X scanChannel %s%d", radioLibErr, result);
if (result != RADIOLIB_ERR_WRONG_MODEM)
return false;
}
// standby failed or the LoRa modem type is gone - the chip lost its runtime state
maybeRecoverChipStateLoss();
return false; // report the channel free: a recovered chip can TX, a dead one fails startSend safely
}
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
template <typename T> bool SX128xInterface<T>::isActivelyReceiving()
{
return receiveDetected(lora.getIrqStatus(), RADIOLIB_SX128X_IRQ_HEADER_VALID, RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED);
}
template <typename T> bool SX128xInterface<T>::sleep()
{
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
// \todo Display actual typename of the adapter, not just `SX128x`
LOG_DEBUG("SX128x entering sleep mode"); // (FIXME, don't keep config)
(void)trySetStandby(); // Stop any pending operations - the chip is being put to sleep, a failure must not crash
// turn off TCXO if it was powered
// FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = true;
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
#ifdef SX128X_POWER_EN
digitalWrite(SX128X_POWER_EN, LOW);
#endif
return true;
}
template <typename T> int16_t SX128xInterface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false);
return (int16_t)round(rssi);
}
#endif