Radio: Refactor RadioDevice thread into compat class

This commit is contained in:
Dominic Höglinger 2025-09-20 05:13:34 +02:00
parent c705359427
commit 98c9fb7201
7 changed files with 230 additions and 209 deletions

View File

@ -0,0 +1,99 @@
#include "RadiolibThreadedDevice.h"
#include <cstring>
constexpr const char* TAG = "RadiolibThreadedDevice";
bool RadiolibThreadedDevice::start(const Modulation modulation) {
auto lock = getMutex().asScopedLock();
if (!isCapableOf(modulation)) {
TT_LOG_E(TAG, "Can't start device \"%s\", not capable of modulation \"%s\"", getName().c_str(), toString(modulation));
return false;
}
lock.lock();
if (thread != nullptr && thread->getState() != tt::Thread::State::Stopped) {
TT_LOG_W(TAG, "Already started");
return true;
}
threadInterrupted = false;
TT_LOG_I(TAG, "Starting thread");
setState(State::PendingOn);
thread = std::make_unique<tt::Thread>(
threadName,
threadSize,
[this, modulation]() {
return this->threadMain(modulation);
}
);
thread->setPriority(tt::Thread::Priority::High);
thread->start();
TT_LOG_I(TAG, "Starting finished");
return true;
}
bool RadiolibThreadedDevice::stop() {
auto lock = getMutex().asScopedLock();
lock.lock();
setState(State::PendingOff);
if (thread != nullptr) {
threadInterrupted = true;
interruptSignal();
// Detach thread, it will auto-delete when leaving the current scope
auto old_thread = std::move(thread);
if (old_thread->getState() != tt::Thread::State::Stopped) {
// Unlock so thread can lock
lock.unlock();
// Wait for thread to finish
old_thread->join();
// Re-lock to continue logic below
lock.lock();
}
}
setState(State::Off);
return true;
}
bool RadiolibThreadedDevice::isThreadInterrupted() const {
auto lock = getMutex().asScopedLock();
lock.lock();
return threadInterrupted;
}
int32_t RadiolibThreadedDevice::threadMain(const Modulation modulation) {
int rc = doBegin(modulation);
if (rc != 0) {
return rc;
}
setState(State::On);
while (!isThreadInterrupted()) {
doListen();
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
}
if (getTxQueueSize() > 0) {
doTransmit();
} else {
doReceive();
}
}
doEnd();
return 0;
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <Tactility/hal/radio/RadioDevice.h>
#include <Tactility/Thread.h>
class RadiolibThreadedDevice : public tt::hal::radio::RadioDevice {
private:
std::string threadName;
size_t threadSize;
std::unique_ptr<tt::Thread> _Nullable thread;
bool threadInterrupted = false;
protected:
virtual int32_t threadMain(const Modulation modulation);
bool isThreadInterrupted() const;
virtual void interruptSignal() = 0;
virtual int doBegin(const Modulation modulation) = 0;
virtual void doEnd() = 0;
virtual void doTransmit() = 0;
virtual void doListen() = 0;
virtual void doReceive() = 0;
public:
explicit RadiolibThreadedDevice(const std::string& threadName, const size_t threadSize)
: threadName(threadName)
, threadSize(threadSize)
{}
~RadiolibThreadedDevice() override = default;
virtual bool start(const Modulation modulation) override;
virtual bool stop() override;
};

View File

@ -83,36 +83,21 @@ void Sx1262::unregisterDio1Isr() {
gpio_wakeup_disable(configuration.irqPin);
gpio_set_intr_type(configuration.irqPin, GPIO_INTR_DISABLE);
}
/*
void IRAM_ATTR Sx1262::dio1Event() {
static const auto DRAM_ATTR rxbit = RADIO_RECEIVED_BIT;
static const auto DRAM_ATTR txbit = RADIO_TRANSMITTED_BIT;
switch (exchangeState) {
case ExchangeState::Receive:
getEventFlag().set(rxbit);
break;
case ExchangeState::TransmitWaiting:
getEventFlag().set(txbit);
break;
default:
break;
}
gpio_set_level(GPIO_NUM_9, 1);
}*/
void IRAM_ATTR Sx1262::dio1Event() {
static const auto DRAM_ATTR bit = SX1262_DIO1_EVENT_BIT;
getEventFlag().set(bit);
gpio_set_level(GPIO_NUM_9, 1);
events.set(bit);
}
int32_t Sx1262::threadMain(const Modulation modulation) {
using enum ExchangeState;
void Sx1262::txQueuedSignal() {
events.set(SX1262_QUEUED_TX_BIT);
}
void Sx1262::interruptSignal() {
events.set(SX1262_INTERRUPT_BIT);
}
int Sx1262::doBegin(const Modulation modulation) {
uint16_t rc = RADIOLIB_ERR_NONE;
if (modulation == Modulation::LoRa) {
@ -127,13 +112,6 @@ int32_t Sx1262::threadMain(const Modulation modulation) {
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
/*
radio.forceLDRO(false);
radio.setCRC(true);
radio.invertIQ(false);
radio.setWhitening(true, 0x00FF);
radio.explicitHeader();*/
} else if (modulation == Modulation::Fsk) {
rc = radio.beginFSK(
frequency,
@ -158,79 +136,69 @@ int32_t Sx1262::threadMain(const Modulation modulation) {
}
registerDio1Isr();
setState(State::On);
return 0;
}
TT_LOG_I(TAG, "SX1262 device ready to receive!");
exchangeState = Receive;
void Sx1262::doEnd() {
unregisterDio1Isr();
}
while (!isThreadInterrupted()) {
radio.startReceive();
TT_LOG_I(TAG, "WAIT FLAG");
auto eventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT | RADIO_TRANSMIT_QUEUED_BIT);
TT_LOG_W(TAG, "Event, flag=%X", eventFlags);
void Sx1262::doTransmit() {
currentTx = popNextQueuedTx();
radio.standby();
uint16_t rc = radio.startTransmit(currentTx.packet.data.data(), currentTx.packet.data.size());
if (rc == RADIOLIB_ERR_NONE) {
currentTx.callback(currentTx.id, TransmissionState::PendingTransmit);
TT_LOG_I(TAG, "WAIT TX FLAG");
auto txEventFlags = events.wait(SX1262_INTERRUPT_BIT | SX1262_DIO1_EVENT_BIT, tt::EventFlag::WaitAny, pdMS_TO_TICKS(2000));
TT_LOG_W(TAG, "Event, flag=%X", txEventFlags);
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
return;
}
if ((eventFlags & RADIO_TRANSMIT_QUEUED_BIT) && (getTxQueueSize() > 0)) {
currentTx = popNextQueuedTx();
radio.standby();
uint16_t rc = radio.startTransmit(currentTx.packet.data.data(), currentTx.packet.data.size());
if (rc == RADIOLIB_ERR_NONE) {
exchangeState = TransmitWaiting;
currentTx.callback(currentTx.id, TransmissionState::PendingTransmit);
TT_LOG_I(TAG, "WAIT TX FLAG");
auto txEventFlags = getEventFlag().wait(RADIO_TERMINATE_BIT | SX1262_DIO1_EVENT_BIT, tt::EventFlag::WaitAny, pdMS_TO_TICKS(2000));
TT_LOG_W(TAG, "Event, flag=%X", txEventFlags);
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
}
if (txEventFlags & SX1262_DIO1_EVENT_BIT) {
currentTx.callback(currentTx.id, TransmissionState::Transmitted);
} else {
currentTx.callback(currentTx.id, TransmissionState::Timeout);
}
exchangeState = Receive;
} else {
TT_LOG_E(TAG, "Error transmitting id=%d, rc=%hi", currentTx.id, rc);
currentTx.callback(currentTx.id, TransmissionState::Error);
exchangeState = Receive;
}
} else if (eventFlags & SX1262_DIO1_EVENT_BIT) {
uint16_t rxSize = radio.getPacketLength(true);
std::vector<uint8_t> data(rxSize);
uint16_t rc = radio.readData(data.data(), rxSize);
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
} else if(rxSize == 0) {
//TT_LOG_W(TAG, "Received data length 0");
} else {
float rssi = radio.getRSSI();
float snr = radio.getSNR();
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
auto rxPacket = tt::hal::radio::RxPacket {
.data = data,
.rssi = rssi,
.snr = snr
};
publishRx(rxPacket);
}
// A delay is needed before a new command
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_NUM_9, 0);
if (txEventFlags & SX1262_DIO1_EVENT_BIT) {
currentTx.callback(currentTx.id, TransmissionState::Transmitted);
} else {
TT_LOG_W(TAG, "Unhandled event, flag=%X", eventFlags);
currentTx.callback(currentTx.id, TransmissionState::Timeout);
}
} else {
TT_LOG_E(TAG, "Error transmitting id=%d, rc=%hi", currentTx.id, rc);
currentTx.callback(currentTx.id, TransmissionState::Error);
}
}
void Sx1262::doListen() {
radio.startReceive();
TT_LOG_I(TAG, "WAIT FLAG");
auto eventFlags = events.wait(SX1262_INTERRUPT_BIT | SX1262_DIO1_EVENT_BIT | SX1262_QUEUED_TX_BIT);
TT_LOG_W(TAG, "Event, flag=%X", eventFlags);
}
void Sx1262::doReceive() {
uint16_t rxSize = radio.getPacketLength(true);
std::vector<uint8_t> data(rxSize);
uint16_t rc = radio.readData(data.data(), rxSize);
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
} else if(rxSize == 0) {
//TT_LOG_W(TAG, "Received data length 0");
} else {
float rssi = radio.getRSSI();
float snr = radio.getSNR();
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
auto rxPacket = tt::hal::radio::RxPacket {
.data = data,
.rssi = rssi,
.snr = snr
};
publishRx(rxPacket);
}
unregisterDio1Isr();
return 0;
// A delay is needed before a new command
vTaskDelay(pdMS_TO_TICKS(100));
}

View File

@ -1,14 +1,16 @@
#pragma once
#include <Tactility/hal/radio/RadioDevice.h>
#include <Tactility/hal/spi/Spi.h>
#include <Tactility/EventFlag.h>
#include <Tactility/Lock.h>
#include <RadioLib.h>
#include "RadiolibTactilityHal.h"
#include "RadiolibThreadedDevice.h"
#include <utility>
class Sx1262 final : public tt::hal::radio::RadioDevice {
class Sx1262 final : public RadiolibThreadedDevice {
public:
struct Configuration {
@ -23,23 +25,18 @@ public:
};
private:
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT8;
enum class ExchangeState {
Idle,
Receive,
//TransmitInitiated,
TransmitWaiting
};
static constexpr auto SX1262_INTERRUPT_BIT = BIT0;
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT1;
static constexpr auto SX1262_QUEUED_TX_BIT = BIT2;
std::string name;
const Configuration configuration;
std::shared_ptr<tt::Lock> lock;
tt::EventFlag events;
RadiolibTactilityHal hal;
Module radioModule;
SX1262 radio;
TxItem currentTx;
ExchangeState exchangeState;
int8_t power = 0;
float frequency = 0.0;
@ -51,20 +48,29 @@ private:
float bitRate = 0.0;
float frequencyDeviation = 0.0;
int32_t threadMain();
void registerDio1Isr();
void unregisterDio1Isr();
protected:
virtual void txQueuedSignal() override;
virtual void interruptSignal() override;
virtual int doBegin(const Modulation modulation) override;
virtual void doEnd() override;
virtual void doTransmit() override;
virtual void doListen() override;
virtual void doReceive() override;
public:
explicit Sx1262(const std::string& name, const Configuration& configuration, std::shared_ptr<tt::Lock> lock = nullptr)
: RadioDevice(name, 4096)
: RadiolibThreadedDevice(name, 4096)
, name(name)
, configuration(configuration)
, hal(configuration.spiHostDevice, configuration.spiFrequency, configuration.csPin, lock)
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
, radio(&radioModule)
, exchangeState(ExchangeState::Idle) {}
{}
~Sx1262() override = default;
@ -78,12 +84,5 @@ public:
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
}
bool hasReceived();
void dio1Event();
//void IRAM_ATTR setRxEvent() { rxFlag = true; }
protected:
int32_t threadMain(const Modulation modulation) override;
};

View File

@ -2,8 +2,6 @@
#include "../Device.h"
#include <Tactility/EventFlag.h>
#include <Tactility/Lock.h>
#include <Tactility/Mutex.h>
#include <Tactility/Thread.h>
@ -80,31 +78,19 @@ private:
std::shared_ptr<std::function<void(Device::Id id, const RxPacket&)>> onData;
};
std::string threadName;
size_t threadSize;
State state;
EventFlag events;
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::unique_ptr<Thread> _Nullable thread;
bool threadInterrupted = false;
std::vector<RxSubscription> rxSubscriptions;
std::deque<TxItem> txQueue;
TxId lastTxId = 0;
RxSubscriptionId lastRxSubscriptionId = 0;
protected:
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
static constexpr auto RADIO_TRANSMIT_QUEUED_BIT = BIT1;
static constexpr auto RADIO_RECEIVED_BIT = BIT2;
static constexpr auto RADIO_TRANSMITTED_BIT = BIT3;
virtual int32_t threadMain(const Modulation modulation) = 0;
Mutex &getMutex() { return mutex; }
EventFlag &getEventFlag() { return events; }
bool isThreadInterrupted() const;
const Mutex &getMutex() const { return mutex; }
void setState(State newState);
virtual void txQueuedSignal() = 0;
size_t getTxQueueSize() const {
auto lock = mutex.asScopedLock();
lock.lock();
@ -125,10 +111,8 @@ protected:
void publishRx(const RxPacket& packet);
public:
explicit RadioDevice(const std::string& threadName, const size_t threadSize)
: threadName(threadName)
, threadSize(threadSize)
, state(State::Off) {}
explicit RadioDevice()
: state(State::Off) {}
~RadioDevice() override = default;
@ -137,8 +121,8 @@ public:
virtual bool configure(const Parameter parameter, const float value) = 0;
virtual bool isCapableOf(const Modulation modulation) = 0;
bool start(const Modulation modulation);
bool stop();
virtual bool start(const Modulation modulation) = 0;
virtual bool stop() = 0;
TxId transmit(const TxPacket& packet, TxStateCallback callback) {
auto lock = mutex.asScopedLock();
@ -147,7 +131,7 @@ public:
txQueue.push_back(TxItem{.id = txId, .packet = packet, .callback = callback});
callback(txId, TransmissionState::Queued);
lastTxId++;
getEventFlag().set(RADIO_TRANSMIT_QUEUED_BIT);
txQueuedSignal();
return txId;
}

View File

@ -60,6 +60,7 @@ namespace app {
namespace boot { extern const AppManifest manifest; }
namespace calculator { extern const AppManifest manifest; }
namespace chat { extern const AppManifest manifest; }
namespace chirp { extern const AppManifest manifest; }
namespace development { extern const AppManifest manifest; }
namespace display { extern const AppManifest manifest; }
namespace files { extern const AppManifest manifest; }
@ -121,6 +122,7 @@ static void registerSystemApps() {
addApp(app::wifiapsettings::manifest);
addApp(app::wificonnect::manifest);
addApp(app::wifimanage::manifest);
addApp(app::chirp::manifest);
#if defined(CONFIG_TINYUSB_MSC_ENABLED) && CONFIG_TINYUSB_MSC_ENABLED
addApp(app::usbsettings::manifest);

View File

@ -5,74 +5,6 @@ namespace tt::hal::radio {
constexpr const char* TAG = "RadioDevice";
bool RadioDevice::start(const Modulation modulation) {
auto lock = mutex.asScopedLock();
if (!isCapableOf(modulation)) {
TT_LOG_E(TAG, "Can't start device \"%s\", not capable of modulation \"%s\"", getName().c_str(), toString(modulation));
return false;
}
lock.lock();
if (thread != nullptr && thread->getState() != Thread::State::Stopped) {
TT_LOG_W(TAG, "Already started");
return true;
}
threadInterrupted = false;
TT_LOG_I(TAG, "Starting thread");
setState(State::PendingOn);
thread = std::make_unique<Thread>(
threadName,
threadSize,
[this, modulation]() {
return this->threadMain(modulation);
}
);
thread->setPriority(tt::Thread::Priority::High);
thread->start();
TT_LOG_I(TAG, "Starting finished");
return true;
}
bool RadioDevice::stop() {
auto lock = mutex.asScopedLock();
lock.lock();
setState(State::PendingOff);
if (thread != nullptr) {
threadInterrupted = true;
getEventFlag().set(RADIO_TERMINATE_BIT);
// Detach thread, it will auto-delete when leaving the current scope
auto old_thread = std::move(thread);
if (old_thread->getState() != Thread::State::Stopped) {
// Unlock so thread can lock
lock.unlock();
// Wait for thread to finish
old_thread->join();
// Re-lock to continue logic below
lock.lock();
}
}
setState(State::Off);
return true;
}
bool RadioDevice::isThreadInterrupted() const {
auto lock = mutex.asScopedLock();
lock.lock();
return threadInterrupted;
}
RadioDevice::State RadioDevice::getState() const {
auto lock = mutex.asScopedLock();
lock.lock();