Radio: Refactor parameters and ParameterSet
This commit is contained in:
parent
6fabd3354c
commit
982f3e11c7
@ -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;
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user