Add RadioDevice and support for SX1262

This commit is contained in:
Dominic Höglinger 2025-09-14 09:53:22 +02:00
parent 179e44ec60
commit 1ab7c4ce9a
14 changed files with 855 additions and 1 deletions

View File

@ -20,4 +20,9 @@ dependencies:
version: "1.7.6~1"
rules:
- if: "target == esp32s3"
jgromes/radiolib:
version: "7.2.1"
rules:
- if: "target in [esp32s3, esp32p4]"
idf: '5.5'

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility radiolib
)

View File

@ -0,0 +1,3 @@
# RadioLibCompat
A set of helper classes to implement `RadioLib` drivers in Tactility.

View File

@ -0,0 +1,38 @@
#include "IsrTrampoline.h"
std::array<IsrTrampolines::VoidFunction, IsrTrampolines::MAX_SLOTS> IsrTrampolines::functions{};
void (*const IsrTrampolines::trampolines[IsrTrampolines::MAX_SLOTS])() = {
&IsrTrampolines::trampoline0,
&IsrTrampolines::trampoline1,
&IsrTrampolines::trampoline2,
&IsrTrampolines::trampoline3,
&IsrTrampolines::trampoline4,
&IsrTrampolines::trampoline5,
&IsrTrampolines::trampoline6,
&IsrTrampolines::trampoline7,
&IsrTrampolines::trampoline8,
&IsrTrampolines::trampoline9,
&IsrTrampolines::trampoline10,
&IsrTrampolines::trampoline11,
&IsrTrampolines::trampoline12,
&IsrTrampolines::trampoline13,
&IsrTrampolines::trampoline14,
&IsrTrampolines::trampoline15,
&IsrTrampolines::trampoline16,
&IsrTrampolines::trampoline17,
&IsrTrampolines::trampoline18,
&IsrTrampolines::trampoline19,
&IsrTrampolines::trampoline20,
&IsrTrampolines::trampoline21,
&IsrTrampolines::trampoline22,
&IsrTrampolines::trampoline23,
&IsrTrampolines::trampoline24,
&IsrTrampolines::trampoline25,
&IsrTrampolines::trampoline26,
&IsrTrampolines::trampoline27,
&IsrTrampolines::trampoline28,
&IsrTrampolines::trampoline29,
&IsrTrampolines::trampoline30,
&IsrTrampolines::trampoline31
};

View File

@ -0,0 +1,118 @@
#pragma once
#include <cstddef>
#include <functional>
#include "esp_attr.h"
#define ISR_TRAMPOLINE_FUNC(slot) \
static void IRAM_ATTR trampoline##slot() { static const int DRAM_ATTR i = slot; auto &f = IsrTrampolines::functions[i]; if (f) f(); }
class IsrTrampolines {
public:
typedef std::size_t SlotNumber;
static constexpr SlotNumber MAX_SLOTS = 32;
using TrampolinePointer = void (*)();
using VoidFunction = std::function<void()>;
static std::array<VoidFunction, MAX_SLOTS> functions;
static void (*const trampolines[MAX_SLOTS])();
static bool registerSlot(VoidFunction f, SlotNumber& slotRegistered) {
for (SlotNumber slot = 0; slot < MAX_SLOTS; ++slot) {
if (!functions[slot]) {
slotRegistered = slot;
functions[slot] = f;
return true;
}
}
return false;
}
static bool unregisterSlot(const SlotNumber slotRegistered) {
if (!functions[slotRegistered]) {
return false;
}
functions[slotRegistered] = nullptr;
return true;
}
static TrampolinePointer getForSlot(const SlotNumber slotRegistered) {
if (slotRegistered < MAX_SLOTS) {
return trampolines[slotRegistered];
}
return nullptr;
}
// Unfortunately, these cannot be templated because the linker cannot correctly handle
// its integer constant indexing the function table (dangerous relocation)
ISR_TRAMPOLINE_FUNC(0);
ISR_TRAMPOLINE_FUNC(1);
ISR_TRAMPOLINE_FUNC(2);
ISR_TRAMPOLINE_FUNC(3);
ISR_TRAMPOLINE_FUNC(4);
ISR_TRAMPOLINE_FUNC(5);
ISR_TRAMPOLINE_FUNC(6);
ISR_TRAMPOLINE_FUNC(7);
ISR_TRAMPOLINE_FUNC(8);
ISR_TRAMPOLINE_FUNC(9);
ISR_TRAMPOLINE_FUNC(10);
ISR_TRAMPOLINE_FUNC(11);
ISR_TRAMPOLINE_FUNC(12);
ISR_TRAMPOLINE_FUNC(13);
ISR_TRAMPOLINE_FUNC(14);
ISR_TRAMPOLINE_FUNC(15);
ISR_TRAMPOLINE_FUNC(16);
ISR_TRAMPOLINE_FUNC(17);
ISR_TRAMPOLINE_FUNC(18);
ISR_TRAMPOLINE_FUNC(19);
ISR_TRAMPOLINE_FUNC(20);
ISR_TRAMPOLINE_FUNC(21);
ISR_TRAMPOLINE_FUNC(22);
ISR_TRAMPOLINE_FUNC(23);
ISR_TRAMPOLINE_FUNC(24);
ISR_TRAMPOLINE_FUNC(25);
ISR_TRAMPOLINE_FUNC(26);
ISR_TRAMPOLINE_FUNC(27);
ISR_TRAMPOLINE_FUNC(28);
ISR_TRAMPOLINE_FUNC(29);
ISR_TRAMPOLINE_FUNC(30);
ISR_TRAMPOLINE_FUNC(31);
/*static void IRAM_ATTR trampoline0() ISR_TRAMPOLINE_FUNC(0)
static void IRAM_ATTR trampoline1() { auto &f = IsrTrampolines::functions[1]; if (f) f(); }
static void IRAM_ATTR trampoline2() { auto &f = IsrTrampolines::functions[2]; if (f) f(); }
static void IRAM_ATTR trampoline3() { auto &f = IsrTrampolines::functions[3]; if (f) f(); }
static void IRAM_ATTR trampoline4() { auto &f = IsrTrampolines::functions[4]; if (f) f(); }
static void IRAM_ATTR trampoline5() { auto &f = IsrTrampolines::functions[5]; if (f) f(); }
static void IRAM_ATTR trampoline6() { auto &f = IsrTrampolines::functions[6]; if (f) f(); }
static void IRAM_ATTR trampoline7() { auto &f = IsrTrampolines::functions[7]; if (f) f(); }
static void IRAM_ATTR trampoline8() { auto &f = IsrTrampolines::functions[8]; if (f) f(); }
static void IRAM_ATTR trampoline9() { auto &f = IsrTrampolines::functions[9]; if (f) f(); }
static void IRAM_ATTR trampoline10() { auto &f = IsrTrampolines::functions[10]; if (f) f(); }
static void IRAM_ATTR trampoline11() { auto &f = IsrTrampolines::functions[11]; if (f) f(); }
static void IRAM_ATTR trampoline12() { auto &f = IsrTrampolines::functions[12]; if (f) f(); }
static void IRAM_ATTR trampoline13() { auto &f = IsrTrampolines::functions[13]; if (f) f(); }
static void IRAM_ATTR trampoline14() { auto &f = IsrTrampolines::functions[14]; if (f) f(); }
static void IRAM_ATTR trampoline15() { auto &f = IsrTrampolines::functions[15]; if (f) f(); }
static void IRAM_ATTR trampoline16() { auto &f = IsrTrampolines::functions[16]; if (f) f(); }
static void IRAM_ATTR trampoline17() { auto &f = IsrTrampolines::functions[17]; if (f) f(); }
static void IRAM_ATTR trampoline18() { auto &f = IsrTrampolines::functions[18]; if (f) f(); }
static void IRAM_ATTR trampoline19() { auto &f = IsrTrampolines::functions[19]; if (f) f(); }
static void IRAM_ATTR trampoline20() { auto &f = IsrTrampolines::functions[20]; if (f) f(); }
static void IRAM_ATTR trampoline21() { auto &f = IsrTrampolines::functions[21]; if (f) f(); }
static void IRAM_ATTR trampoline22() { auto &f = IsrTrampolines::functions[22]; if (f) f(); }
static void IRAM_ATTR trampoline23() { auto &f = IsrTrampolines::functions[23]; if (f) f(); }
static void IRAM_ATTR trampoline24() { auto &f = IsrTrampolines::functions[24]; if (f) f(); }
static void IRAM_ATTR trampoline25() { auto &f = IsrTrampolines::functions[25]; if (f) f(); }
static void IRAM_ATTR trampoline26() { auto &f = IsrTrampolines::functions[26]; if (f) f(); }
static void IRAM_ATTR trampoline27() { auto &f = IsrTrampolines::functions[27]; if (f) f(); }
static void IRAM_ATTR trampoline28() { auto &f = IsrTrampolines::functions[28]; if (f) f(); }
static void IRAM_ATTR trampoline29() { auto &f = IsrTrampolines::functions[29]; if (f) f(); }
static void IRAM_ATTR trampoline30() { auto &f = IsrTrampolines::functions[30]; if (f) f(); }
static void IRAM_ATTR trampoline31() { auto &f = IsrTrampolines::functions[31]; if (f) f(); }
*/
};

View File

@ -0,0 +1,155 @@
#include "RadiolibTactilityHal.h"
#include "hal/gpio_hal.h"
#include "esp_timer.h"
constexpr const char* TAG = "RadiolibTactilityHal";
void RadiolibTactilityHal::init() {
// we only need to init the SPI here
spiBegin();
}
void RadiolibTactilityHal::term() {
// we only need to stop the SPI here
spiEnd();
}
void RadiolibTactilityHal::pinMode(uint32_t pin, uint32_t mode) {
if(pin == RADIOLIB_NC) {
return;
}
gpio_hal_context_t gpiohal;
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
gpio_config_t conf = {
.pin_bit_mask = (1ULL<<pin),
.mode = (gpio_mode_t)mode,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[pin].int_type,
};
gpio_config(&conf);
}
void RadiolibTactilityHal::digitalWrite(uint32_t pin, uint32_t value) {
if(pin == RADIOLIB_NC) {
return;
}
gpio_set_level((gpio_num_t)pin, value);
}
uint32_t RadiolibTactilityHal::digitalRead(uint32_t pin) {
if(pin == RADIOLIB_NC) {
return(0);
}
return(gpio_get_level((gpio_num_t)pin));
}
void RadiolibTactilityHal::attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) {
if(interruptNum == RADIOLIB_NC) {
return;
}
gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM);
gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7));
// this uses function typecasting, which is not defined when the functions have different signatures
// untested and might not work
gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL);
}
void RadiolibTactilityHal::detachInterrupt(uint32_t interruptNum) {
if(interruptNum == RADIOLIB_NC) {
return;
}
gpio_isr_handler_remove((gpio_num_t)interruptNum);
gpio_wakeup_disable((gpio_num_t)interruptNum);
gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE);
}
void RadiolibTactilityHal::delay(unsigned long ms) {
vTaskDelay(ms / portTICK_PERIOD_MS);
}
void RadiolibTactilityHal::delayMicroseconds(unsigned long us) {
uint64_t m = (uint64_t)esp_timer_get_time();
if(us) {
uint64_t e = (m + us);
if(m > e) { // overflow
while((uint64_t)esp_timer_get_time() > e);
}
while((uint64_t)esp_timer_get_time() < e);
}
}
unsigned long RadiolibTactilityHal::millis() {
return((unsigned long)(esp_timer_get_time() / 1000ULL));
}
unsigned long RadiolibTactilityHal::micros() {
return((unsigned long)(esp_timer_get_time()));
}
long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) {
if(pin == RADIOLIB_NC) {
return(0);
}
this->pinMode(pin, GPIO_MODE_INPUT);
uint32_t start = this->micros();
uint32_t curtick = this->micros();
while(this->digitalRead(pin) == state) {
if((this->micros() - curtick) > timeout) {
return(0);
}
}
return(this->micros() - start);
}
void RadiolibTactilityHal::spiBegin() {
spi_device_interface_config_t devcfg = {};
devcfg.clock_speed_hz = 1 * 1000 * 1000; // 1MHz
devcfg.mode = 0;
devcfg.spics_io_num = csPin;
devcfg.queue_size = 1;
esp_err_t ret = spi_bus_add_device(spiHostDevice, &devcfg, &spiDeviceHandle);
if (ret != ESP_OK) {
TT_LOG_E(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
}
}
void RadiolibTactilityHal::spiBeginTransaction() {
// not needed - in ESP32 Arduino core, this function
// repeats clock div, mode and bit order configuration
}
void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
spi_transaction_t t;
auto lock = getLock()->asScopedLock();
bool locked = lock.lock(portMAX_DELAY);
if (!locked) {
TT_LOG_E(TAG, "Failed to aquire SPI lock");
}
memset(&t, 0, sizeof(t)); // Zero out the transaction
t.length = len * 8; // Length is in bits
t.tx_buffer = out; // The data to send
t.rx_buffer = in; // The data to receive
spi_device_polling_transmit(spiDeviceHandle, &t);
}
void RadiolibTactilityHal::spiEndTransaction() {
// nothing needs to be done here
}
void RadiolibTactilityHal::spiEnd() {
}

View File

@ -0,0 +1,58 @@
#pragma once
#include <Tactility/Lock.h>
#include <Tactility/hal/spi/Spi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <RadioLib.h>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <memory>
class RadiolibTactilityHal : public RadioLibHal {
private:
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
spi_device_handle_t spiDeviceHandle;
std::shared_ptr<tt::Lock> lock;
public:
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, gpio_num_t csPin)
: RadioLibHal(
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
0, // LOW
1, // HIGH
GPIO_INTR_POSEDGE,
GPIO_INTR_NEGEDGE)
, spiHostDevice(spiHostDevice)
, csPin(csPin)
, lock(tt::hal::spi::getLock(spiHostDevice)) {}
void init() override;
void term() override;
void pinMode(uint32_t pin, uint32_t mode) override;
void digitalWrite(uint32_t pin, uint32_t value) override;
uint32_t digitalRead(uint32_t pin) override;
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override;
void detachInterrupt(uint32_t interruptNum) override;
void delay(unsigned long ms) override;
void delayMicroseconds(unsigned long us) override;
unsigned long millis() override;
unsigned long micros() override;
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override;
void spiBegin() override;
void spiBeginTransaction() override;
void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override;
void spiEndTransaction() override;
void spiEnd();
std::shared_ptr<tt::Lock> getLock() const { return lock; }
};

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver RadioLibCompat radiolib
)

7
Drivers/SX126x/README.md Normal file
View File

@ -0,0 +1,7 @@
# SX126x
Radio with LoRa/(G)FSK capabilities.
## SX1262
- [Product Information](https://www.semtech.com/products/wireless-rf/lora-connect/sx1262)

View File

@ -0,0 +1,125 @@
#include "Sx1262.h"
#include <cstring>
constexpr const char* TAG = "LoraDevice";
bool Sx1262::configure(const Parameter parameter, const float value) {
using enum Parameter;
switch (parameter) {
case Power:
power = value;
return true;
case Frequency:
frequency = value;
return true;
case Bandwidth:
bandwidth = value;
return true;
case SpreadFactor:
spreadFactor = value;
return true;
case CodingRate:
codingRate = value;
return true;
case SyncWord:
syncWord = value;
return true;
case PreambleLength:
preambleLength = value;
return true;
case DataRate:
bitRate = value;
return true;
case FrequencyDeviation:
frequencyDeviation = value;
return true;
default:
break;
}
return false;
}
void Sx1262::onReceive() {
getEventFlag().set(RADIO_RECEIVED_BIT);
}
int32_t Sx1262::threadMain(const Modulation modulation) {
uint16_t rc = RADIOLIB_ERR_NONE;
if (modulation == Modulation::LoRa) {
rc = radio.begin(
frequency,
bandwidth,
spreadFactor,
codingRate,
syncWord,
power,
preambleLength,
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
} else if (modulation == Modulation::Fsk) {
rc = radio.beginFSK(
frequency,
bitRate,
frequencyDeviation,
bandwidth,
power,
preambleLength,
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
} else {
TT_LOG_E(TAG, "SX1262 not capable of modulation \"%s\"", toString(modulation));
setState(State::Error);
return -1;
}
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Radiolib init failed with code %hi", rc);
setState(State::Error);
return -1;
}
setState(State::On);
TT_LOG_I(TAG, "SX1262 device ready to receive!");
while (!isThreadInterrupted()) {
radio.startReceive();
getEventFlag().wait(RADIO_TERMINATE_BIT, RADIO_RECEIVED_BIT);
// Thread might've been interrupted in the meanwhile
if (isThreadInterrupted()) {
break;
}
uint16_t rxSize = radio.getPacketLength(true);
uint8_t *dataBuffer = new uint8_t[rxSize];
uint16_t rc = radio.readData(dataBuffer, rxSize);
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Error receiving data, RadioLib returned %hi", rc);
} else {
float rssi = radio.getRSSI();
float snr = radio.getSNR();
TT_LOG_I(TAG, "LoRa RX size=%d RSSI=%f SNR=%f", rxSize, rssi, snr);
std::string message((char*)dataBuffer, rxSize);
TT_LOG_I(TAG, "msg=%s", message.c_str());
auto rxPacket = tt::hal::radio::RxPacket {
.data = dataBuffer,
.size = rxSize,
.rssi = rssi,
.snr = snr
};
publishRx(rxPacket);
}
delete[] dataBuffer;
}
return 0;
}

View File

@ -0,0 +1,80 @@
#pragma once
#include <Tactility/hal/radio/RadioDevice.h>
#include <Tactility/hal/spi/Spi.h>
#include <RadioLib.h>
#include "RadiolibTactilityHal.h"
#include "IsrTrampoline.h"
#include <utility>
class Sx1262 final : public tt::hal::radio::RadioDevice {
public:
struct Configuration {
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t resetPin;
gpio_num_t busyPin;
gpio_num_t irqPin;
float tcxoVoltage;
bool useRegulatorLdo;
};
private:
std::string name;
const Configuration configuration;
std::shared_ptr<tt::Lock> lock;
RadiolibTactilityHal hal;
Module radioModule;
SX1262 radio;
IsrTrampolines::SlotNumber isrTrampolineSlot;
int8_t power = 0;
float frequency = 0.0;
float bandwidth = 0.0;
uint8_t spreadFactor = 0.0;
uint8_t codingRate = 0;
uint8_t syncWord = 0;
uint16_t preambleLength = 0;
float bitRate = 0.0;
float frequencyDeviation = 0.0;
int32_t threadMain();
void onReceive();
public:
explicit Sx1262(const std::string& name, const Configuration& configuration)
: RadioDevice(name, 1024)
, name(name)
, configuration(configuration)
, hal(configuration.spiHostDevice, configuration.csPin)
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
, radio(&radioModule) {
IsrTrampolines::registerSlot([this]() {
this->onReceive();
}, isrTrampolineSlot);
radio.setDio1Action(IsrTrampolines::getForSlot(isrTrampolineSlot));
}
~Sx1262() override {
IsrTrampolines::unregisterSlot(isrTrampolineSlot);
}
std::string getName() const override { return name; }
std::string getDescription() const override { return "SX1262 LoRa and FSK capable radio"; }
bool configure(const Parameter parameter, const float value) override;
bool isCapableOf(const Modulation modulation) {
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
}
protected:
int32_t threadMain(const Modulation modulation) override;
};

View File

@ -21,7 +21,8 @@ public:
Keyboard,
Encoder,
Power,
Gps
Gps,
Radio
};
typedef uint32_t Id;

View File

@ -0,0 +1,118 @@
#pragma once
#include "../Device.h"
#include <Tactility/EventFlag.h>
#include <Tactility/Lock.h>
#include <Tactility/Mutex.h>
#include <Tactility/Thread.h>
#include <utility>
namespace tt::hal::radio {
struct RxPacket {
uint8_t const *data;
size_t const size;
float rssi;
float snr;
};
class RadioDevice : public Device {
public:
enum class Modulation {
Fsk,
LoRa
};
enum class Parameter {
Power,
Frequency,
Bandwidth,
SpreadFactor,
CodingRate,
SyncWord,
PreambleLength,
FrequencyDeviation,
DataRate,
AddressWidth
};
typedef int RxSubscriptionId;
enum class State {
PendingOn,
On,
Error,
PendingOff,
Off
};
private:
struct RxSubscription {
RxSubscriptionId id;
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;
RxSubscriptionId lastRxSubscriptionId = 0;
protected:
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
static constexpr auto RADIO_RECEIVED_BIT = BIT1;
virtual int32_t threadMain(const Modulation modulation) = 0;
Mutex &getMutex() { return mutex; }
EventFlag &getEventFlag() { return events; }
bool isThreadInterrupted() const;
void setState(State newState);
void publishRx(const RxPacket& packet);
public:
explicit RadioDevice(const std::string& threadName, const size_t threadSize)
: threadName(threadName)
, threadSize(threadSize)
, state(State::Off) {}
~RadioDevice() override = default;
Type getType() const override { return Type::Radio; }
virtual bool configure(const Parameter parameter, const float value) = 0;
virtual bool isCapableOf(const Modulation modulation) = 0;
bool start(const Modulation modulation);
bool stop();
RxSubscriptionId subscribeRx(const std::function<void(Device::Id id, const RxPacket&)>& onData) {
auto lock = mutex.asScopedLock();
lock.lock();
rxSubscriptions.push_back({
.id = ++lastRxSubscriptionId,
.onData = std::make_shared<std::function<void(Device::Id, const RxPacket&)>>(onData)
});
return lastRxSubscriptionId;
}
void unsubscribeRx(RxSubscriptionId subscriptionId) {
auto lock = mutex.asScopedLock();
lock.lock();
std::erase_if(rxSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; });
}
State getState() const;
};
const char* toString(RadioDevice::Modulation modulation);
const char* toString(RadioDevice::Parameter parameter);
}

View File

@ -0,0 +1,136 @@
#include "Tactility/hal/radio/RadioDevice.h"
#include <cstring>
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();
return state; // Make copy because of thread safety
}
void RadioDevice::setState(State newState) {
auto lock = mutex.asScopedLock();
lock.lock();
state = newState;
}
void RadioDevice::publishRx(const RxPacket& packet) {
mutex.lock();
for (auto& subscription : rxSubscriptions) {
(*subscription.onData)(getId(), packet);
}
mutex.unlock();
}
const char* toString(RadioDevice::Modulation modulation) {
using enum RadioDevice::Modulation;
switch (modulation) {
case Fsk:
return "FSK";
case LoRa:
return "LoRa";
default:
return "Unkown";
}
}
const char* toString(RadioDevice::Parameter parameter) {
using enum RadioDevice::Parameter;
switch (parameter) {
case Power:
return TT_STRINGIFY(Power);
case Frequency:
return TT_STRINGIFY(Frequency);
case Bandwidth:
return TT_STRINGIFY(Bandwidth);
case SpreadFactor:
return TT_STRINGIFY(SpreadFactor);
case CodingRate:
return TT_STRINGIFY(CodingRate);
case SyncWord:
return TT_STRINGIFY(SyncWord);
case PreambleLength:
return TT_STRINGIFY(PreambleLength);
case FrequencyDeviation:
return TT_STRINGIFY(FrequencyDeviation);
case DataRate:
return TT_STRINGIFY(DataRate);
case AddressWidth:
return TT_STRINGIFY(AddressWidth);
default:
return "Unknown";
}
}
} // namespace tt::hal::radio