Files
pyxis/lib/sx1262_interface/SX1262Interface.cpp
torlando-agent[bot] 70d4aa6be9 feat: graft pyxis onto upstream microReticulum 0.4.1
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts
pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged
0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's
reimplementation; only the still-needed fixes ride on the pinned branches
(PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 --
Packet link-proof callback, Identity short-sig guard, and the bz2 layer +
decompress-on-receive in Resource::assemble()).

Consumer-side changes:
- platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and
  microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore
  ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API);
  -std=gnu++11 -> gnu++17 (upstream requires C++17).
- Namespace all microReticulum includes (angle + quote) to <microReticulum/...>
  for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved.
- Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto
  overrides with correct success/failure returns.
- SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore.
- Static Transport::get_path_table() -> path_table(); instance getter unchanged.
- Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep
  lib/libbz2 as the ESP32 bzlib provider).
- patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a
  leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which
  silently broke the path store (no peer paths learned, all messaging blocked).

Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable
(no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC +
bz2-compressed-Resource receive) passes 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-19 15:49:44 -04:00

306 lines
9.3 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#include "SX1262Interface.h"
#include <microReticulum/Log.h>
#include <microReticulum/Utilities/OS.h>
#ifdef ARDUINO
#include <SPI.h>
#endif
using namespace RNS;
#ifdef ARDUINO
// Static members for SPI mutex (shared with display and SD card)
SemaphoreHandle_t SX1262Interface::_spi_mutex = nullptr;
bool SX1262Interface::_mutex_initialized = false;
void SX1262Interface::set_spi_mutex(SemaphoreHandle_t mutex) {
_spi_mutex = mutex;
_mutex_initialized = (mutex != nullptr);
if (_mutex_initialized) {
DEBUG("SX1262Interface: Using external SPI mutex");
}
}
#endif
SX1262Interface::SX1262Interface(const char* name) : InterfaceImpl(name) {
_IN = true;
_OUT = true;
_HW_MTU = HW_MTU;
_AUTOCONFIGURE_MTU = true;
// Calculate bitrate from modulation parameters (matching Python RNS formula)
// bitrate = sf * ((4.0/cr) / (2^sf / (bw/1000))) * 1000
_bitrate = (double)_config.spreading_factor *
((4.0 / _config.coding_rate) /
(pow(2, _config.spreading_factor) / (_config.bandwidth / 1000.0))) * 1000.0;
}
SX1262Interface::~SX1262Interface() {
stop();
}
void SX1262Interface::set_config(const SX1262Config& config) {
_config = config;
// Recalculate bitrate
_bitrate = (double)_config.spreading_factor *
((4.0 / _config.coding_rate) /
(pow(2, _config.spreading_factor) / (_config.bandwidth / 1000.0))) * 1000.0;
}
std::string SX1262Interface::toString() const {
return "SX1262Interface[" + _name + "]";
}
bool SX1262Interface::start() {
_online = false;
#ifdef ARDUINO
INFO("SX1262Interface: Initializing...");
INFO(" Frequency: " + std::to_string(_config.frequency) + " MHz");
INFO(" Bandwidth: " + std::to_string(_config.bandwidth) + " kHz");
INFO(" SF: " + std::to_string(_config.spreading_factor));
INFO(" CR: 4/" + std::to_string(_config.coding_rate));
INFO(" TX Power: " + std::to_string(_config.tx_power) + " dBm");
// Use external mutex if provided, otherwise create our own (fallback)
if (!_mutex_initialized) {
WARNING("SX1262Interface: No external SPI mutex set, creating own");
_spi_mutex = xSemaphoreCreateMutex();
if (_spi_mutex == nullptr) {
ERROR("SX1262Interface: Failed to create SPI mutex");
return false;
}
_mutex_initialized = true;
}
// Acquire SPI mutex
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
ERROR("SX1262Interface: Failed to acquire SPI mutex for init");
return false;
}
// Set radio CS high to avoid conflicts
pinMode(SX1262Pins::CS, OUTPUT);
digitalWrite(SX1262Pins::CS, HIGH);
// Use global SPI (FSPI) — all peripherals share same SPI peripheral
// to avoid GPIO matrix conflicts. SPI.begin() already called by SDAccess or Display.
_lora_spi = &SPI;
DEBUG("SX1262Interface: Using global SPI (FSPI) for LoRa");
// Create RadioLib module and radio with the shared SPI instance
_module = new Module(SX1262Pins::CS, SX1262Pins::DIO1, SX1262Pins::RST, SX1262Pins::BUSY, *_lora_spi);
_radio = new SX1262(_module);
// Initialize radio with configuration
int16_t state = _radio->begin(
_config.frequency,
_config.bandwidth,
_config.spreading_factor,
_config.coding_rate,
_config.sync_word,
_config.tx_power,
_config.preamble_length
);
if (state != RADIOLIB_ERR_NONE) {
ERROR("SX1262Interface: Radio init failed, code " + std::to_string(state));
xSemaphoreGive(_spi_mutex);
delete _radio;
delete _module;
_radio = nullptr;
_module = nullptr;
return false;
}
// Enable CRC for error detection
state = _radio->setCRC(true);
if (state != RADIOLIB_ERR_NONE) {
WARNING("SX1262Interface: Failed to enable CRC, code " + std::to_string(state));
}
// Use explicit header mode (includes length in LoRa header)
state = _radio->explicitHeader();
if (state != RADIOLIB_ERR_NONE) {
WARNING("SX1262Interface: Failed to set explicit header, code " + std::to_string(state));
}
xSemaphoreGive(_spi_mutex);
// Start listening for packets
start_receive();
_online = true;
INFO("SX1262Interface: Initialized successfully");
INFO(" Bitrate: " + std::to_string(Utilities::OS::round(_bitrate / 1000.0, 2)) + " kbps");
return true;
#else
ERROR("SX1262Interface: Not supported on this platform");
return false;
#endif
}
void SX1262Interface::stop() {
#ifdef ARDUINO
if (_radio != nullptr) {
if (_spi_mutex != nullptr && xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
_radio->standby();
xSemaphoreGive(_spi_mutex);
}
delete _radio;
delete _module;
_radio = nullptr;
_module = nullptr;
}
#endif
_online = false;
INFO("SX1262Interface: Stopped");
}
void SX1262Interface::start_receive() {
#ifdef ARDUINO
if (_radio == nullptr) return;
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
int16_t state = _radio->startReceive();
xSemaphoreGive(_spi_mutex);
if (state != RADIOLIB_ERR_NONE) {
ERROR("SX1262Interface: Failed to start receive, code " + std::to_string(state));
}
} else {
ERROR("SX1262Interface: Failed to acquire SPI mutex for start_receive!");
}
#endif
}
void SX1262Interface::loop() {
if (!_online) return;
#ifdef ARDUINO
if (_radio == nullptr) return;
// Try to acquire SPI mutex (non-blocking to avoid stalling display)
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(5)) != pdTRUE) {
return; // Display is using SPI, try again later
}
// Check IRQ status to see if a packet was actually received
uint16_t irqStatus = _radio->getIrqStatus();
// Only process if RX_DONE flag is set (0x0002 for SX126x)
if (!(irqStatus & 0x0002)) {
xSemaphoreGive(_spi_mutex);
return; // No new packet
}
// Read the received packet (this also clears IRQ internally)
int16_t state = _radio->readData(_rx_buffer.writable(HW_MTU), HW_MTU);
// Immediately restart receive to clear IRQ flags and prepare for next packet
_radio->startReceive();
if (state == RADIOLIB_ERR_NONE) {
// Got a packet
size_t len = _radio->getPacketLength();
if (len > 1) { // Must have at least header + data
_rx_buffer.resize(len);
// Get signal quality
_last_rssi = _radio->getRSSI();
_last_snr = _radio->getSNR();
xSemaphoreGive(_spi_mutex);
// RNode packet format: [1-byte random header][payload]
// Skip header byte, pass payload to transport
Bytes payload = _rx_buffer.mid(1);
DEBUG("SX1262Interface: Received " + std::to_string(len) + " bytes, " +
"RSSI=" + std::to_string((int)_last_rssi) + " dBm, " +
"SNR=" + std::to_string((int)_last_snr) + " dB");
on_incoming(payload);
return;
}
} else if (state != RADIOLIB_ERR_RX_TIMEOUT) {
// An error occurred (not just timeout)
ERROR("SX1262Interface: Receive error, code " + std::to_string(state));
}
xSemaphoreGive(_spi_mutex);
#endif
}
bool SX1262Interface::send_outgoing(const Bytes& data) {
if (!_online) return false;
#ifdef ARDUINO
if (_radio == nullptr) return false;
DEBUG(toString() + ": Sending " + std::to_string(data.size()) + " bytes");
// Build packet with random header (RNode-compatible format)
// Header: upper 4 bits random, lower 4 bits reserved
uint8_t header = Cryptography::randomnum(256) & 0xF0;
size_t len = 1 + data.size();
if (len > HW_MTU) {
ERROR("SX1262Interface: Packet too large (" + std::to_string(len) + " > " + std::to_string(HW_MTU) + ")");
return false;
}
uint8_t* buf = new uint8_t[len];
buf[0] = header;
memcpy(buf + 1, data.data(), data.size());
// Acquire SPI mutex
if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
ERROR("SX1262Interface: Failed to acquire SPI mutex for TX");
delete[] buf;
return false;
}
_transmitting = true;
// Transmit (blocking)
int16_t state = _radio->transmit(buf, len);
_transmitting = false;
// Return to receive mode immediately while still holding SPI mutex
// (no gap for display task to steal the bus and leave radio in STANDBY)
int16_t rxState = _radio->startReceive();
xSemaphoreGive(_spi_mutex);
delete[] buf;
if (rxState != RADIOLIB_ERR_NONE) {
ERROR("SX1262Interface: Failed to restart receive after TX, code " + std::to_string(rxState));
}
if (state == RADIOLIB_ERR_NONE) {
DEBUG("SX1262Interface: Sent " + std::to_string(len) + " bytes");
// Perform post-send housekeeping
InterfaceImpl::handle_outgoing(data);
return true;
} else {
ERROR("SX1262Interface: Transmit failed, code " + std::to_string(state));
return false;
}
#endif
return false;
}
void SX1262Interface::on_incoming(const Bytes& data) {
DEBUG(toString() + ": Incoming " + std::to_string(data.size()) + " bytes");
// Pass received data to transport
InterfaceImpl::handle_incoming(data);
}