Radio: Iteration 2 with Sx1262 - TX Update
Not quite as reliable still, but sending works.
This commit is contained in:
parent
9f05bcf066
commit
04edfa7c99
@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS "Source"
|
||||
REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 PwmBacklight driver esp_adc
|
||||
REQUIRES Tactility esp_lcd ST7796 BQ25896 BQ27220 TCA8418 DRV2605 SX126x PwmBacklight driver esp_adc
|
||||
)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <Bq25896.h>
|
||||
#include <Drv2605.h>
|
||||
#include <Sx1262.h>
|
||||
#include <Tactility/hal/Configuration.h>
|
||||
|
||||
#define TPAGER_SPI_TRANSFER_SIZE_LIMIT (480 * 222 * (LV_COLOR_DEPTH / 8))
|
||||
@ -22,6 +23,17 @@ static DeviceVector createDevices() {
|
||||
auto tca8418 = std::make_shared<Tca8418>(I2C_NUM_0);
|
||||
auto keyboard = std::make_shared<TpagerKeyboard>(tca8418);
|
||||
|
||||
auto sx1262 = std::make_shared<Sx1262>("SX1262", Sx1262::Configuration{
|
||||
.spiHostDevice = SPI2_HOST,
|
||||
.spiFrequency = 10'000'000,
|
||||
.csPin = GPIO_NUM_36,
|
||||
.resetPin = GPIO_NUM_47,
|
||||
.busyPin = GPIO_NUM_48,
|
||||
.irqPin = GPIO_NUM_14,
|
||||
.tcxoVoltage = 3.0,
|
||||
.useRegulatorLdo = false
|
||||
});
|
||||
|
||||
return std::vector<std::shared_ptr<Device>> {
|
||||
tca8418,
|
||||
std::make_shared<Bq25896>(I2C_NUM_0),
|
||||
@ -31,7 +43,8 @@ static DeviceVector createDevices() {
|
||||
createTpagerSdCard(),
|
||||
createDisplay(),
|
||||
keyboard,
|
||||
std::make_shared<TpagerEncoder>()
|
||||
std::make_shared<TpagerEncoder>(),
|
||||
sx1262
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -114,20 +114,24 @@ long RadiolibTactilityHal::pulseIn(uint32_t pin, uint32_t state, unsigned long t
|
||||
}
|
||||
|
||||
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));
|
||||
if (!spiInitialized) {
|
||||
TT_LOG_I(TAG, "SPI Begin!");
|
||||
spi_device_interface_config_t devcfg = {};
|
||||
devcfg.clock_speed_hz = spiFrequency;
|
||||
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));
|
||||
}
|
||||
spiInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiBeginTransaction() {
|
||||
// not needed - in ESP32 Arduino core, this function
|
||||
// repeats clock div, mode and bit order configuration
|
||||
// This function is used to set up the transaction (speed, bit order, mode, ...).
|
||||
// With the ESP-IDF HAL this is automatically done, so no code needed.
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
|
||||
@ -151,5 +155,8 @@ void RadiolibTactilityHal::spiEndTransaction() {
|
||||
}
|
||||
|
||||
void RadiolibTactilityHal::spiEnd() {
|
||||
|
||||
if (spiInitialized) {
|
||||
spi_bus_remove_device(spiDeviceHandle);
|
||||
spiInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,12 +16,14 @@
|
||||
class RadiolibTactilityHal : public RadioLibHal {
|
||||
private:
|
||||
spi_host_device_t spiHostDevice;
|
||||
int spiFrequency;
|
||||
gpio_num_t csPin;
|
||||
spi_device_handle_t spiDeviceHandle;
|
||||
std::shared_ptr<tt::Lock> lock;
|
||||
bool spiInitialized;
|
||||
|
||||
public:
|
||||
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, gpio_num_t csPin)
|
||||
explicit RadiolibTactilityHal(spi_host_device_t spiHostDevice, int spiFrequency, gpio_num_t csPin, std::shared_ptr<tt::Lock> spiLock)
|
||||
: RadioLibHal(
|
||||
GPIO_MODE_INPUT,
|
||||
GPIO_MODE_OUTPUT,
|
||||
@ -30,8 +32,12 @@ public:
|
||||
GPIO_INTR_POSEDGE,
|
||||
GPIO_INTR_NEGEDGE)
|
||||
, spiHostDevice(spiHostDevice)
|
||||
, spiFrequency(spiFrequency)
|
||||
, csPin(csPin)
|
||||
, lock(tt::hal::spi::getLock(spiHostDevice)) {}
|
||||
, lock(spiLock)
|
||||
, spiInitialized(false) {
|
||||
if (!lock) lock = tt::hal::spi::getLock(spiHostDevice);
|
||||
}
|
||||
|
||||
void init() override;
|
||||
void term() override;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
constexpr const char* TAG = "Sx1262";
|
||||
|
||||
void IRAM_ATTR dio1handler(void* context) {
|
||||
((Sx1262*)context)->setRxEvent();
|
||||
((Sx1262*)context)->dio1Event();
|
||||
}
|
||||
|
||||
bool Sx1262::configure(const Parameter parameter, const float value) {
|
||||
@ -15,24 +15,31 @@ bool Sx1262::configure(const Parameter parameter, const float value) {
|
||||
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;
|
||||
@ -48,6 +55,21 @@ bool Sx1262::configure(const Parameter parameter, const float value) {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
gpio_config_t conf = {
|
||||
.pin_bit_mask = (1ULL<<configuration.irqPin),
|
||||
.mode = (gpio_mode_t)GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
||||
.intr_type = (gpio_int_type_t)gpiohal.dev->pin[configuration.irqPin].int_type,
|
||||
};
|
||||
gpio_config(&conf);
|
||||
|
||||
// We cannot use the RadioLib API to register this action,
|
||||
// as it does not have the capability to pass an instance pointer via context.
|
||||
// A trampoline has been tried, but is not linkable to be in IRAM_ATTR (dangerous relocation).
|
||||
@ -61,8 +83,35 @@ 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);
|
||||
}
|
||||
|
||||
int32_t Sx1262::threadMain(const Modulation modulation) {
|
||||
using enum ExchangeState;
|
||||
|
||||
uint16_t rc = RADIOLIB_ERR_NONE;
|
||||
|
||||
@ -78,6 +127,13 @@ 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,
|
||||
@ -101,41 +157,85 @@ int32_t Sx1262::threadMain(const Modulation modulation) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
registerDio1Isr();
|
||||
setState(State::On);
|
||||
|
||||
TT_LOG_I(TAG, "SX1262 device ready to receive!");
|
||||
exchangeState = Receive;
|
||||
|
||||
while (!isThreadInterrupted()) {
|
||||
radio.startReceive();
|
||||
getEventFlag().wait(RADIO_TERMINATE_BIT, RADIO_RECEIVED_BIT);
|
||||
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);
|
||||
|
||||
// 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
|
||||
};
|
||||
if ((eventFlags & RADIO_TRANSMIT_QUEUED_BIT) && (getTxQueueSize() > 0)) {
|
||||
currentTx = popNextQueuedTx();
|
||||
radio.standby();
|
||||
uint16_t rc = radio.startTransmit(currentTx.data.data(), currentTx.data.size());
|
||||
if (rc == RADIOLIB_ERR_NONE) {
|
||||
exchangeState = TransmitWaiting;
|
||||
currentTx.callback(currentTx.id, TransmissionState::PendingTransmit);
|
||||
|
||||
publishRx(rxPacket);
|
||||
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);
|
||||
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 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);
|
||||
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;
|
||||
|
||||
// A delay is needed before a new command
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(GPIO_NUM_9, 0);
|
||||
} else {
|
||||
TT_LOG_W(TAG, "Unhandled event, flag=%X", eventFlags);
|
||||
}
|
||||
delete[] dataBuffer;
|
||||
}
|
||||
|
||||
unregisterDio1Isr();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ class Sx1262 final : public tt::hal::radio::RadioDevice {
|
||||
public:
|
||||
struct Configuration {
|
||||
spi_host_device_t spiHostDevice;
|
||||
int spiFrequency;
|
||||
gpio_num_t csPin;
|
||||
gpio_num_t resetPin;
|
||||
gpio_num_t busyPin;
|
||||
@ -22,6 +23,14 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr auto SX1262_DIO1_EVENT_BIT = BIT8;
|
||||
|
||||
enum class ExchangeState {
|
||||
Idle,
|
||||
Receive,
|
||||
//TransmitInitiated,
|
||||
TransmitWaiting
|
||||
};
|
||||
|
||||
std::string name;
|
||||
const Configuration configuration;
|
||||
@ -29,7 +38,9 @@ private:
|
||||
RadiolibTactilityHal hal;
|
||||
Module radioModule;
|
||||
SX1262 radio;
|
||||
IsrTrampolines<Sx1262>::SlotNumber isrTrampolineSlot;
|
||||
TxPacket currentTx;
|
||||
ExchangeState exchangeState;
|
||||
|
||||
int8_t power = 0;
|
||||
float frequency = 0.0;
|
||||
float bandwidth = 0.0;
|
||||
@ -46,19 +57,16 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
explicit Sx1262(const std::string& name, const Configuration& configuration)
|
||||
: RadioDevice(name, 1024)
|
||||
explicit Sx1262(const std::string& name, const Configuration& configuration, std::shared_ptr<tt::Lock> lock = nullptr)
|
||||
: RadioDevice(name, 4096)
|
||||
, name(name)
|
||||
, configuration(configuration)
|
||||
, hal(configuration.spiHostDevice, configuration.csPin)
|
||||
, hal(configuration.spiHostDevice, configuration.spiFrequency, configuration.csPin, lock)
|
||||
, radioModule(&hal, configuration.csPin, configuration.irqPin, configuration.resetPin, configuration.busyPin)
|
||||
, radio(&radioModule) {
|
||||
registerDio1Isr();
|
||||
}
|
||||
, radio(&radioModule)
|
||||
, exchangeState(ExchangeState::Idle) {}
|
||||
|
||||
~Sx1262() override {
|
||||
unregisterDio1Isr();
|
||||
}
|
||||
~Sx1262() override = default;
|
||||
|
||||
std::string getName() const override { return name; }
|
||||
|
||||
@ -70,7 +78,10 @@ public:
|
||||
return (modulation == Modulation::Fsk) || (modulation == Modulation::LoRa);
|
||||
}
|
||||
|
||||
void IRAM_ATTR setRxEvent() { getEventFlag().set(RADIO_RECEIVED_BIT); }
|
||||
bool hasReceived();
|
||||
|
||||
void dio1Event();
|
||||
//void IRAM_ATTR setRxEvent() { rxFlag = true; }
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Device.h"
|
||||
|
||||
#include <Tactility/EventFlag.h>
|
||||
#include <Tactility/Lock.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Thread.h>
|
||||
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
namespace tt::hal::radio {
|
||||
@ -39,6 +42,8 @@ public:
|
||||
};
|
||||
|
||||
typedef int RxSubscriptionId;
|
||||
typedef int TxId;
|
||||
|
||||
|
||||
enum class State {
|
||||
PendingOn,
|
||||
@ -48,8 +53,24 @@ public:
|
||||
Off
|
||||
};
|
||||
|
||||
private:
|
||||
enum class TransmissionState {
|
||||
Queued,
|
||||
PendingTransmit,
|
||||
Transmitted,
|
||||
Timeout,
|
||||
Error
|
||||
};
|
||||
|
||||
using TxStateCallback = std::function<void(TxId id, TransmissionState state)>;
|
||||
|
||||
protected:
|
||||
struct TxPacket {
|
||||
TxId id;
|
||||
std::vector<uint8_t> data;
|
||||
TxStateCallback callback;
|
||||
};
|
||||
|
||||
private:
|
||||
struct RxSubscription {
|
||||
RxSubscriptionId id;
|
||||
std::shared_ptr<std::function<void(Device::Id id, const RxPacket&)>> onData;
|
||||
@ -63,12 +84,16 @@ private:
|
||||
std::unique_ptr<Thread> _Nullable thread;
|
||||
bool threadInterrupted = false;
|
||||
std::vector<RxSubscription> rxSubscriptions;
|
||||
std::deque<TxPacket> txQueue;
|
||||
TxId lastTxId = 0;
|
||||
RxSubscriptionId lastRxSubscriptionId = 0;
|
||||
|
||||
protected:
|
||||
|
||||
static constexpr auto RADIO_TERMINATE_BIT = BIT0;
|
||||
static constexpr auto RADIO_RECEIVED_BIT = BIT1;
|
||||
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; }
|
||||
@ -76,6 +101,23 @@ protected:
|
||||
bool isThreadInterrupted() const;
|
||||
void setState(State newState);
|
||||
|
||||
size_t getTxQueueSize() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
const auto size = txQueue.size();
|
||||
return size;
|
||||
}
|
||||
|
||||
TxPacket popNextQueuedTx() {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
auto tx = std::move(txQueue.front());
|
||||
txQueue.pop_front();
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
void publishRx(const RxPacket& packet);
|
||||
|
||||
public:
|
||||
@ -94,6 +136,21 @@ public:
|
||||
bool start(const Modulation modulation);
|
||||
bool stop();
|
||||
|
||||
TxId transmit(uint8_t *data, const size_t size, TxStateCallback callback) {
|
||||
return transmit(std::vector<uint8_t>(data, data + size), callback);
|
||||
}
|
||||
|
||||
TxId transmit(const std::vector<uint8_t>& data, TxStateCallback callback) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
const auto txId = lastTxId;
|
||||
txQueue.push_back(TxPacket{.id = txId, .data = data, .callback = callback});
|
||||
callback(txId, TransmissionState::Queued);
|
||||
lastTxId++;
|
||||
getEventFlag().set(RADIO_TRANSMIT_QUEUED_BIT);
|
||||
return txId;
|
||||
}
|
||||
|
||||
RxSubscriptionId subscribeRx(const std::function<void(Device::Id id, const RxPacket&)>& onData) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user