Lora callbacks refactored to events

This commit is contained in:
Ken Van Hoeylandt 2026-08-28 01:17:59 +02:00
parent 020aa471e2
commit bf6952d88e
7 changed files with 256 additions and 259 deletions

View File

@ -11,14 +11,12 @@
## Higher Priority
- AppHubApp: Prevent download callbacks from accessing a destroyed view. Create a "download task" concept that emits events.
- CrashDiagnostics shouldn't show a QR when there's no callstack
- Apps should be able to specify stack size in their manifest, per architecture.
Use thread_get_stack_space() to find out the unused bytes
- Apps currently have a `Context` object with an `appInstanceId` in it, purely for being able to close the app.
Change it so that the app has its own termination signal that it waits for in the loop, it should subscribe to the event group.
- stopAppFromToolbar() in Tactility.cpp stops the top-most app. Change it so the toolbar knows for which app id it is created, so it can rely on that.
- Warn if file operations are done from prohibited tasks (e.g. lvgl task)
- AppHubApp: Prevent download callbacks from accessing a destroyed view.
- Move USB host task stacks to SPIRAM when available: esp32_usbhost*.cpp
- Get rid of WiFi service (Wifi.cpp/h) in Tactility.cpp
- Make it more clear to end-users that an SD card is required to run Tactility
@ -51,12 +49,12 @@
## Medium Priority
- esp_lvgl_port settings has a large stack size (~9kB) to fix stackoverflow when LVGL events (e.g. button click) do actions like file operations do actions like file operations. Can we reduce the callstack?
- `struct Driver` has an `.owner`, but it's not always set. Either validate on Module construct that it matches, or otherwise set it during module start. The problem: NULL parent currently means that driver is not removable. This clashes with setting it dynamically. Consider some kind of flag to determine removability.
- Consider moving certain drivers into separate modules: audio, bt, wifi, etc
- Consider using https://github.com/Graphify-Labs/graphify
- Consider implementing LVGL gridnav in apps https://lvgl.io/docs/open/9.3/details/auxiliary-modules/gridnav.html
- Make USB host driver disabled by default, so it doesn't consume memory
- Diceware app has large "+" and "-' buttons on Cardputer. It should be smaller.
- TactilityTool: Make API compatibility table (and check for compatibility in the tool itself)
- Improve EspLcdDisplay to contain all the standard configuration options, and implement a default init function. Add a configuration class.
- Unify the way displays are dimmed. Some implementations turn off the display when it's fully dimmed. Make this a separate functionality.
@ -64,6 +62,7 @@
## Lower Priority
- Diceware app has large "+" and "-' buttons on Cardputer. It should be smaller.
- lvgl-module has a keyboard.cpp that creates a `keyboard_group`. This group is set as the default group, so it can also work with trackball(= LVGL "encoder").
Make a separate group that is the default group. The keyboard can then use it (or use its own).
The basic idea is to invert the ownership: now the keyboard group is made the default group, but it's probably more logical to have the default group used by the keyboard.
@ -74,7 +73,6 @@
- CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?)
- Use GPS time to set/update the current time
- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting.
- Consider using non_null (either via MS GSL, or custom)
- Fix system time to not be 1980 (use build year as a minimum). Consider keeping track of the last known time.
- Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754
@ -96,8 +94,6 @@
- Audio recording app
- OTA updates
- If present, use LED to show boot/wifi status
- On crash, try to save the current log to flash or SD card? (this is risky, though, so ask in Discord first)
- Support more than 1 hardware keyboard (see lvgl::hardware_keyboard_set_indev()). LVGL init currently calls keyboard init, but that part should probably be done from the KeyboardDevice base class.
# App Ideas

View File

@ -9,6 +9,7 @@
#include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include <RadioLib.h>
@ -345,12 +346,11 @@ void Sx1262Radio::setState(enum LoraRadioState newState) {
}
LOG_I(TAG, "State: %s -> %s", toString(state), toString(newState));
state = newState;
auto callbacks = stateCallbacks;
unlock();
for (const auto& entry : callbacks) {
entry.callback(settings.device, entry.context, newState);
}
LoraEvent event { .type = LORA_EVENT_STATE, .timestamp = 0, .data = {} };
event.data.state = { .state = newState };
lora_event_emit(settings.device, &event);
}
error_t Sx1262Radio::setModulation(enum LoraModulation newModulation) {
@ -377,76 +377,21 @@ enum LoraModulation Sx1262Radio::getModulation() const {
return result;
}
// Callbacks are invoked on a snapshot of the list, with the radio mutex released:
// consumers take their own locks in callbacks and also call into this API while
// holding those locks, so invoking under the radio mutex would set up an AB-BA
// deadlock between the radio thread and any consumer thread.
void Sx1262Radio::publishRx(const struct LoraRxPacket& packet) {
lock();
auto callbacks = rxCallbacks;
unlock();
for (const auto& entry : callbacks) {
entry.callback(settings.device, entry.context, &packet);
}
void Sx1262Radio::publishRx(const uint8_t* data, size_t length, float rssi, float snr) {
LoraEvent event { .type = LORA_EVENT_RX, .timestamp = 0, .data = {} };
LoraRxEventData& rx = event.data.rx;
const size_t copied_length = std::min(length, sizeof(rx.data));
std::memcpy(rx.data, data, copied_length);
rx.length = copied_length;
rx.rssi = rssi;
rx.snr = snr;
lora_event_emit(settings.device, &event);
}
void Sx1262Radio::publishTx(LoraTxId id, enum LoraTransmissionState txState) {
lock();
auto callbacks = txCallbacks;
unlock();
for (const auto& entry : callbacks) {
entry.callback(settings.device, entry.context, id, txState);
}
}
error_t Sx1262Radio::addRxCallback(void* context, LoraRxCallback callback) {
lock();
rxCallbacks.push_back({context, callback});
unlock();
return ERROR_NONE;
}
error_t Sx1262Radio::removeRxCallback(LoraRxCallback callback) {
lock();
const auto old_size = rxCallbacks.size();
std::erase_if(rxCallbacks, [callback](const auto& entry) { return entry.callback == callback; });
const auto result = (rxCallbacks.size() == old_size) ? ERROR_NOT_FOUND : ERROR_NONE;
unlock();
return result;
}
error_t Sx1262Radio::addStateCallback(void* context, LoraStateCallback callback) {
lock();
stateCallbacks.push_back({context, callback});
unlock();
return ERROR_NONE;
}
error_t Sx1262Radio::removeStateCallback(LoraStateCallback callback) {
lock();
const auto old_size = stateCallbacks.size();
std::erase_if(stateCallbacks, [callback](const auto& entry) { return entry.callback == callback; });
const auto result = (stateCallbacks.size() == old_size) ? ERROR_NOT_FOUND : ERROR_NONE;
unlock();
return result;
}
error_t Sx1262Radio::addTxCallback(void* context, LoraTxCallback callback) {
lock();
txCallbacks.push_back({context, callback});
unlock();
return ERROR_NONE;
}
error_t Sx1262Radio::removeTxCallback(LoraTxCallback callback) {
lock();
const auto old_size = txCallbacks.size();
std::erase_if(txCallbacks, [callback](const auto& entry) { return entry.callback == callback; });
const auto result = (txCallbacks.size() == old_size) ? ERROR_NOT_FOUND : ERROR_NONE;
unlock();
return result;
LoraEvent event { .type = LORA_EVENT_TX, .timestamp = 0, .data = {} };
event.data.tx = { .id = id, .state = txState };
lora_event_emit(settings.device, &event);
}
// endregion
@ -952,15 +897,10 @@ void Sx1262Radio::doReceive() {
} else if (rxSize == 0) {
// Empty read: skip silently to avoid log flooding on spurious IRQs.
} else {
const struct LoraRxPacket packet = {
.data = data.data(),
.length = data.size(),
.rssi = radio.getRSSI(),
.snr = radio.getSNR(),
};
LOG_I(TAG, "RX: %u bytes, RSSI %.1f dBm, SNR %.1f dB", (unsigned)packet.length, packet.rssi, packet.snr);
publishRx(packet);
const float rssi = radio.getRSSI();
const float snr = radio.getSNR();
LOG_I(TAG, "RX: %u bytes, RSSI %.1f dBm, SNR %.1f dB", (unsigned)data.size(), rssi, snr);
publishRx(data.data(), data.size(), rssi, snr);
radio.finishReceive();
}
}

View File

@ -19,10 +19,9 @@ struct Device;
struct GpioDescriptor;
/**
* SX1262 radio engine: owns the radio thread, the TX queue and the callback lists.
* The public methods are thread-safe. Callbacks are invoked on a snapshot of the list with
* the internal mutex released, either from the radio thread (RX, TX progress, state) or from
* the caller of transmit() (QUEUED).
* SX1262 radio engine: owns the radio thread and the TX queue. The public methods are
* thread-safe. State/RX/TX events are published via lora_event_emit(), either from the radio
* thread (RX, TX progress, state) or from the caller of transmit() (QUEUED).
*
* The RadioLib types live behind the RadioParts indirection: RadioLib declares a global
* `class Module` that collides with the kernel's `struct Module` when both are visible
@ -59,12 +58,6 @@ private:
std::vector<uint8_t> data;
};
template<typename Callback>
struct CallbackEntry {
void* context;
Callback callback;
};
const Settings settings;
RadioParts* parts;
mutable RecursiveMutex mutex = {};
@ -80,10 +73,6 @@ private:
TxItem currentTx;
LoraTxId lastTxId = 0;
std::vector<CallbackEntry<LoraStateCallback>> stateCallbacks;
std::vector<CallbackEntry<LoraRxCallback>> rxCallbacks;
std::vector<CallbackEntry<LoraTxCallback>> txCallbacks;
// Parameter store, applied on the next doBegin(). Frequencies/rates are held in base SI
// units (Hz, bit/s) and converted to RadioLib's MHz/kHz/kbps floats in doBegin().
int8_t power = -9;
@ -111,7 +100,7 @@ private:
int32_t threadMain();
void setState(enum LoraRadioState newState);
void publishRx(const struct LoraRxPacket& packet);
void publishRx(const uint8_t* data, size_t length, float rssi, float snr);
void publishTx(LoraTxId id, enum LoraTransmissionState txState);
size_t getTxQueueSize() const;
@ -165,11 +154,4 @@ public:
error_t getParameter(enum LoraParameter parameter, int32_t* value) const;
error_t transmit(const uint8_t* data, size_t length, LoraTxId* id);
error_t addRxCallback(void* context, LoraRxCallback callback);
error_t removeRxCallback(LoraRxCallback callback);
error_t addStateCallback(void* context, LoraStateCallback callback);
error_t removeStateCallback(LoraStateCallback callback);
error_t addTxCallback(void* context, LoraTxCallback callback);
error_t removeTxCallback(LoraTxCallback callback);
};

View File

@ -312,42 +312,6 @@ static error_t api_transmit(Device* device, const uint8_t* data, size_t length,
return radio->transmit(data, length, id);
}
static error_t api_add_rx_callback(Device* device, void* callback_context, LoraRxCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->addRxCallback(callback_context, callback);
}
static error_t api_remove_rx_callback(Device* device, LoraRxCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->removeRxCallback(callback);
}
static error_t api_add_state_callback(Device* device, void* callback_context, LoraStateCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->addStateCallback(callback_context, callback);
}
static error_t api_remove_state_callback(Device* device, LoraStateCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->removeStateCallback(callback);
}
static error_t api_add_tx_callback(Device* device, void* callback_context, LoraTxCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->addTxCallback(callback_context, callback);
}
static error_t api_remove_tx_callback(Device* device, LoraTxCallback callback) {
auto* radio = get_radio(device);
if (radio == nullptr) return ERROR_INVALID_STATE;
return radio->removeTxCallback(callback);
}
static const struct LoraApi sx1262_lora_api = {
.get_radio_state = api_get_radio_state,
.set_enabled = api_set_enabled,
@ -358,12 +322,6 @@ static const struct LoraApi sx1262_lora_api = {
.set_parameter = api_set_parameter,
.get_parameter = api_get_parameter,
.transmit = api_transmit,
.add_rx_callback = api_add_rx_callback,
.remove_rx_callback = api_remove_rx_callback,
.add_state_callback = api_add_state_callback,
.remove_state_callback = api_remove_state_callback,
.add_tx_callback = api_add_tx_callback,
.remove_tx_callback = api_remove_tx_callback,
};
// endregion

View File

@ -5,6 +5,7 @@
#include <stddef.h>
#include <stdint.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h>
#ifdef __cplusplus
@ -93,27 +94,8 @@ enum LoraTransmissionState {
LORA_TRANSMISSION_STATE_ERROR,
};
struct LoraRxPacket {
/** Packet payload. Only valid for the duration of the RX callback. */
const uint8_t* data;
size_t length;
/** Received signal strength in dBm */
float rssi;
/** Signal-to-noise ratio in dB */
float snr;
};
/**
* Callbacks are invoked without any driver lock held, either from the driver's radio
* thread (RX, TX progress, state) or from the thread calling transmit()/set_enabled()
* (the QUEUED TX event and enable/disable state changes). Taking consumer locks in a
* callback is therefore safe, but keep callbacks short: RX processing stalls while
* they run. After remove_*_callback returns, a callback that was already in flight
* may still complete once — disable the radio before destroying callback context.
*/
typedef void (*LoraStateCallback)(struct Device* device, void* context, enum LoraRadioState state);
typedef void (*LoraRxCallback)(struct Device* device, void* context, const struct LoraRxPacket* packet);
typedef void (*LoraTxCallback)(struct Device* device, void* context, LoraTxId id, enum LoraTransmissionState state);
/** Largest RX payload any supported modem can hand back in one packet. */
#define LORA_RX_MAX_PACKET_LENGTH 255
struct LoraApi {
/**
@ -198,59 +180,123 @@ struct LoraApi {
* @return ERROR_NONE on success
*/
error_t (*transmit)(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id);
/**
* Add a callback for received packets.
* @param[in] device the lora device
* @param[in] callback_context the context to pass to the callback
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*add_rx_callback)(struct Device* device, void* callback_context, LoraRxCallback callback);
/**
* Remove a callback for received packets.
* @param[in] device the lora device
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*remove_rx_callback)(struct Device* device, LoraRxCallback callback);
/**
* Add a callback for radio state changes.
* @param[in] device the lora device
* @param[in] callback_context the context to pass to the callback
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*add_state_callback)(struct Device* device, void* callback_context, LoraStateCallback callback);
/**
* Remove a callback for radio state changes.
* @param[in] device the lora device
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*remove_state_callback)(struct Device* device, LoraStateCallback callback);
/**
* Add a callback for transmission progress.
* @param[in] device the lora device
* @param[in] callback_context the context to pass to the callback
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*add_tx_callback)(struct Device* device, void* callback_context, LoraTxCallback callback);
/**
* Remove a callback for transmission progress.
* @param[in] device the lora device
* @param[in] callback the callback function
* @return ERROR_NONE on success
*/
error_t (*remove_tx_callback)(struct Device* device, LoraTxCallback callback);
};
/** Identifies the kind of lora event delivered through lora_event_poll(). */
enum LoraEventType {
LORA_EVENT_STATE, // struct LoraStateEventData
LORA_EVENT_RX, // struct LoraRxEventData
LORA_EVENT_TX, // struct LoraTxEventData
};
/** Data for LORA_EVENT_STATE. */
struct LoraStateEventData {
enum LoraRadioState state;
};
/** Data for LORA_EVENT_RX. */
struct LoraRxEventData {
uint8_t data[LORA_RX_MAX_PACKET_LENGTH];
size_t length;
/** Received signal strength in dBm */
float rssi;
/** Signal-to-noise ratio in dB */
float snr;
};
/** Data for LORA_EVENT_TX. */
struct LoraTxEventData {
LoraTxId id;
enum LoraTransmissionState state;
};
struct LoraEvent {
enum LoraEventType type;
/** Stamped by lora_event_emit(); any value passed in by the caller is ignored. */
uint64_t timestamp;
union {
struct LoraStateEventData state;
struct LoraRxEventData rx;
struct LoraTxEventData tx;
} data;
};
/**
* Number of events that can be queued per subscription before lora_event_emit() starts
* returning ERROR_RESOURCE (dropping the newest event, preserving FIFO order of what's
* already queued).
*/
#define LORA_EVENT_QUEUE_CAPACITY 8
/**
* Caller-owned subscription node, registered with lora_event_subscribe() and drained with
* lora_event_poll(). Events queue by value (FIFO): a poll subscription that falls behind loses
* the oldest un-popped RX packet's data only once the queue is full, not on every new event.
* @warning Fields other than `bit` are for internal use only; do not read or write them directly.
*/
struct LoraEventSubscription {
/** Set by lora_event_subscribe(). Read-only for the caller: OR it into a
* task_event_group_wait() mask (alongside other subscriptions sharing the same
* `event_group`) to block on this subscription and other event sources with one call. */
uint32_t bit;
struct {
/** The lora device this subscription receives events for; set by lora_event_subscribe(). */
struct Device* device;
/** Caller-owned, borrowed; set by lora_event_subscribe(). */
struct TaskEventGroup* event_group;
struct LoraEvent queue[LORA_EVENT_QUEUE_CAPACITY];
uint8_t head;
uint8_t count;
struct LoraEventSubscription* next;
} internal;
};
/**
* Register a subscription for events emitted by @a device.
* @warning Does not work in ISR context.
* @param[in,out] sub subscription to register; owns the storage, must stay alive (and
* stationary) until unsubscribed
* @param[in] event_group caller-owned group to wait on; must outlive @a sub (i.e. be
* destructed only after lora_event_unsubscribe()). To block for an event, call
* task_event_group_wait()/task_event_group_wait_any() on this group (OR sub->bit into the mask,
* or use _wait_any() to include every subscription sharing it), then drain with lora_event_poll().
* @param[in] device the lora device this subscription receives events for
* @retval ERROR_NONE on success
* @retval ERROR_RESOURCE @a event_group has no free bits left to claim; @a sub was not registered
* @retval ERROR_INVALID_STATE @a sub is already registered
*/
error_t lora_event_subscribe(struct LoraEventSubscription* sub, struct TaskEventGroup* event_group, struct Device* device);
/**
* Remove a previously registered subscription.
* @warning Does not work in ISR context.
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
*/
error_t lora_event_unsubscribe(struct LoraEventSubscription* sub);
/**
* Non-blocking: pop the next event for @a sub if one is already queued.
* @warning Never blocks. To wait for an event, block in task_event_group_wait()/
* task_event_group_wait_any() on @a sub's event group first (see lora_event_subscribe()), then
* drain with this in a loop.
* @retval ERROR_NONE @a out_event was filled
* @retval ERROR_TIMEOUT nothing queued right now
*/
error_t lora_event_poll(struct LoraEventSubscription* sub, struct LoraEvent* out_event);
/**
* Emit a lora event to every subscriber of @a device. Called by LoraApi driver
* implementations; not intended for consumers of the API.
* @param[in] device the lora device the event originates from
* @param[in] event the event to emit; @a event->timestamp is overwritten
* @return ERROR_NONE on success, ERROR_NOT_FOUND if @a device has no subscribers
*/
error_t lora_event_emit(struct Device* device, const struct LoraEvent* event);
extern const struct DeviceType LORA_TYPE;
/** @return the first registered lora device, regardless of started state, or NULL if none exists */
@ -265,12 +311,6 @@ bool lora_can_receive(struct Device* device, enum LoraModulation modulation);
error_t lora_set_parameter(struct Device* device, enum LoraParameter parameter, int32_t value);
error_t lora_get_parameter(struct Device* device, enum LoraParameter parameter, int32_t* value);
error_t lora_transmit(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id);
error_t lora_add_rx_callback(struct Device* device, void* callback_context, LoraRxCallback callback);
error_t lora_remove_rx_callback(struct Device* device, LoraRxCallback callback);
error_t lora_add_state_callback(struct Device* device, void* callback_context, LoraStateCallback callback);
error_t lora_remove_state_callback(struct Device* device, LoraStateCallback callback);
error_t lora_add_tx_callback(struct Device* device, void* callback_context, LoraTxCallback callback);
error_t lora_remove_tx_callback(struct Device* device, LoraTxCallback callback);
#ifdef __cplusplus
}

View File

@ -1,12 +1,120 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/lora.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/time.h>
#define LORA_API(device) ((const struct LoraApi*)device_get_driver(device)->api)
/**
* Intrusive singly-linked list of subscriptions, keyed by device. Guarded by a single
* coarse-grained mutex; notifying a subscriber here never invokes caller code (just a struct
* copy and a task_event_group_signal), so there is no reentrancy concern requiring a
* snapshot-then-unlock dance.
*/
static LoraEventSubscription* lora_event_subscriptions = nullptr;
struct LoraEventMutex {
Mutex handle {};
LoraEventMutex() { mutex_construct(&handle); }
~LoraEventMutex() { mutex_destruct(&handle); }
};
static LoraEventMutex lora_event_subscriptions_mutex;
extern "C" {
error_t lora_event_subscribe(LoraEventSubscription* sub, TaskEventGroup* event_group, Device* device) {
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&lora_event_subscriptions_mutex.handle);
// Avoid cyclic subscription list that would loop forever
if (lora_event_subscriptions == sub) {
mutex_unlock(&lora_event_subscriptions_mutex.handle);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->bit = bit;
sub->internal.device = device;
sub->internal.event_group = event_group;
sub->internal.head = 0;
sub->internal.count = 0;
sub->internal.next = lora_event_subscriptions;
lora_event_subscriptions = sub;
mutex_unlock(&lora_event_subscriptions_mutex.handle);
return ERROR_NONE;
}
error_t lora_event_unsubscribe(LoraEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_event_subscriptions_mutex.handle);
for (LoraEventSubscription** link = &lora_event_subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&lora_event_subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
error_t lora_event_emit(Device* device, const LoraEvent* event) {
LoraEvent stamped_event = *event;
stamped_event.timestamp = get_micros_since_boot();
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_event_subscriptions_mutex.handle);
for (LoraEventSubscription* sub = lora_event_subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.device != device) {
continue;
}
if (sub->internal.count >= LORA_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->internal.head + sub->internal.count) % LORA_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = stamped_event;
sub->internal.count++;
if (result != ERROR_RESOURCE) {
result = ERROR_NONE;
}
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&lora_event_subscriptions_mutex.handle);
return result;
}
error_t lora_event_poll(LoraEventSubscription* sub, LoraEvent* out_event) {
mutex_lock(&lora_event_subscriptions_mutex.handle);
bool has_event = sub->internal.count > 0;
if (has_event) {
*out_event = sub->internal.queue[sub->internal.head];
sub->internal.head = (sub->internal.head + 1) % LORA_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&lora_event_subscriptions_mutex.handle);
return has_event ? ERROR_NONE : ERROR_TIMEOUT;
}
struct Device* lora_find_first_registered_device() {
struct Device* found = nullptr;
device_for_each_of_type(&LORA_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
@ -52,30 +160,6 @@ error_t lora_transmit(struct Device* device, const uint8_t* data, size_t length,
return LORA_API(device)->transmit(device, data, length, id);
}
error_t lora_add_rx_callback(struct Device* device, void* callback_context, LoraRxCallback callback) {
return LORA_API(device)->add_rx_callback(device, callback_context, callback);
}
error_t lora_remove_rx_callback(struct Device* device, LoraRxCallback callback) {
return LORA_API(device)->remove_rx_callback(device, callback);
}
error_t lora_add_state_callback(struct Device* device, void* callback_context, LoraStateCallback callback) {
return LORA_API(device)->add_state_callback(device, callback_context, callback);
}
error_t lora_remove_state_callback(struct Device* device, LoraStateCallback callback) {
return LORA_API(device)->remove_state_callback(device, callback);
}
error_t lora_add_tx_callback(struct Device* device, void* callback_context, LoraTxCallback callback) {
return LORA_API(device)->add_tx_callback(device, callback_context, callback);
}
error_t lora_remove_tx_callback(struct Device* device, LoraTxCallback callback) {
return LORA_API(device)->remove_tx_callback(device, callback);
}
const struct DeviceType LORA_TYPE = {
.name = "lora"
};

View File

@ -407,12 +407,9 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(lora_set_parameter),
DEFINE_MODULE_SYMBOL(lora_get_parameter),
DEFINE_MODULE_SYMBOL(lora_transmit),
DEFINE_MODULE_SYMBOL(lora_add_rx_callback),
DEFINE_MODULE_SYMBOL(lora_remove_rx_callback),
DEFINE_MODULE_SYMBOL(lora_add_state_callback),
DEFINE_MODULE_SYMBOL(lora_remove_state_callback),
DEFINE_MODULE_SYMBOL(lora_add_tx_callback),
DEFINE_MODULE_SYMBOL(lora_remove_tx_callback),
DEFINE_MODULE_SYMBOL(lora_event_subscribe),
DEFINE_MODULE_SYMBOL(lora_event_unsubscribe),
DEFINE_MODULE_SYMBOL(lora_event_poll),
DEFINE_MODULE_SYMBOL(LORA_TYPE),
// drivers/usb_host_hid
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),