mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-08-28 13:34:07 +00:00
827 lines
26 KiB
C++
827 lines
26 KiB
C++
/**
|
|
* @file app_tasks.cpp
|
|
* @brief Application task implementation
|
|
*/
|
|
|
|
#include "platform/esp/arduino_common/app_tasks.h"
|
|
|
|
#include "platform/esp/common/shared_spi_lock.h"
|
|
#include "platform/ui/reticulum_call_runtime.h"
|
|
#include "platform/ui/screen_runtime.h"
|
|
#if defined(ARDUINO)
|
|
#include <Arduino.h>
|
|
#include <RadioLib.h>
|
|
#else
|
|
#include "esp_timer.h"
|
|
|
|
#include <cstdio>
|
|
|
|
#define RADIOLIB_ERR_NONE 0
|
|
#define RADIOLIB_ERR_SPI_WRITE_FAILED (-16)
|
|
#define RADIOLIB_SX126X_IRQ_RX_DONE 0x0002U
|
|
#define RADIOLIB_SX126X_IRQ_HEADER_ERR 0x0020U
|
|
#define RADIOLIB_SX126X_IRQ_CRC_ERR 0x0040U
|
|
#define RADIOLIB_SX126X_IRQ_TIMEOUT 0x0200U
|
|
|
|
static uint32_t millis()
|
|
{
|
|
return static_cast<uint32_t>(esp_timer_get_time() / 1000ULL);
|
|
}
|
|
|
|
struct IdfSerialLogAdapter
|
|
{
|
|
template <typename... Args>
|
|
int printf(const char* format, Args... args) const
|
|
{
|
|
return std::printf(format, args...);
|
|
}
|
|
};
|
|
|
|
static constexpr IdfSerialLogAdapter Serial{};
|
|
#endif
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <esp_heap_caps.h>
|
|
|
|
#ifndef LORA_LOG_ENABLE
|
|
#define LORA_LOG_ENABLE 0
|
|
#endif
|
|
|
|
#if LORA_LOG_ENABLE
|
|
#define LORA_LOG(...) Serial.printf(__VA_ARGS__)
|
|
#else
|
|
#define LORA_LOG(...) \
|
|
do \
|
|
{ \
|
|
} while (0)
|
|
#endif
|
|
|
|
namespace app
|
|
{
|
|
|
|
namespace
|
|
{
|
|
constexpr TickType_t kRadioPollDelay = pdMS_TO_TICKS(10);
|
|
constexpr TickType_t kRadioDisplayPressurePollDelay = pdMS_TO_TICKS(50);
|
|
constexpr TickType_t kRadioForegroundPollDelay = pdMS_TO_TICKS(80);
|
|
constexpr uint32_t kRadioDisplayPressureWindowMs = 300;
|
|
constexpr uint32_t kRadioTaskStackBytes = 3 * 1024;
|
|
constexpr uint32_t kMeshTaskStackBytes = 8 * 1024;
|
|
constexpr uint32_t kRadioRxSummaryIntervalMs = 5000;
|
|
constexpr uint32_t kRadioForegroundRxSummaryIntervalMs = 15000;
|
|
constexpr uint32_t kRadioForegroundPostRxQuietMs = 120;
|
|
constexpr uint32_t kRadioTxRetryInitialMs = 25;
|
|
constexpr uint32_t kRadioTxRetryMaxMs = 400;
|
|
|
|
struct RadioRxSummary
|
|
{
|
|
uint32_t packets = 0;
|
|
uint32_t bytes = 0;
|
|
uint32_t queue_drops = 0;
|
|
uint32_t alloc_drops = 0;
|
|
uint32_t read_failures = 0;
|
|
uint32_t other_irqs = 0;
|
|
uint32_t last_log_ms = 0;
|
|
};
|
|
|
|
bool display_spi_pressure_for_radio()
|
|
{
|
|
return ::platform::esp::common::display_spi_recently_timed_out(
|
|
millis(),
|
|
kRadioDisplayPressureWindowMs);
|
|
}
|
|
|
|
bool foreground_ui_pressure_for_radio()
|
|
{
|
|
return !::platform::ui::screen::is_sleeping() ||
|
|
::platform::ui::screen::is_saver_active();
|
|
}
|
|
|
|
TickType_t radio_idle_poll_delay()
|
|
{
|
|
if (foreground_ui_pressure_for_radio())
|
|
{
|
|
return kRadioForegroundPollDelay;
|
|
}
|
|
return display_spi_pressure_for_radio()
|
|
? kRadioDisplayPressurePollDelay
|
|
: kRadioPollDelay;
|
|
}
|
|
|
|
bool radio_rx_quiet_window_active(uint32_t now_ms, uint32_t quiet_until_ms)
|
|
{
|
|
return quiet_until_ms != 0 &&
|
|
static_cast<int32_t>(quiet_until_ms - now_ms) > 0;
|
|
}
|
|
|
|
bool deadline_reached(uint32_t now_ms, uint32_t deadline_ms)
|
|
{
|
|
return deadline_ms == 0U ||
|
|
static_cast<int32_t>(now_ms - deadline_ms) >= 0;
|
|
}
|
|
|
|
uint32_t radio_tx_retry_delay_ms(uint8_t retry_count)
|
|
{
|
|
const uint8_t shift = retry_count > 4U ? 4U : retry_count;
|
|
const uint32_t delay_ms = kRadioTxRetryInitialMs << shift;
|
|
return std::min(delay_ms, kRadioTxRetryMaxMs);
|
|
}
|
|
|
|
bool transient_radio_tx_failure(int state)
|
|
{
|
|
return state == RADIOLIB_ERR_SPI_WRITE_FAILED;
|
|
}
|
|
|
|
void drain_radio_packet_queue(QueueHandle_t queue)
|
|
{
|
|
if (!queue)
|
|
{
|
|
return;
|
|
}
|
|
AppTasks::RadioPacket packet{};
|
|
while (xQueueReceive(queue, &packet, 0) == pdPASS)
|
|
{
|
|
if (packet.data)
|
|
{
|
|
heap_caps_free(packet.data);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t* allocate_radio_packet_buffer(std::size_t size)
|
|
{
|
|
const uint32_t caps =
|
|
heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0
|
|
? MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT
|
|
: MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT;
|
|
return static_cast<uint8_t*>(heap_caps_malloc(size, caps));
|
|
}
|
|
|
|
uint32_t radio_rx_done_mask()
|
|
{
|
|
uint32_t mask = 0;
|
|
#if defined(RADIOLIB_SX126X_IRQ_RX_DONE)
|
|
mask |= RADIOLIB_SX126X_IRQ_RX_DONE;
|
|
#endif
|
|
#if defined(RADIOLIB_SX128X_IRQ_RX_DONE)
|
|
mask |= RADIOLIB_SX128X_IRQ_RX_DONE;
|
|
#endif
|
|
#if defined(ARDUINO_LILYGO_LORA_LR1121) && defined(RADIOLIB_LR11X0_IRQ_RX_DONE)
|
|
mask |= RADIOLIB_LR11X0_IRQ_RX_DONE;
|
|
#endif
|
|
return mask;
|
|
}
|
|
|
|
uint32_t radio_terminal_irq_mask()
|
|
{
|
|
uint32_t mask = radio_rx_done_mask();
|
|
#if defined(RADIOLIB_SX126X_IRQ_CRC_ERR)
|
|
mask |= RADIOLIB_SX126X_IRQ_CRC_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_SX126X_IRQ_HEADER_ERR)
|
|
mask |= RADIOLIB_SX126X_IRQ_HEADER_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_SX126X_IRQ_TIMEOUT)
|
|
mask |= RADIOLIB_SX126X_IRQ_TIMEOUT;
|
|
#endif
|
|
#if defined(RADIOLIB_SX128X_IRQ_CRC_ERR)
|
|
mask |= RADIOLIB_SX128X_IRQ_CRC_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_SX128X_IRQ_HEADER_ERR)
|
|
mask |= RADIOLIB_SX128X_IRQ_HEADER_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_SX128X_IRQ_TIMEOUT)
|
|
mask |= RADIOLIB_SX128X_IRQ_TIMEOUT;
|
|
#endif
|
|
#if defined(ARDUINO_LILYGO_LORA_LR1121)
|
|
#if defined(RADIOLIB_LR11X0_IRQ_CRC_ERR)
|
|
mask |= RADIOLIB_LR11X0_IRQ_CRC_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_LR11X0_IRQ_HEADER_ERR)
|
|
mask |= RADIOLIB_LR11X0_IRQ_HEADER_ERR;
|
|
#endif
|
|
#if defined(RADIOLIB_LR11X0_IRQ_TIMEOUT)
|
|
mask |= RADIOLIB_LR11X0_IRQ_TIMEOUT;
|
|
#endif
|
|
#endif
|
|
return mask;
|
|
}
|
|
|
|
bool radio_read_state_has_payload(int state)
|
|
{
|
|
if (state == RADIOLIB_ERR_NONE)
|
|
{
|
|
return true;
|
|
}
|
|
#if defined(RADIOLIB_ERR_CRC_MISMATCH)
|
|
// RadioLib drivers can report CRC mismatch after the FIFO payload has
|
|
// already been copied. Let protocol-level parsers/signatures decide
|
|
// whether the bytes are usable instead of dropping them before the adapter.
|
|
return state == RADIOLIB_ERR_CRC_MISMATCH;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void maybe_log_radio_rx_summary(RadioRxSummary& summary)
|
|
{
|
|
#if LORA_LOG_ENABLE
|
|
const uint32_t now_ms = millis();
|
|
const uint32_t interval_ms = foreground_ui_pressure_for_radio()
|
|
? kRadioForegroundRxSummaryIntervalMs
|
|
: kRadioRxSummaryIntervalMs;
|
|
if (summary.last_log_ms == 0)
|
|
{
|
|
summary.last_log_ms = now_ms;
|
|
}
|
|
if ((now_ms - summary.last_log_ms) < interval_ms)
|
|
{
|
|
return;
|
|
}
|
|
if (summary.packets != 0 || summary.queue_drops != 0 || summary.alloc_drops != 0 ||
|
|
summary.read_failures != 0 || summary.other_irqs != 0)
|
|
{
|
|
Serial.printf("[LORA] RX stats packets=%lu bytes=%lu queue_drop=%lu alloc_drop=%lu read_fail=%lu other_irq=%lu\n",
|
|
static_cast<unsigned long>(summary.packets),
|
|
static_cast<unsigned long>(summary.bytes),
|
|
static_cast<unsigned long>(summary.queue_drops),
|
|
static_cast<unsigned long>(summary.alloc_drops),
|
|
static_cast<unsigned long>(summary.read_failures),
|
|
static_cast<unsigned long>(summary.other_irqs));
|
|
}
|
|
summary = RadioRxSummary{};
|
|
summary.last_log_ms = now_ms;
|
|
#else
|
|
(void)summary;
|
|
#endif
|
|
}
|
|
} // namespace
|
|
|
|
// Static members
|
|
QueueHandle_t AppTasks::radio_tx_queue_ = nullptr;
|
|
QueueHandle_t AppTasks::radio_rx_queue_ = nullptr;
|
|
QueueHandle_t AppTasks::mesh_queue_ = nullptr;
|
|
TaskHandle_t AppTasks::radio_task_handle_ = nullptr;
|
|
TaskHandle_t AppTasks::mesh_task_handle_ = nullptr;
|
|
LoraBoard* AppTasks::board_ = nullptr;
|
|
chat::IMeshAdapter* AppTasks::adapter_ = nullptr;
|
|
uint8_t* AppTasks::radio_rx_scratch_ = nullptr;
|
|
volatile bool AppTasks::radio_tasks_paused_ = false;
|
|
volatile bool AppTasks::radio_receive_active_ = false;
|
|
volatile bool AppTasks::radio_receive_restart_pending_ = true;
|
|
volatile bool AppTasks::radio_receive_suppressed_ = false;
|
|
volatile bool AppTasks::radio_transmit_active_ = false;
|
|
volatile bool AppTasks::radio_task_quiesced_ = false;
|
|
volatile bool AppTasks::mesh_task_quiesced_ = false;
|
|
|
|
bool AppTasks::init(LoraBoard& board, chat::IMeshAdapter* adapter)
|
|
{
|
|
board_ = &board;
|
|
adapter_ = adapter;
|
|
radio_tasks_paused_ = false;
|
|
radio_task_quiesced_ = false;
|
|
mesh_task_quiesced_ = false;
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = !radio_receive_suppressed_;
|
|
if (!radio_rx_scratch_)
|
|
{
|
|
radio_rx_scratch_ = allocate_radio_packet_buffer(255U);
|
|
}
|
|
if (!radio_rx_scratch_)
|
|
{
|
|
Serial.printf("[LORA] RX scratch allocation failed bytes=255\n");
|
|
return false;
|
|
}
|
|
|
|
// Create queues
|
|
radio_tx_queue_ = xQueueCreate(RADIO_QUEUE_SIZE, sizeof(RadioPacket));
|
|
radio_rx_queue_ = xQueueCreate(RADIO_QUEUE_SIZE, sizeof(RadioPacket));
|
|
mesh_queue_ = xQueueCreate(MESH_QUEUE_SIZE, sizeof(RadioPacket));
|
|
|
|
if (!radio_tx_queue_ || !radio_rx_queue_ || !mesh_queue_)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Create radio task (high priority)
|
|
BaseType_t result = xTaskCreate(
|
|
radioTask,
|
|
"radio_task",
|
|
kRadioTaskStackBytes,
|
|
nullptr,
|
|
10, // High priority
|
|
&radio_task_handle_);
|
|
|
|
if (result != pdPASS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Create mesh task (medium priority)
|
|
result = xTaskCreate(
|
|
meshTask,
|
|
"mesh_task",
|
|
kMeshTaskStackBytes,
|
|
nullptr,
|
|
5, // Medium priority
|
|
&mesh_task_handle_);
|
|
|
|
return (result == pdPASS);
|
|
}
|
|
|
|
bool AppTasks::pauseRadioTasks(uint32_t timeout_ms)
|
|
{
|
|
if (radio_tasks_paused_)
|
|
{
|
|
return (radio_task_handle_ == nullptr || radio_task_quiesced_) &&
|
|
(mesh_task_handle_ == nullptr || mesh_task_quiesced_);
|
|
}
|
|
const TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
|
if (current_task == radio_task_handle_ || current_task == mesh_task_handle_)
|
|
{
|
|
Serial.printf("[LORA] task quiesce rejected caller=owned_task\n");
|
|
return false;
|
|
}
|
|
|
|
radio_tasks_paused_ = true;
|
|
requestRadioReceiveRestart();
|
|
const uint32_t start_ms = millis();
|
|
while ((radio_task_handle_ != nullptr && !radio_task_quiesced_) ||
|
|
(mesh_task_handle_ != nullptr && !mesh_task_quiesced_))
|
|
{
|
|
if (timeout_ms != 0U && millis() - start_ms >= timeout_ms)
|
|
{
|
|
radio_tasks_paused_ = false;
|
|
Serial.printf("[LORA] task quiesce timeout radio=%u mesh=%u wait_ms=%lu\n",
|
|
radio_task_quiesced_ ? 1U : 0U,
|
|
mesh_task_quiesced_ ? 1U : 0U,
|
|
static_cast<unsigned long>(millis() - start_ms));
|
|
return false;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
}
|
|
|
|
if (radio_tx_queue_)
|
|
{
|
|
drain_radio_packet_queue(radio_tx_queue_);
|
|
}
|
|
if (radio_rx_queue_)
|
|
{
|
|
drain_radio_packet_queue(radio_rx_queue_);
|
|
}
|
|
if (mesh_queue_)
|
|
{
|
|
drain_radio_packet_queue(mesh_queue_);
|
|
}
|
|
Serial.printf("[LORA] tasks quiesced wait_ms=%lu\n",
|
|
static_cast<unsigned long>(millis() - start_ms));
|
|
return true;
|
|
}
|
|
|
|
void AppTasks::resumeRadioTasks()
|
|
{
|
|
if (!radio_tasks_paused_)
|
|
{
|
|
return;
|
|
}
|
|
radio_tasks_paused_ = false;
|
|
|
|
requestRadioReceiveRestart();
|
|
}
|
|
|
|
void AppTasks::setRadioReceiveActive(bool active)
|
|
{
|
|
if (radio_receive_suppressed_ && active)
|
|
{
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = false;
|
|
return;
|
|
}
|
|
radio_receive_active_ = active;
|
|
radio_receive_restart_pending_ = !active;
|
|
}
|
|
|
|
void AppTasks::requestRadioReceiveRestart()
|
|
{
|
|
if (radio_receive_suppressed_)
|
|
{
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = false;
|
|
return;
|
|
}
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = true;
|
|
}
|
|
|
|
void AppTasks::setRadioReceiveSuppressed(bool suppressed)
|
|
{
|
|
if (radio_receive_suppressed_ == suppressed)
|
|
{
|
|
return;
|
|
}
|
|
radio_receive_suppressed_ = suppressed;
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = !suppressed;
|
|
Serial.printf("[LORA] RX gate %s\n", suppressed ? "suppressed" : "enabled");
|
|
}
|
|
|
|
bool AppTasks::isRadioReceiveSuppressed()
|
|
{
|
|
return radio_receive_suppressed_;
|
|
}
|
|
|
|
void AppTasks::setRadioTransmitActive(bool active)
|
|
{
|
|
radio_transmit_active_ = active;
|
|
if (active)
|
|
{
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = false;
|
|
}
|
|
else
|
|
{
|
|
radio_receive_restart_pending_ = !radio_receive_suppressed_;
|
|
}
|
|
}
|
|
|
|
bool AppTasks::isRadioTransmitActive()
|
|
{
|
|
return radio_transmit_active_;
|
|
}
|
|
|
|
bool AppTasks::enqueueRadioTransmit(const uint8_t* data, size_t size)
|
|
{
|
|
if (!data || size == 0 || size > 255 || !radio_tx_queue_)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint8_t* copy = allocate_radio_packet_buffer(size);
|
|
if (!copy)
|
|
{
|
|
return false;
|
|
}
|
|
std::memcpy(copy, data, size);
|
|
|
|
RadioPacket packet{};
|
|
packet.data = copy;
|
|
packet.size = size;
|
|
packet.is_tx = true;
|
|
packet.queued_ms = millis();
|
|
if (xQueueSend(radio_tx_queue_, &packet, 0) != pdPASS)
|
|
{
|
|
heap_caps_free(copy);
|
|
LORA_LOG("[LORA] TX queue full len=%u\n", static_cast<unsigned>(size));
|
|
return false;
|
|
}
|
|
|
|
requestRadioReceiveRestart();
|
|
return true;
|
|
}
|
|
|
|
AppTasks::ScopedRadioTransmitActivity::ScopedRadioTransmitActivity()
|
|
{
|
|
AppTasks::setRadioTransmitActive(true);
|
|
}
|
|
|
|
AppTasks::ScopedRadioTransmitActivity::~ScopedRadioTransmitActivity()
|
|
{
|
|
AppTasks::setRadioTransmitActive(false);
|
|
}
|
|
|
|
void AppTasks::radioTask(void* pvParameters)
|
|
{
|
|
(void)pvParameters;
|
|
|
|
const uint32_t rx_done_mask = radio_rx_done_mask();
|
|
const uint32_t terminal_irq_mask = radio_terminal_irq_mask();
|
|
RadioRxSummary rx_summary{};
|
|
uint32_t foreground_rx_quiet_until_ms = 0;
|
|
|
|
while (true)
|
|
{
|
|
if (radio_tasks_paused_)
|
|
{
|
|
radio_task_quiesced_ = true;
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
continue;
|
|
}
|
|
radio_task_quiesced_ = false;
|
|
|
|
if (::platform::ui::reticulum_call::realtime_mode_active())
|
|
{
|
|
requestRadioReceiveRestart();
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
continue;
|
|
}
|
|
|
|
bool should_restart_rx = radio_receive_restart_pending_;
|
|
bool handled_tx = false;
|
|
bool deferred_tx = false;
|
|
|
|
if (radio_transmit_active_)
|
|
{
|
|
vTaskDelay(kRadioDisplayPressurePollDelay);
|
|
continue;
|
|
}
|
|
|
|
// Process TX queue
|
|
RadioPacket tx_packet{};
|
|
const uint32_t tx_now_ms = millis();
|
|
RadioPacket queued_packet{};
|
|
const bool tx_ready =
|
|
xQueuePeek(radio_tx_queue_, &queued_packet, 0) == pdPASS &&
|
|
deadline_reached(tx_now_ms, queued_packet.next_attempt_ms);
|
|
if (tx_ready &&
|
|
xQueueReceive(radio_tx_queue_, &tx_packet, 0) == pdPASS)
|
|
{
|
|
handled_tx = tx_packet.is_tx && tx_packet.data && tx_packet.size > 0;
|
|
if (tx_packet.is_tx && tx_packet.data && tx_packet.size > 0)
|
|
{
|
|
bool keep_packet = false;
|
|
// Send packet
|
|
if (board_ && board_->isRadioOnline())
|
|
{
|
|
requestRadioReceiveRestart();
|
|
int state = RADIOLIB_ERR_NONE;
|
|
setRadioTransmitActive(true);
|
|
state = board_->transmitRadio(tx_packet.data, tx_packet.size);
|
|
setRadioTransmitActive(false);
|
|
LORA_LOG("[LORA] TX queue len=%u state=%d\n", (unsigned)tx_packet.size, state);
|
|
if (transient_radio_tx_failure(state))
|
|
{
|
|
if (tx_packet.retry_count != UINT8_MAX)
|
|
{
|
|
++tx_packet.retry_count;
|
|
}
|
|
tx_packet.next_attempt_ms =
|
|
millis() + radio_tx_retry_delay_ms(tx_packet.retry_count);
|
|
if (xQueueSendToFront(radio_tx_queue_, &tx_packet, 0) == pdPASS)
|
|
{
|
|
keep_packet = true;
|
|
deferred_tx = true;
|
|
LORA_LOG("[LORA] TX deferred len=%u state=%d retry=%u age_ms=%lu\n",
|
|
static_cast<unsigned>(tx_packet.size),
|
|
state,
|
|
static_cast<unsigned>(tx_packet.retry_count),
|
|
static_cast<unsigned long>(millis() - tx_packet.queued_ms));
|
|
if (tx_packet.retry_count == 1U ||
|
|
(tx_packet.retry_count % 16U) == 0U)
|
|
{
|
|
Serial.printf("[LORA] TX deferred reason=spi_busy len=%u retry=%u age_ms=%lu\n",
|
|
static_cast<unsigned>(tx_packet.size),
|
|
static_cast<unsigned>(tx_packet.retry_count),
|
|
static_cast<unsigned long>(millis() - tx_packet.queued_ms));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Serial.printf("[LORA] TX retry queue lost len=%u state=%d retry=%u\n",
|
|
static_cast<unsigned>(tx_packet.size),
|
|
state,
|
|
static_cast<unsigned>(tx_packet.retry_count));
|
|
}
|
|
}
|
|
else if (state == RADIOLIB_ERR_NONE &&
|
|
tx_packet.retry_count != 0U)
|
|
{
|
|
Serial.printf("[LORA] TX recovered len=%u retries=%u age_ms=%lu\n",
|
|
static_cast<unsigned>(tx_packet.size),
|
|
static_cast<unsigned>(tx_packet.retry_count),
|
|
static_cast<unsigned long>(millis() - tx_packet.queued_ms));
|
|
}
|
|
else if (state != RADIOLIB_ERR_NONE)
|
|
{
|
|
Serial.printf("[LORA] TX failed len=%u state=%d age_ms=%lu\n",
|
|
static_cast<unsigned>(tx_packet.size),
|
|
state,
|
|
static_cast<unsigned long>(millis() - tx_packet.queued_ms));
|
|
}
|
|
if (state == RADIOLIB_ERR_NONE && !radio_receive_suppressed_)
|
|
{
|
|
int rx_state = board_->startRadioReceive();
|
|
if (rx_state == RADIOLIB_ERR_NONE)
|
|
{
|
|
setRadioReceiveActive(true);
|
|
should_restart_rx = false;
|
|
}
|
|
else
|
|
{
|
|
requestRadioReceiveRestart();
|
|
LORA_LOG("[LORA] RX start fail state=%d\n", rx_state);
|
|
}
|
|
}
|
|
else if (state == RADIOLIB_ERR_NONE)
|
|
{
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = false;
|
|
}
|
|
else
|
|
{
|
|
requestRadioReceiveRestart();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LORA_LOG("[LORA] TX drop (radio offline) len=%u\n", (unsigned)tx_packet.size);
|
|
}
|
|
if (!keep_packet)
|
|
{
|
|
heap_caps_free(tx_packet.data);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deferred_tx)
|
|
{
|
|
maybe_log_radio_rx_summary(rx_summary);
|
|
vTaskDelay(kRadioDisplayPressurePollDelay);
|
|
continue;
|
|
}
|
|
|
|
if (radio_receive_suppressed_)
|
|
{
|
|
radio_receive_active_ = false;
|
|
radio_receive_restart_pending_ = false;
|
|
maybe_log_radio_rx_summary(rx_summary);
|
|
vTaskDelay(kRadioDisplayPressurePollDelay);
|
|
continue;
|
|
}
|
|
|
|
if (!handled_tx && foreground_ui_pressure_for_radio() &&
|
|
radio_rx_quiet_window_active(millis(), foreground_rx_quiet_until_ms))
|
|
{
|
|
maybe_log_radio_rx_summary(rx_summary);
|
|
vTaskDelay(kRadioForegroundPollDelay);
|
|
continue;
|
|
}
|
|
|
|
// Poll for RX (non-blocking)
|
|
if (board_ && board_->isRadioOnline())
|
|
{
|
|
if (!radio_receive_active_ || radio_receive_restart_pending_ || should_restart_rx)
|
|
{
|
|
int rx_state = board_->startRadioReceive();
|
|
if (rx_state == RADIOLIB_ERR_NONE)
|
|
{
|
|
setRadioReceiveActive(true);
|
|
should_restart_rx = false;
|
|
}
|
|
else
|
|
{
|
|
requestRadioReceiveRestart();
|
|
LORA_LOG("[LORA] RX start fail state=%d\n", rx_state);
|
|
}
|
|
}
|
|
const bool display_pressure = display_spi_pressure_for_radio();
|
|
if (display_pressure &&
|
|
!handled_tx &&
|
|
radio_receive_active_ &&
|
|
!radio_receive_restart_pending_ &&
|
|
!should_restart_rx)
|
|
{
|
|
vTaskDelay(kRadioDisplayPressurePollDelay);
|
|
continue;
|
|
}
|
|
// Check if data available using RadioLib IRQs
|
|
int packet_length = 0;
|
|
bool handled_rx_activity = false;
|
|
uint32_t irq = board_->getRadioIrqFlags();
|
|
if ((irq & rx_done_mask) != 0)
|
|
{
|
|
handled_rx_activity = true;
|
|
packet_length = static_cast<int>(board_->getRadioPacketLength(true));
|
|
if (packet_length > 0 && packet_length <= 255)
|
|
{
|
|
int state = board_->readRadioData(radio_rx_scratch_, packet_length);
|
|
if (radio_read_state_has_payload(state))
|
|
{
|
|
RadioPacket rx_packet;
|
|
rx_packet.data =
|
|
allocate_radio_packet_buffer(packet_length);
|
|
if (rx_packet.data)
|
|
{
|
|
memcpy(rx_packet.data,
|
|
radio_rx_scratch_,
|
|
packet_length);
|
|
rx_packet.size = packet_length;
|
|
rx_packet.is_tx = false;
|
|
rx_packet.rssi = board_->getRadioRSSI();
|
|
rx_packet.snr = board_->getRadioSNR();
|
|
|
|
++rx_summary.packets;
|
|
rx_summary.bytes += static_cast<uint32_t>(packet_length);
|
|
if (xQueueSend(mesh_queue_, &rx_packet, 0) != pdPASS)
|
|
{
|
|
heap_caps_free(rx_packet.data);
|
|
++rx_summary.queue_drops;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++rx_summary.alloc_drops;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++rx_summary.read_failures;
|
|
board_->clearRadioIrqFlags(irq);
|
|
}
|
|
}
|
|
board_->clearRadioIrqFlags(irq);
|
|
should_restart_rx = true;
|
|
}
|
|
else if (irq)
|
|
{
|
|
handled_rx_activity = true;
|
|
++rx_summary.other_irqs;
|
|
board_->clearRadioIrqFlags(irq);
|
|
if ((irq & terminal_irq_mask) != 0)
|
|
{
|
|
should_restart_rx = true;
|
|
}
|
|
}
|
|
if (packet_length > 0 || should_restart_rx)
|
|
{
|
|
requestRadioReceiveRestart();
|
|
int rx_state = board_->startRadioReceive();
|
|
if (rx_state == RADIOLIB_ERR_NONE)
|
|
{
|
|
setRadioReceiveActive(true);
|
|
}
|
|
else
|
|
{
|
|
requestRadioReceiveRestart();
|
|
LORA_LOG("[LORA] RX restart fail state=%d\n", rx_state);
|
|
}
|
|
}
|
|
if (handled_rx_activity && foreground_ui_pressure_for_radio())
|
|
{
|
|
foreground_rx_quiet_until_ms = millis() + kRadioForegroundPostRxQuietMs;
|
|
}
|
|
maybe_log_radio_rx_summary(rx_summary);
|
|
}
|
|
|
|
vTaskDelay(radio_idle_poll_delay());
|
|
}
|
|
}
|
|
|
|
void AppTasks::meshTask(void* pvParameters)
|
|
{
|
|
(void)pvParameters;
|
|
|
|
const TickType_t poll_delay = pdMS_TO_TICKS(50);
|
|
|
|
while (true)
|
|
{
|
|
if (radio_tasks_paused_)
|
|
{
|
|
mesh_task_quiesced_ = true;
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
continue;
|
|
}
|
|
mesh_task_quiesced_ = false;
|
|
|
|
// Process received packets
|
|
RadioPacket rx_packet;
|
|
if (::platform::ui::reticulum_call::realtime_mode_active())
|
|
{
|
|
while (xQueueReceive(mesh_queue_, &rx_packet, 0) == pdPASS)
|
|
{
|
|
if (rx_packet.data)
|
|
{
|
|
heap_caps_free(rx_packet.data);
|
|
}
|
|
}
|
|
if (adapter_)
|
|
{
|
|
adapter_->processSendQueue();
|
|
}
|
|
vTaskDelay(poll_delay);
|
|
continue;
|
|
}
|
|
if (xQueueReceive(mesh_queue_, &rx_packet, 0) == pdPASS)
|
|
{
|
|
if (!rx_packet.is_tx && rx_packet.data && adapter_)
|
|
{
|
|
// Decode and process through configured mesh adapter
|
|
adapter_->setLastRxStats(rx_packet.rssi, rx_packet.snr);
|
|
adapter_->handleRawPacket(rx_packet.data, rx_packet.size);
|
|
|
|
// Free buffer
|
|
heap_caps_free(rx_packet.data);
|
|
}
|
|
}
|
|
|
|
// Process send queue in adapter
|
|
if (adapter_)
|
|
{
|
|
adapter_->processSendQueue();
|
|
}
|
|
|
|
vTaskDelay(poll_delay);
|
|
}
|
|
}
|
|
|
|
} // namespace app
|