Radio: Refactor parameters and ParameterSet

This commit is contained in:
Dominic Höglinger 2025-09-20 19:14:21 +02:00
parent 6fabd3354c
commit 982f3e11c7
7 changed files with 181 additions and 49 deletions

View File

@ -6,14 +6,14 @@ constexpr const char* TAG = "RadiolibThreadedDevice";
bool RadiolibThreadedDevice::start(const Modulation modulation) {
auto lock = getMutex().asScopedLock();
if (!isCapableOf(modulation)) {
if (!canTransmit(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) {
if ((thread != nullptr) && (thread->getState() != tt::Thread::State::Stopped)) {
TT_LOG_W(TAG, "Already started");
return true;
}

View File

@ -9,37 +9,30 @@ void IRAM_ATTR dio1handler(void* context) {
((Sx1262*)context)->dio1Event();
}
bool Sx1262::configure(const Parameter parameter, const float value) {
bool Sx1262::setParameter(const Parameter parameter, const float value) {
using enum Parameter;
switch (parameter) {
case Power:
power = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), power);
return true;
case Frequency:
frequency = value;
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), frequency);
return true;
case Bandwidth:
bandwidth = value;
TT_LOG_I(TAG, "Configure %s=%f", toString(parameter), bandwidth);
return true;
case SpreadFactor:
spreadFactor = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), spreadFactor);
return true;
case CodingRate:
codingRate = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), codingRate);
return true;
case SyncWord:
syncWord = value;
TT_LOG_I(TAG, "Configure %s=%X", toString(parameter), syncWord);
return true;
case PreambleLength:
preambleLength = value;
TT_LOG_I(TAG, "Configure %s=%d", toString(parameter), preambleLength);
return true;
case DataRate:
bitRate = value;
@ -47,17 +40,62 @@ bool Sx1262::configure(const Parameter parameter, const float value) {
case FrequencyDeviation:
frequencyDeviation = value;
return true;
case NarrowGrid:
narrowGrid = value;
return true;
default:
break;
}
TT_LOG_W(TAG, "Tried to set unsupported parameter \"%s\" to %f", toString(parameter), value);
return false;
}
bool Sx1262::getParameter(const Parameter parameter, float &value) const {
using enum Parameter;
switch (parameter) {
case Power:
value = power;
return true;
case Frequency:
value = frequency;
return true;
case Bandwidth:
value = bandwidth;
return true;
case SpreadFactor:
value = spreadFactor;
return true;
case CodingRate:
value = codingRate;
return true;
case SyncWord:
value = syncWord;
return true;
case PreambleLength:
value = preambleLength;
return true;
case DataRate:
value = bitRate;
return true;
case FrequencyDeviation:
value = frequencyDeviation;
return true;
case NarrowGrid:
value = narrowGrid;
return true;
default:
break;
}
TT_LOG_W(TAG, "Tried to get unsupported parameter \"%s\"", toString(parameter));
return false;
}
void Sx1262::registerDio1Isr() {
hal.pinMode(GPIO_NUM_9, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_9, 0);
gpio_hal_context_t gpiohal;
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
@ -123,6 +161,14 @@ int Sx1262::doBegin(const Modulation modulation) {
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
} else if (modulation == Modulation::LrFhss) {
rc = radio.beginLRFHSS(
bandwidth,
codingRate,
narrowGrid,
configuration.tcxoVoltage,
configuration.useRegulatorLdo
);
} else {
TT_LOG_E(TAG, "SX1262 not capable of modulation \"%s\"", toString(modulation));
setState(State::Error);
@ -130,11 +176,12 @@ int Sx1262::doBegin(const Modulation modulation) {
}
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_E(TAG, "Radiolib init failed with code %hi", rc);
TT_LOG_E(TAG, "Radiolib initialization failed with code %hi", rc);
setState(State::Error);
return -1;
}
currentModem = modulation;
registerDio1Isr();
return 0;
}
@ -145,19 +192,24 @@ void Sx1262::doEnd() {
void Sx1262::doTransmit() {
currentTx = popNextQueuedTx();
radio.standby();
uint16_t rc = radio.startTransmit(currentTx.packet.data.data(), currentTx.packet.data.size());
uint16_t rc = RADIOLIB_ERR_NONE;
rc = radio.standby();
if (rc != RADIOLIB_ERR_NONE) {
TT_LOG_W(TAG, "RadioLib returned %hi on standby", rc);
}
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()) {
return;
}
// If the DIO1 bit is unset, this means the wait timed out
if (txEventFlags & SX1262_DIO1_EVENT_BIT) {
currentTx.callback(currentTx.id, TransmissionState::Transmitted);
} else {
@ -171,24 +223,31 @@ void Sx1262::doTransmit() {
}
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);
if (currentModem != Modulation::LrFhss) {
radio.startReceive();
events.wait(SX1262_INTERRUPT_BIT | SX1262_DIO1_EVENT_BIT | SX1262_QUEUED_TX_BIT);
} else {
// LR-FHSS modem only supports TX
events.wait(SX1262_INTERRUPT_BIT | SX1262_QUEUED_TX_BIT);
}
}
void Sx1262::doReceive() {
// LR-FHSS modem only supports TX
if (currentModem == Modulation::LrFhss) return;
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");
// This can cause a flood of messages if there are ones emitted here,
// as a warning here doesn't bring that much to the table it is skipped.
// The body is kept empty intentionally.'
} 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,
@ -198,7 +257,7 @@ void Sx1262::doReceive() {
publishRx(rxPacket);
}
// A delay is needed before a new command
vTaskDelay(pdMS_TO_TICKS(100));
// A delay before a new command improves reliability
vTaskDelay(pdMS_TO_TICKS(SX1262_COOLDOWN_MILLIS));
}

View File

@ -25,6 +25,7 @@ public:
};
private:
static constexpr auto SX1262_COOLDOWN_MILLIS = 100;
static constexpr auto SX1262_INTERRUPT_BIT = BIT0;
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT1;
static constexpr auto SX1262_QUEUED_TX_BIT = BIT2;
@ -37,6 +38,7 @@ private:
Module radioModule;
SX1262 radio;
TxItem currentTx;
Modulation currentModem;
int8_t power = 0;
float frequency = 0.0;
@ -47,6 +49,7 @@ private:
uint16_t preambleLength = 0;
float bitRate = 0.0;
float frequencyDeviation = 0.0;
bool narrowGrid = false;
void registerDio1Isr();
void unregisterDio1Isr();
@ -70,17 +73,25 @@ public:
, hal(configuration.spiHostDevice, configuration.spiFrequency, configuration.csPin, lock)
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
, radio(&radioModule)
, currentModem(Modulation::None)
{}
~Sx1262() override = default;
std::string getName() const override { return name; }
std::string getDescription() const override { return "SX1262 LoRa and FSK capable radio"; }
std::string getDescription() const override { return "Semtech SX1262 LoRa, FSK and LR-FHSS capable radio"; }
bool configure(const Parameter parameter, const float value) override;
bool setParameter(const Parameter parameter, const float value) override;
bool getParameter(const Parameter parameter, float &value) const override;
bool isCapableOf(const Modulation modulation) {
bool canTransmit(const Modulation modulation) override {
return (modulation == Modulation::Fsk) ||
(modulation == Modulation::LoRa) ||
(modulation == Modulation::LrFhss);
}
bool canReceive(const Modulation modulation) override {
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
}

View File

@ -8,13 +8,21 @@ namespace tt::hal::radio {
class ParameterSet {
private:
using Map = std::unordered_map<RadioDevice::Parameter, float, std::hash<int> >;
struct ParameterHash
{
std::size_t operator()(RadioDevice::Parameter t) const
{
return static_cast<std::size_t>(t);
}
};
using Map = std::unordered_map<RadioDevice::Parameter, float, ParameterHash>;
Map parameters;
public:
explicit ParameterSet() {}
explicit ParameterSet(const ParameterSet& other) { parameters = other.parameters; }
~ParameterSet() override = default;
~ParameterSet() = default;
float get(const RadioDevice::Parameter parameter) { return parameters[parameter]; }
void set(const RadioDevice::Parameter parameter, const float value) { parameters[parameter] = value; }
@ -32,7 +40,15 @@ public:
for (const auto& [parameter, value] : parameters) {
// No break on error chosen to apply all parameters,
// a bad one doesn't make the successive tries any more invalid
successful &= radio.configure(parameter, value);
successful &= radio.setParameter(parameter, value);
}
return successful;
}
bool load(const RadioDevice &radio) {
bool successful = true;
for (const auto& [parameter, value] : parameters) {
successful &= radio.getParameter(parameter, parameters[parameter]);
}
return successful;
}

View File

@ -25,6 +25,7 @@ class RadioDevice : public Device {
public:
enum class Modulation {
None,
Fsk,
LoRa,
LrFhss
@ -40,7 +41,8 @@ public:
PreambleLength,
FrequencyDeviation,
DataRate,
AddressWidth
AddressWidth,
NarrowGrid
};
typedef int RxSubscriptionId;
@ -118,8 +120,10 @@ public:
Type getType() const override { return Type::Radio; }
virtual bool configure(const Parameter parameter, const float value) = 0;
virtual bool isCapableOf(const Modulation modulation) = 0;
virtual bool setParameter(const Parameter parameter, const float value) = 0;
virtual bool getParameter(const Parameter parameter, float &value) const = 0;
virtual bool canTransmit(const Modulation modulation) = 0;
virtual bool canReceive(const Modulation modulation) = 0;
virtual bool start(const Modulation modulation) = 0;
virtual bool stop() = 0;

View File

@ -5,6 +5,7 @@
#include <Tactility/Assets.h>
#include <Tactility/StringUtils.h>
#include <Tactility/hal/radio/RadioDevice.h>
#include <Tactility/hal/radio/ParameterSet.h>
#include "Tactility/lvgl/LvglSync.h"
@ -90,6 +91,29 @@ std::string hexdump(const std::vector<uint8_t>& data) {
return oss.str();
}
bool isValidHexString(const std::string& hex) {
if (hex.empty() || (hex.size() % 2) != 0) return false;
for (char c : hex)
if (!std::isxdigit(static_cast<unsigned char>(c)))
return false;
return true;
}
bool parseHexString(const std::string& hex, std::vector<uint8_t>& out) {
if (!isValidHexString(hex)) return false;
out.clear();
out.reserve(hex.size() / 2);
for (size_t i = 0; i < hex.size(); i += 2) {
uint8_t high = std::isdigit(hex[i]) ? (hex[i] - '0')
: (std::tolower(hex[i]) - 'a' + 10);
uint8_t low = std::isdigit(hex[i+1]) ? (hex[i+1] - '0')
: (std::tolower(hex[i+1]) - 'a' + 10);
out.push_back(static_cast<uint8_t>((high << 4) | low));
}
return true;
}
class LoraView {
public:
@ -110,7 +134,8 @@ private:
loraDevs.clear();
for (const auto& radio: radios) {
if (radio->isCapableOf(tt::hal::radio::RadioDevice::Modulation::LoRa)) {
if (radio->canTransmit(tt::hal::radio::RadioDevice::Modulation::LoRa) &&
radio->canReceive(tt::hal::radio::RadioDevice::Modulation::LoRa)) {
loraDevNames.push_back(radio->getName());
loraDevs.push_back(radio);
}
@ -409,11 +434,11 @@ public:
bool configured = true;
buffer = lv_textarea_get_text(frequencyInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Frequency, std::stof(buffer));
configured &= loraDevice->setParameter(Frequency, std::stof(buffer));
}
buffer = lv_textarea_get_text(bandwidthInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Bandwidth, std::stof(buffer));
configured &= loraDevice->setParameter(Bandwidth, std::stof(buffer));
}
buffer = lv_textarea_get_text(syncwordInput);
if (!buffer.empty()) {
@ -421,20 +446,20 @@ public:
std::stringstream ss(buffer);
ss >> std::hex >> syncWord;
configured &= loraDevice->configure(SyncWord, std::stoi(buffer, nullptr, 16));
configured &= loraDevice->setParameter(SyncWord, std::stoi(buffer, nullptr, 16));
}
value = lv_slider_get_value(deBitsInput);
configured &= loraDevice->configure(CodingRate, value);
configured &= loraDevice->setParameter(CodingRate, value);
value = lv_slider_get_value(sfInput);
configured &= loraDevice->configure(SpreadFactor, value);
configured &= loraDevice->setParameter(SpreadFactor, value);
value = lv_slider_get_value(preambleChirpsInput);
configured &= loraDevice->configure(PreambleLength, value);
configured &= loraDevice->setParameter(PreambleLength, value);
buffer = lv_textarea_get_text(txPowInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Power, std::stof(buffer));
configured &= loraDevice->setParameter(Power, std::stof(buffer));
}
}
@ -558,12 +583,11 @@ class ChirpChatterApp : public App {
lv_obj_t* msg_label = lv_label_create(msg_container);
std::vector<uint8_t> data(packet.data, packet.data + packet.size);
std::string messageBuf = "";
if (isPrintableData(data)) {
messageBuf = std::string((char*)packet.data, packet.size);
if (isPrintableData(packet.data)) {
messageBuf = std::string(packet.data.begin(), packet.data.end());
} else {
messageBuf = hexdump(data);
messageBuf = hexdump(packet.data);
}
lv_label_set_text(msg_label, messageBuf.c_str());
lv_obj_set_width(msg_label, lv_pct(100));
@ -793,8 +817,22 @@ public:
void sendMessage() {
std::string message = lv_textarea_get_text(inputField);
std::vector<uint8_t> data(message.begin(), message.end());
loraDevice->transmit(data, [this](hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
std::vector<uint8_t> data;
if (message == "!test1") {
parseHexString("ffffffff1147fec0c60940e5810800009671ad09dd2a0e7841ce266a3d759e967dc32a16bf4d5eecafde28d82b690f22eccf968a", data);
} else if (message == "!ack") {
parseHexString("ffffffff1147fec0f1dee9ab81080000922bf53364151a15", data);
} else if (message == "!gdn8") {
parseHexString("ffffffff1147fec025ffdd7a81080000768023f848619a3782ddadb5f686dc", data);
} else if (message == "!gm") {
parseHexString("ffffffff1147fec08a27ff4b810800003185053566e837fb0b88ade5fc84d9a13e", data);
} else if (message == "!ptest1") {
parseHexString("ffffffff1147fec0ef0e5bd48108000035f4be1f4bf703b3cce235423dd218a0c9ec745032a1f04be19c", data);
} else {
data = std::vector<uint8_t>(message.begin(), message.end());
}
loraDevice->transmit(tt::hal::radio::TxPacket{.data = data}, [this](hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
this->onTxStatus(id, state);
});
}

View File

@ -28,6 +28,8 @@ void RadioDevice::publishRx(const RxPacket& packet) {
const char* toString(RadioDevice::Modulation modulation) {
using enum RadioDevice::Modulation;
switch (modulation) {
case None:
return "none";
case Fsk:
return "FSK";
case LoRa:
@ -62,6 +64,8 @@ const char* toString(RadioDevice::Parameter parameter) {
return TT_STRINGIFY(DataRate);
case AddressWidth:
return TT_STRINGIFY(AddressWidth);
case NarrowGrid:
return TT_STRINGIFY(NarrowGrid);
default:
return "Unknown";
}