From 5fed7c32d9bb3cbaad1da415bb89cf274c405eeb Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 28 Aug 2026 08:54:55 +0200 Subject: [PATCH] Improve API, fix issue --- .../private/drivers/sx1262_radio.cpp | 19 +- .../include/tactility/drivers/lora.h | 255 ++++++++++++---- TactilityKernel/source/drivers/lora.cpp | 279 +++++++++++++++--- TactilityKernel/source/symbols.c | 12 +- 4 files changed, 453 insertions(+), 112 deletions(-) diff --git a/Drivers/sx126x-module/private/drivers/sx1262_radio.cpp b/Drivers/sx126x-module/private/drivers/sx1262_radio.cpp index 24563c5b6..ef31ee1ac 100644 --- a/Drivers/sx126x-module/private/drivers/sx1262_radio.cpp +++ b/Drivers/sx126x-module/private/drivers/sx1262_radio.cpp @@ -8,8 +8,6 @@ #include #include -#include -#include #include #include @@ -348,9 +346,7 @@ void Sx1262Radio::setState(enum LoraRadioState newState) { state = newState; unlock(); - LoraEvent event { .type = LORA_EVENT_STATE, .timestamp = 0, .data = {} }; - event.data.state = { .state = newState }; - lora_event_emit(settings.device, &event); + lora_state_event_emit(settings.device, newState); } 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) { - 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); + lora_rx_event_emit(settings.device, data, length, rssi, snr); } void Sx1262Radio::publishTx(LoraTxId id, enum LoraTransmissionState txState) { - LoraEvent event { .type = LORA_EVENT_TX, .timestamp = 0, .data = {} }; - event.data.tx = { .id = id, .state = txState }; - lora_event_emit(settings.device, &event); + lora_tx_event_emit(settings.device, id, txState); } // endregion diff --git a/TactilityKernel/include/tactility/drivers/lora.h b/TactilityKernel/include/tactility/drivers/lora.h index 4a6a31fa3..b28f6acff 100644 --- a/TactilityKernel/include/tactility/drivers/lora.h +++ b/TactilityKernel/include/tactility/drivers/lora.h @@ -182,20 +182,97 @@ struct LoraApi { 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(). */ -enum LoraEventType { - LORA_EVENT_STATE, // struct LoraStateEventData - LORA_EVENT_RX, // struct LoraRxEventData - LORA_EVENT_TX, // struct LoraTxEventData -}; +// region State events -/** Data for LORA_EVENT_STATE. */ -struct LoraStateEventData { +struct LoraStateEvent { + /** Stamped by lora_state_event_emit(); any value passed in by the caller is ignored. */ + uint64_t timestamp; 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]; size_t length; /** Received signal strength in dBm */ @@ -204,104 +281,178 @@ struct LoraRxEventData { 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 * 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 - * 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. + * Caller-owned subscription node, registered with lora_rx_event_subscribe() and drained with + * lora_rx_event_poll(). Events queue by value (FIFO): a poll subscription that falls behind + * 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. */ -struct LoraEventSubscription { - /** Set by lora_event_subscribe(). Read-only for the caller: OR it into a +struct LoraRxEventSubscription { + /** 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 * `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(). */ + /** The lora device this subscription receives events for; set by lora_rx_event_subscribe(). */ 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 LoraEvent queue[LORA_EVENT_QUEUE_CAPACITY]; + struct LoraRxEvent queue[LORA_RX_EVENT_QUEUE_CAPACITY]; uint8_t head; uint8_t count; - struct LoraEventSubscription* next; + struct LoraRxEventSubscription* next; } 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. * @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 + * 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, - * 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 * @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); +error_t lora_rx_event_subscribe(struct LoraRxEventSubscription* 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); +error_t lora_rx_event_unsubscribe(struct LoraRxEventSubscription* 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. + * task_event_group_wait_any() on @a sub's event group first (see lora_rx_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); +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. * @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 */ -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; -/** @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_set_enabled(struct Device* device, bool enabled); error_t lora_set_modulation(struct Device* device, enum LoraModulation modulation); diff --git a/TactilityKernel/source/drivers/lora.cpp b/TactilityKernel/source/drivers/lora.cpp index f9593b439..13327cc42 100644 --- a/TactilityKernel/source/drivers/lora.cpp +++ b/TactilityKernel/source/drivers/lora.cpp @@ -5,15 +5,10 @@ #include #include -#define LORA_API(device) ((const struct LoraApi*)device_get_driver(device)->api) +#include +#include -/** - * 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; +#define LORA_API(device) ((const struct LoraApi*)device_get_driver(device)->api) struct LoraEventMutex { Mutex handle {}; @@ -21,24 +16,39 @@ struct LoraEventMutex { ~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" { -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; 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); + mutex_lock(&lora_state_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; + for (LoraStateEventSubscription* existing = lora_state_event_subscriptions; existing != nullptr; existing = existing->internal.next) { + if (existing == sub) { + mutex_unlock(&lora_state_event_subscriptions_mutex.handle); + task_event_group_release_bit(event_group, bit); + return ERROR_INVALID_STATE; + } } 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.head = 0; sub->internal.count = 0; - sub->internal.next = lora_event_subscriptions; - lora_event_subscriptions = sub; - mutex_unlock(&lora_event_subscriptions_mutex.handle); + sub->internal.next = lora_state_event_subscriptions; + lora_state_event_subscriptions = sub; + mutex_unlock(&lora_state_event_subscriptions_mutex.handle); return ERROR_NONE; } -error_t lora_event_unsubscribe(LoraEventSubscription* sub) { +error_t lora_state_event_unsubscribe(LoraStateEventSubscription* 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) { + mutex_lock(&lora_state_event_subscriptions_mutex.handle); + for (LoraStateEventSubscription** link = &lora_state_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); + mutex_unlock(&lora_state_event_subscriptions_mutex.handle); if (result == ERROR_NONE) { task_event_group_release_bit(sub->internal.event_group, sub->bit); @@ -73,24 +83,23 @@ error_t lora_event_unsubscribe(LoraEventSubscription* sub) { 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 lora_state_event_emit(Device* device, enum LoraRadioState state) { + LoraStateEvent stamped_event { .timestamp = get_micros_since_boot(), .state = state }; 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) { + mutex_lock(&lora_state_event_subscriptions_mutex.handle); + for (LoraStateEventSubscription* sub = lora_state_event_subscriptions; sub != nullptr; sub = sub->internal.next) { if (sub->internal.device != device) { continue; } - if (sub->internal.count >= LORA_EVENT_QUEUE_CAPACITY) { + if (sub->internal.count >= LORA_STATE_EVENT_QUEUE_CAPACITY) { result = ERROR_RESOURCE; 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.count++; 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); } - mutex_unlock(&lora_event_subscriptions_mutex.handle); + mutex_unlock(&lora_state_event_subscriptions_mutex.handle); return result; } -error_t lora_event_poll(LoraEventSubscription* sub, LoraEvent* out_event) { - mutex_lock(&lora_event_subscriptions_mutex.handle); +error_t lora_state_event_poll(LoraStateEventSubscription* sub, LoraStateEvent* out_event) { + mutex_lock(&lora_state_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.head = (sub->internal.head + 1) % LORA_STATE_EVENT_QUEUE_CAPACITY; 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; } -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 { - *static_cast(ctx) = dev; - return false; - }); - return found; +// endregion + +// region RX events + +error_t lora_rx_event_subscribe(LoraRxEventSubscription* 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_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) { return LORA_API(device)->get_radio_state(device, state); } diff --git a/TactilityKernel/source/symbols.c b/TactilityKernel/source/symbols.c index b613aced1..a45030131 100644 --- a/TactilityKernel/source/symbols.c +++ b/TactilityKernel/source/symbols.c @@ -407,9 +407,15 @@ 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_event_subscribe), - DEFINE_MODULE_SYMBOL(lora_event_unsubscribe), - DEFINE_MODULE_SYMBOL(lora_event_poll), + DEFINE_MODULE_SYMBOL(lora_state_event_subscribe), + DEFINE_MODULE_SYMBOL(lora_state_event_unsubscribe), + 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), // drivers/usb_host_hid DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),