Improve API, fix issue

This commit is contained in:
Ken Van Hoeylandt 2026-08-28 08:54:55 +02:00
parent bf6952d88e
commit 5fed7c32d9
4 changed files with 453 additions and 112 deletions

View File

@ -8,8 +8,6 @@
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <initializer_list> #include <initializer_list>
#include <RadioLib.h> #include <RadioLib.h>
@ -348,9 +346,7 @@ void Sx1262Radio::setState(enum LoraRadioState newState) {
state = newState; state = newState;
unlock(); unlock();
LoraEvent event { .type = LORA_EVENT_STATE, .timestamp = 0, .data = {} }; lora_state_event_emit(settings.device, newState);
event.data.state = { .state = newState };
lora_event_emit(settings.device, &event);
} }
error_t Sx1262Radio::setModulation(enum LoraModulation newModulation) { error_t Sx1262Radio::setModulation(enum LoraModulation newModulation) {
@ -378,20 +374,11 @@ enum LoraModulation Sx1262Radio::getModulation() const {
} }
void Sx1262Radio::publishRx(const uint8_t* data, size_t length, float rssi, float snr) { void Sx1262Radio::publishRx(const uint8_t* data, size_t length, float rssi, float snr) {
LoraEvent event { .type = LORA_EVENT_RX, .timestamp = 0, .data = {} }; lora_rx_event_emit(settings.device, data, length, rssi, snr);
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) { void Sx1262Radio::publishTx(LoraTxId id, enum LoraTransmissionState txState) {
LoraEvent event { .type = LORA_EVENT_TX, .timestamp = 0, .data = {} }; lora_tx_event_emit(settings.device, id, txState);
event.data.tx = { .id = id, .state = txState };
lora_event_emit(settings.device, &event);
} }
// endregion // endregion

View File

@ -182,20 +182,97 @@ struct LoraApi {
error_t (*transmit)(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id); error_t (*transmit)(struct Device* device, const uint8_t* data, size_t length, LoraTxId* id);
}; };
/** Identifies the kind of lora event delivered through lora_event_poll(). */ // region State events
enum LoraEventType {
LORA_EVENT_STATE, // struct LoraStateEventData
LORA_EVENT_RX, // struct LoraRxEventData
LORA_EVENT_TX, // struct LoraTxEventData
};
/** Data for LORA_EVENT_STATE. */ struct LoraStateEvent {
struct LoraStateEventData { /** Stamped by lora_state_event_emit(); any value passed in by the caller is ignored. */
uint64_t timestamp;
enum LoraRadioState state; enum LoraRadioState state;
}; };
/** Data for LORA_EVENT_RX. */ /**
struct LoraRxEventData { * Number of events that can be queued per subscription before lora_state_event_emit() starts
* returning ERROR_RESOURCE (dropping the newest event, preserving FIFO order of what's
* already queued).
*/
#define LORA_STATE_EVENT_QUEUE_CAPACITY 4
/**
* Caller-owned subscription node, registered with lora_state_event_subscribe() and drained with
* lora_state_event_poll(). Events queue by value (FIFO).
* @warning Fields other than `bit` are for internal use only; do not read or write them directly.
*/
struct LoraStateEventSubscription {
/** Set by lora_state_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_state_event_subscribe(). */
struct Device* device;
/** Caller-owned, borrowed; set by lora_state_event_subscribe(). */
struct TaskEventGroup* event_group;
struct LoraStateEvent queue[LORA_STATE_EVENT_QUEUE_CAPACITY];
uint8_t head;
uint8_t count;
struct LoraStateEventSubscription* next;
} internal;
};
/**
* Register a subscription for radio state changes 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_state_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_state_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_state_event_subscribe(struct LoraStateEventSubscription* 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_state_event_unsubscribe(struct LoraStateEventSubscription* 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_state_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_state_event_poll(struct LoraStateEventSubscription* sub, struct LoraStateEvent* out_event);
/**
* Emit a radio state 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] state the new radio state
* @return ERROR_NONE on success, ERROR_NOT_FOUND if @a device has no subscribers
*/
error_t lora_state_event_emit(struct Device* device, enum LoraRadioState state);
// endregion
// region RX events
struct LoraRxEvent {
/** Stamped by lora_rx_event_emit(); any value passed in by the caller is ignored. */
uint64_t timestamp;
uint8_t data[LORA_RX_MAX_PACKET_LENGTH]; uint8_t data[LORA_RX_MAX_PACKET_LENGTH];
size_t length; size_t length;
/** Received signal strength in dBm */ /** Received signal strength in dBm */
@ -204,104 +281,178 @@ struct LoraRxEventData {
float snr; 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 * Number of events that can be queued per subscription before lora_rx_event_emit() starts
* returning ERROR_RESOURCE (dropping the newest event, preserving FIFO order of what's * returning ERROR_RESOURCE (dropping the newest event, preserving FIFO order of what's
* already queued). * already queued).
*/ */
#define LORA_EVENT_QUEUE_CAPACITY 8 #define LORA_RX_EVENT_QUEUE_CAPACITY 8
/** /**
* Caller-owned subscription node, registered with lora_event_subscribe() and drained with * Caller-owned subscription node, registered with lora_rx_event_subscribe() and drained with
* lora_event_poll(). Events queue by value (FIFO): a poll subscription that falls behind loses * lora_rx_event_poll(). Events queue by value (FIFO): a poll subscription that falls behind
* the oldest un-popped RX packet's data only once the queue is full, not on every new event. * loses the oldest un-popped 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. * @warning Fields other than `bit` are for internal use only; do not read or write them directly.
*/ */
struct LoraEventSubscription { struct LoraRxEventSubscription {
/** Set by lora_event_subscribe(). Read-only for the caller: OR it into a /** Set by lora_rx_event_subscribe(). Read-only for the caller: OR it into a
* task_event_group_wait() mask (alongside other subscriptions sharing the same * 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. */ * `event_group`) to block on this subscription and other event sources with one call. */
uint32_t bit; uint32_t bit;
struct { struct {
/** The lora device this subscription receives events for; set by lora_event_subscribe(). */ /** The lora device this subscription receives events for; set by lora_rx_event_subscribe(). */
struct Device* device; struct Device* device;
/** Caller-owned, borrowed; set by lora_event_subscribe(). */ /** Caller-owned, borrowed; set by lora_rx_event_subscribe(). */
struct TaskEventGroup* event_group; struct TaskEventGroup* event_group;
struct LoraEvent queue[LORA_EVENT_QUEUE_CAPACITY]; struct LoraRxEvent queue[LORA_RX_EVENT_QUEUE_CAPACITY];
uint8_t head; uint8_t head;
uint8_t count; uint8_t count;
struct LoraEventSubscription* next; struct LoraRxEventSubscription* next;
} internal; } internal;
}; };
/** /**
* Register a subscription for events emitted by @a device. * Register a subscription for received packets emitted by @a device.
* @warning Does not work in ISR context. * @warning Does not work in ISR context.
* @param[in,out] sub subscription to register; owns the storage, must stay alive (and * @param[in,out] sub subscription to register; owns the storage, must stay alive (and
* stationary) until unsubscribed * stationary) until unsubscribed
* @param[in] event_group caller-owned group to wait on; must outlive @a sub (i.e. be * @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 * destructed only after lora_rx_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, * 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(). * or use _wait_any() to include every subscription sharing it), then drain with
* lora_rx_event_poll().
* @param[in] device the lora device this subscription receives events for * @param[in] device the lora device this subscription receives events for
* @retval ERROR_NONE on success * @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_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 * @retval ERROR_INVALID_STATE @a sub is already registered
*/ */
error_t lora_event_subscribe(struct LoraEventSubscription* sub, struct TaskEventGroup* event_group, struct Device* device); error_t lora_rx_event_subscribe(struct LoraRxEventSubscription* sub, struct TaskEventGroup* event_group, struct Device* device);
/** /**
* Remove a previously registered subscription. * Remove a previously registered subscription.
* @warning Does not work in ISR context. * @warning Does not work in ISR context.
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists * @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
*/ */
error_t lora_event_unsubscribe(struct LoraEventSubscription* sub); error_t lora_rx_event_unsubscribe(struct LoraRxEventSubscription* sub);
/** /**
* Non-blocking: pop the next event for @a sub if one is already queued. * 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()/ * @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 * task_event_group_wait_any() on @a sub's event group first (see lora_rx_event_subscribe()),
* drain with this in a loop. * then drain with this in a loop.
* @retval ERROR_NONE @a out_event was filled * @retval ERROR_NONE @a out_event was filled
* @retval ERROR_TIMEOUT nothing queued right now * @retval ERROR_TIMEOUT nothing queued right now
*/ */
error_t lora_event_poll(struct LoraEventSubscription* sub, struct LoraEvent* out_event); error_t lora_rx_event_poll(struct LoraRxEventSubscription* sub, struct LoraRxEvent* out_event);
/** /**
* Emit a lora event to every subscriber of @a device. Called by LoraApi driver * Emit a received-packet event to every subscriber of @a device. Called by LoraApi driver
* implementations; not intended for consumers of the API. * implementations; not intended for consumers of the API.
* @param[in] device the lora device the event originates from * @param[in] device the lora device the event originates from
* @param[in] event the event to emit; @a event->timestamp is overwritten * @param[in] data the packet payload; copied, truncated to LORA_RX_MAX_PACKET_LENGTH
* @param[in] length the payload length in bytes
* @param[in] rssi received signal strength in dBm
* @param[in] snr signal-to-noise ratio in dB
* @return ERROR_NONE on success, ERROR_NOT_FOUND if @a device has no subscribers * @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); error_t lora_rx_event_emit(struct Device* device, const uint8_t* data, size_t length, float rssi, float snr);
// endregion
// region TX events
struct LoraTxEvent {
/** Stamped by lora_tx_event_emit(); any value passed in by the caller is ignored. */
uint64_t timestamp;
LoraTxId id;
enum LoraTransmissionState state;
};
/**
* Number of events that can be queued per subscription before lora_tx_event_emit() starts
* returning ERROR_RESOURCE (dropping the newest event, preserving FIFO order of what's
* already queued).
*/
#define LORA_TX_EVENT_QUEUE_CAPACITY 4
/**
* Caller-owned subscription node, registered with lora_tx_event_subscribe() and drained with
* lora_tx_event_poll(). Events queue by value (FIFO).
* @warning Fields other than `bit` are for internal use only; do not read or write them directly.
*/
struct LoraTxEventSubscription {
/** Set by lora_tx_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_tx_event_subscribe(). */
struct Device* device;
/** Caller-owned, borrowed; set by lora_tx_event_subscribe(). */
struct TaskEventGroup* event_group;
struct LoraTxEvent queue[LORA_TX_EVENT_QUEUE_CAPACITY];
uint8_t head;
uint8_t count;
struct LoraTxEventSubscription* next;
} internal;
};
/**
* Register a subscription for transmission progress 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_tx_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_tx_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_tx_event_subscribe(struct LoraTxEventSubscription* 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_tx_event_unsubscribe(struct LoraTxEventSubscription* 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_tx_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_tx_event_poll(struct LoraTxEventSubscription* sub, struct LoraTxEvent* out_event);
/**
* Emit a transmission-progress 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] id the transmission this event reports progress for
* @param[in] state the transmission's new state
* @return ERROR_NONE on success, ERROR_NOT_FOUND if @a device has no subscribers
*/
error_t lora_tx_event_emit(struct Device* device, LoraTxId id, enum LoraTransmissionState state);
// endregion
extern const struct DeviceType LORA_TYPE; extern const struct DeviceType LORA_TYPE;
/** @return the first registered lora device, regardless of started state, or NULL if none exists */
struct Device* lora_find_first_registered_device(void);
error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state); error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state);
error_t lora_set_enabled(struct Device* device, bool enabled); error_t lora_set_enabled(struct Device* device, bool enabled);
error_t lora_set_modulation(struct Device* device, enum LoraModulation modulation); error_t lora_set_modulation(struct Device* device, enum LoraModulation modulation);

View File

@ -5,15 +5,10 @@
#include <tactility/driver.h> #include <tactility/driver.h>
#include <tactility/time.h> #include <tactility/time.h>
#define LORA_API(device) ((const struct LoraApi*)device_get_driver(device)->api) #include <algorithm>
#include <cstring>
/** #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 { struct LoraEventMutex {
Mutex handle {}; Mutex handle {};
@ -21,24 +16,39 @@ struct LoraEventMutex {
~LoraEventMutex() { mutex_destruct(&handle); } ~LoraEventMutex() { mutex_destruct(&handle); }
}; };
static LoraEventMutex lora_event_subscriptions_mutex; // Each event kind gets its own intrusive singly-linked subscription list, keyed by device, and
// its own coarse-grained mutex. Notifying a subscriber 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 LoraStateEventSubscription* lora_state_event_subscriptions = nullptr;
static LoraEventMutex lora_state_event_subscriptions_mutex;
static LoraRxEventSubscription* lora_rx_event_subscriptions = nullptr;
static LoraEventMutex lora_rx_event_subscriptions_mutex;
static LoraTxEventSubscription* lora_tx_event_subscriptions = nullptr;
static LoraEventMutex lora_tx_event_subscriptions_mutex;
extern "C" { extern "C" {
error_t lora_event_subscribe(LoraEventSubscription* sub, TaskEventGroup* event_group, Device* device) { // region State events
error_t lora_state_event_subscribe(LoraStateEventSubscription* sub, TaskEventGroup* event_group, Device* device) {
uint32_t bit; uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit); error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) { if (claim_result != ERROR_NONE) {
return claim_result; return claim_result;
} }
mutex_lock(&lora_event_subscriptions_mutex.handle); mutex_lock(&lora_state_event_subscriptions_mutex.handle);
// Avoid cyclic subscription list that would loop forever // Avoid cyclic subscription list that would loop forever
if (lora_event_subscriptions == sub) { for (LoraStateEventSubscription* existing = lora_state_event_subscriptions; existing != nullptr; existing = existing->internal.next) {
mutex_unlock(&lora_event_subscriptions_mutex.handle); if (existing == sub) {
task_event_group_release_bit(event_group, bit); mutex_unlock(&lora_state_event_subscriptions_mutex.handle);
return ERROR_INVALID_STATE; task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
} }
sub->bit = bit; sub->bit = bit;
@ -46,25 +56,25 @@ error_t lora_event_subscribe(LoraEventSubscription* sub, TaskEventGroup* event_g
sub->internal.event_group = event_group; sub->internal.event_group = event_group;
sub->internal.head = 0; sub->internal.head = 0;
sub->internal.count = 0; sub->internal.count = 0;
sub->internal.next = lora_event_subscriptions; sub->internal.next = lora_state_event_subscriptions;
lora_event_subscriptions = sub; lora_state_event_subscriptions = sub;
mutex_unlock(&lora_event_subscriptions_mutex.handle); mutex_unlock(&lora_state_event_subscriptions_mutex.handle);
return ERROR_NONE; return ERROR_NONE;
} }
error_t lora_event_unsubscribe(LoraEventSubscription* sub) { error_t lora_state_event_unsubscribe(LoraStateEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND; error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_event_subscriptions_mutex.handle); mutex_lock(&lora_state_event_subscriptions_mutex.handle);
for (LoraEventSubscription** link = &lora_event_subscriptions; *link != nullptr; link = &(*link)->internal.next) { for (LoraStateEventSubscription** link = &lora_state_event_subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) { if (*link == sub) {
*link = sub->internal.next; *link = sub->internal.next;
result = ERROR_NONE; result = ERROR_NONE;
break; break;
} }
} }
mutex_unlock(&lora_event_subscriptions_mutex.handle); mutex_unlock(&lora_state_event_subscriptions_mutex.handle);
if (result == ERROR_NONE) { if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit); task_event_group_release_bit(sub->internal.event_group, sub->bit);
@ -73,24 +83,23 @@ error_t lora_event_unsubscribe(LoraEventSubscription* sub) {
return result; return result;
} }
error_t lora_event_emit(Device* device, const LoraEvent* event) { error_t lora_state_event_emit(Device* device, enum LoraRadioState state) {
LoraEvent stamped_event = *event; LoraStateEvent stamped_event { .timestamp = get_micros_since_boot(), .state = state };
stamped_event.timestamp = get_micros_since_boot();
error_t result = ERROR_NOT_FOUND; error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_event_subscriptions_mutex.handle); mutex_lock(&lora_state_event_subscriptions_mutex.handle);
for (LoraEventSubscription* sub = lora_event_subscriptions; sub != nullptr; sub = sub->internal.next) { for (LoraStateEventSubscription* sub = lora_state_event_subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.device != device) { if (sub->internal.device != device) {
continue; continue;
} }
if (sub->internal.count >= LORA_EVENT_QUEUE_CAPACITY) { if (sub->internal.count >= LORA_STATE_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE; result = ERROR_RESOURCE;
continue; continue;
} }
uint8_t tail = (sub->internal.head + sub->internal.count) % LORA_EVENT_QUEUE_CAPACITY; uint8_t tail = (sub->internal.head + sub->internal.count) % LORA_STATE_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = stamped_event; sub->internal.queue[tail] = stamped_event;
sub->internal.count++; sub->internal.count++;
if (result != ERROR_RESOURCE) { if (result != ERROR_RESOURCE) {
@ -98,32 +107,220 @@ error_t lora_event_emit(Device* device, const LoraEvent* event) {
} }
task_event_group_signal(sub->internal.event_group, sub->bit); task_event_group_signal(sub->internal.event_group, sub->bit);
} }
mutex_unlock(&lora_event_subscriptions_mutex.handle); mutex_unlock(&lora_state_event_subscriptions_mutex.handle);
return result; return result;
} }
error_t lora_event_poll(LoraEventSubscription* sub, LoraEvent* out_event) { error_t lora_state_event_poll(LoraStateEventSubscription* sub, LoraStateEvent* out_event) {
mutex_lock(&lora_event_subscriptions_mutex.handle); mutex_lock(&lora_state_event_subscriptions_mutex.handle);
bool has_event = sub->internal.count > 0; bool has_event = sub->internal.count > 0;
if (has_event) { if (has_event) {
*out_event = sub->internal.queue[sub->internal.head]; *out_event = sub->internal.queue[sub->internal.head];
sub->internal.head = (sub->internal.head + 1) % LORA_EVENT_QUEUE_CAPACITY; sub->internal.head = (sub->internal.head + 1) % LORA_STATE_EVENT_QUEUE_CAPACITY;
sub->internal.count--; sub->internal.count--;
} }
mutex_unlock(&lora_event_subscriptions_mutex.handle); mutex_unlock(&lora_state_event_subscriptions_mutex.handle);
return has_event ? ERROR_NONE : ERROR_TIMEOUT; return has_event ? ERROR_NONE : ERROR_TIMEOUT;
} }
struct Device* lora_find_first_registered_device() { // endregion
struct Device* found = nullptr;
device_for_each_of_type(&LORA_TYPE, &found, [](struct Device* dev, void* ctx) -> bool { // region RX events
*static_cast<struct Device**>(ctx) = dev;
return false; error_t lora_rx_event_subscribe(LoraRxEventSubscription* sub, TaskEventGroup* event_group, Device* device) {
}); uint32_t bit;
return found; error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&lora_rx_event_subscriptions_mutex.handle);
// Avoid cyclic subscription list that would loop forever
for (LoraRxEventSubscription* existing = lora_rx_event_subscriptions; existing != nullptr; existing = existing->internal.next) {
if (existing == sub) {
mutex_unlock(&lora_rx_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_rx_event_subscriptions;
lora_rx_event_subscriptions = sub;
mutex_unlock(&lora_rx_event_subscriptions_mutex.handle);
return ERROR_NONE;
} }
error_t lora_rx_event_unsubscribe(LoraRxEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_rx_event_subscriptions_mutex.handle);
for (LoraRxEventSubscription** link = &lora_rx_event_subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&lora_rx_event_subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
error_t lora_rx_event_emit(Device* device, const uint8_t* data, size_t length, float rssi, float snr) {
LoraRxEvent stamped_event {};
stamped_event.timestamp = get_micros_since_boot();
stamped_event.length = std::min(length, sizeof(stamped_event.data));
std::memcpy(stamped_event.data, data, stamped_event.length);
stamped_event.rssi = rssi;
stamped_event.snr = snr;
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_rx_event_subscriptions_mutex.handle);
for (LoraRxEventSubscription* sub = lora_rx_event_subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.device != device) {
continue;
}
if (sub->internal.count >= LORA_RX_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->internal.head + sub->internal.count) % LORA_RX_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_rx_event_subscriptions_mutex.handle);
return result;
}
error_t lora_rx_event_poll(LoraRxEventSubscription* sub, LoraRxEvent* out_event) {
mutex_lock(&lora_rx_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_RX_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&lora_rx_event_subscriptions_mutex.handle);
return has_event ? ERROR_NONE : ERROR_TIMEOUT;
}
// endregion
// region TX events
error_t lora_tx_event_subscribe(LoraTxEventSubscription* 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_tx_event_subscriptions_mutex.handle);
// Avoid cyclic subscription list that would loop forever
for (LoraTxEventSubscription* existing = lora_tx_event_subscriptions; existing != nullptr; existing = existing->internal.next) {
if (existing == sub) {
mutex_unlock(&lora_tx_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_tx_event_subscriptions;
lora_tx_event_subscriptions = sub;
mutex_unlock(&lora_tx_event_subscriptions_mutex.handle);
return ERROR_NONE;
}
error_t lora_tx_event_unsubscribe(LoraTxEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_tx_event_subscriptions_mutex.handle);
for (LoraTxEventSubscription** link = &lora_tx_event_subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&lora_tx_event_subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
error_t lora_tx_event_emit(Device* device, LoraTxId id, enum LoraTransmissionState state) {
LoraTxEvent stamped_event { .timestamp = get_micros_since_boot(), .id = id, .state = state };
error_t result = ERROR_NOT_FOUND;
mutex_lock(&lora_tx_event_subscriptions_mutex.handle);
for (LoraTxEventSubscription* sub = lora_tx_event_subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.device != device) {
continue;
}
if (sub->internal.count >= LORA_TX_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->internal.head + sub->internal.count) % LORA_TX_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_tx_event_subscriptions_mutex.handle);
return result;
}
error_t lora_tx_event_poll(LoraTxEventSubscription* sub, LoraTxEvent* out_event) {
mutex_lock(&lora_tx_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_TX_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&lora_tx_event_subscriptions_mutex.handle);
return has_event ? ERROR_NONE : ERROR_TIMEOUT;
}
// endregion
error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state) { error_t lora_get_radio_state(struct Device* device, enum LoraRadioState* state) {
return LORA_API(device)->get_radio_state(device, state); return LORA_API(device)->get_radio_state(device, state);
} }

View File

@ -407,9 +407,15 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(lora_set_parameter), DEFINE_MODULE_SYMBOL(lora_set_parameter),
DEFINE_MODULE_SYMBOL(lora_get_parameter), DEFINE_MODULE_SYMBOL(lora_get_parameter),
DEFINE_MODULE_SYMBOL(lora_transmit), DEFINE_MODULE_SYMBOL(lora_transmit),
DEFINE_MODULE_SYMBOL(lora_event_subscribe), DEFINE_MODULE_SYMBOL(lora_state_event_subscribe),
DEFINE_MODULE_SYMBOL(lora_event_unsubscribe), DEFINE_MODULE_SYMBOL(lora_state_event_unsubscribe),
DEFINE_MODULE_SYMBOL(lora_event_poll), DEFINE_MODULE_SYMBOL(lora_state_event_poll),
DEFINE_MODULE_SYMBOL(lora_rx_event_subscribe),
DEFINE_MODULE_SYMBOL(lora_rx_event_unsubscribe),
DEFINE_MODULE_SYMBOL(lora_rx_event_poll),
DEFINE_MODULE_SYMBOL(lora_tx_event_subscribe),
DEFINE_MODULE_SYMBOL(lora_tx_event_unsubscribe),
DEFINE_MODULE_SYMBOL(lora_tx_event_poll),
DEFINE_MODULE_SYMBOL(LORA_TYPE), DEFINE_MODULE_SYMBOL(LORA_TYPE),
// drivers/usb_host_hid // drivers/usb_host_hid
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected), DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),