// SPDX-License-Identifier: Apache-2.0 #pragma once #include #include #include #include #include #include #include #include #include #include #include struct Device; struct GpioDescriptor; /** * SX1262 radio engine: owns the radio thread, the TX queue and the callback lists. * The public methods are thread-safe. Callbacks are invoked on a snapshot of the list with * the internal mutex released, either from the radio thread (RX, TX progress, state) or from * the caller of transmit() (QUEUED). * * The RadioLib types live behind the RadioParts indirection: RadioLib declares a global * `class Module` that collides with the kernel's `struct Module` when both are visible * in the same translation unit, so RadioLib headers must not leak out of the implementation. */ class Sx1262Radio final { public: struct Settings { /** The kernel device, passed to callbacks */ Device* device; /** The parent SPI controller device, for the SPI controller bus lock */ Device* spi_controller; spi_host_device_t spi_host; int spi_frequency_hz; // CS/RESET/BUSY are native SoC GPIO numbers: the RadioLib HAL drives them directly // through ESP-IDF, so they can't sit behind an IO expander (unlike enable/antenna-select, // which the driver-registration layer resolves through the GPIO descriptor API). gpio_num_t pin_cs; gpio_num_t pin_reset; gpio_num_t pin_busy; /** DIO1 IRQ line, owned by the driver. The radio thread arms it as a * HIGH_LEVEL one-shot via the GPIO descriptor callback API. */ struct GpioDescriptor* dio1; float tcxo_voltage; bool use_regulator_ldo; bool dio2_rf_switch; }; private: struct RadioParts; struct TxItem { LoraTxId id = 0; std::vector data; }; template struct CallbackEntry { void* context; Callback callback; }; const Settings settings; RadioParts* parts; mutable RecursiveMutex mutex = {}; EventGroupHandle_t events = nullptr; Thread* thread = nullptr; bool threadInterrupted = false; enum LoraRadioState state = LORA_RADIO_STATE_OFF; enum LoraModulation modulation = LORA_MODULATION_NONE; std::deque txQueue; TxItem currentTx; LoraTxId lastTxId = 0; std::vector> stateCallbacks; std::vector> rxCallbacks; std::vector> txCallbacks; // Parameter store, applied on the next doBegin(). Frequencies/rates are held in base SI // units (Hz, bit/s) and converted to RadioLib's MHz/kHz/kbps floats in doBegin(). int8_t power = -9; int32_t frequency = 150000000; // Hz int32_t bandwidth = 0; // Hz uint8_t spreadingFactor = 0; uint8_t codingRate = 0; uint8_t syncWord = 0; uint16_t preambleLength = 0; int32_t bitRate = 0; // bit/s int32_t frequencyDeviation = 0; // Hz bool narrowGrid = false; bool boostedGain = false; // PA over-current protection limit in mA. Default matches RadioLib's fail-safe 60 mA, // which caps output below +22 dBm; a board-aware consumer can raise it (up to 140 mA). int32_t currentLimit = 60; // mA static void dio1Isr(void* context); static int32_t threadMainStatic(void* context); void lock() const { recursive_mutex_lock(&mutex); } void unlock() const { recursive_mutex_unlock(&mutex); } bool isThreadInterrupted() const; int32_t threadMain(); void setState(enum LoraRadioState newState); void publishRx(const struct LoraRxPacket& packet); void publishTx(LoraTxId id, enum LoraTransmissionState txState); size_t getTxQueueSize() const; TxItem popNextQueuedTx(); void registerDio1Isr(); void unregisterDio1Isr(); error_t setBaseParameter(enum LoraParameter parameter, int32_t value); error_t setLoraParameter(enum LoraParameter parameter, int32_t value); error_t setFskParameter(enum LoraParameter parameter, int32_t value); error_t setLrFhssParameter(enum LoraParameter parameter, int32_t value); error_t getBaseParameter(enum LoraParameter parameter, int32_t* value) const; error_t getLoraParameter(enum LoraParameter parameter, int32_t* value) const; error_t getFskParameter(enum LoraParameter parameter, int32_t* value) const; error_t getLrFhssParameter(enum LoraParameter parameter, int32_t* value) const; int doBegin(enum LoraModulation beginModulation); void doEnd(); void doTransmit(); bool doListen(); void doReceive(); public: explicit Sx1262Radio(const Settings& settings); ~Sx1262Radio(); /** * Verify a live SX1262 responds on the wired pins, using only GPIO (no SPI traffic): * pulse NRESET and expect the chip to drive BUSY low once it reaches standby. * @return ERROR_NONE when the chip responded */ error_t probe() const; enum LoraRadioState getState() const; error_t setEnabled(bool enabled); error_t setModulation(enum LoraModulation newModulation); enum LoraModulation getModulation() const; bool canTransmit(enum LoraModulation withModulation) const { return (withModulation == LORA_MODULATION_FSK) || (withModulation == LORA_MODULATION_LORA) || (withModulation == LORA_MODULATION_LR_FHSS); } bool canReceive(enum LoraModulation withModulation) const { return (withModulation == LORA_MODULATION_FSK) || (withModulation == LORA_MODULATION_LORA); } error_t setParameter(enum LoraParameter parameter, int32_t value); error_t getParameter(enum LoraParameter parameter, int32_t* value) const; error_t transmit(const uint8_t* data, size_t length, LoraTxId* id); error_t addRxCallback(void* context, LoraRxCallback callback); error_t removeRxCallback(LoraRxCallback callback); error_t addStateCallback(void* context, LoraStateCallback callback); error_t removeStateCallback(LoraStateCallback callback); error_t addTxCallback(void* context, LoraTxCallback callback); error_t removeTxCallback(LoraTxCallback callback); };