Implement ValueManager and ValueComposer (#183)

* Fix ValueManager implementation
* Implement ValueComposer
* Add constructor for ValueManager
* Add value-expanders.h to flipper_v2.h set
* Move COPY_COMPOSE body into a .c file
* Add test for ValueManager
* Add destructors for ValueMutex, ValueManager and ValueComposer
* Use destructors in tests
* Move composition logic into perform_compose()
* Add docs for perform_compose()
* Add test for ValueComposer
* Replace atomic_bool with bool as g++ compiler doesn't support C11 atomics
* Add Event type
* Add semaphore support to the local target
* Add test for Event
* Update input records and relevant examples
* Rename Event to AppEvent in the cc1101-workaround example
* Rename Event to AppEvent in the irda example
* Use Event in ValueComposer to wait for update request
* Add perform_compose_internal() function
* fix Event/AppEvent

Co-authored-by: aanper <mail@s3f.ru>
This commit is contained in:
Vadim Kaushan 2020-10-26 12:26:15 +03:00 committed by GitHub
parent 69d97afea8
commit bb68fca20b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 641 additions and 96 deletions

View File

@ -49,6 +49,8 @@ C_SOURCES += $(APP_DIR)/tests/minunit_test.c
C_SOURCES += $(APP_DIR)/tests/furi_valuemutex_test.c C_SOURCES += $(APP_DIR)/tests/furi_valuemutex_test.c
C_SOURCES += $(APP_DIR)/tests/furi_pubsub_test.c C_SOURCES += $(APP_DIR)/tests/furi_pubsub_test.c
C_SOURCES += $(APP_DIR)/tests/furi_memmgr_test.c C_SOURCES += $(APP_DIR)/tests/furi_memmgr_test.c
C_SOURCES += $(APP_DIR)/tests/furi_value_expanders_test.c
C_SOURCES += $(APP_DIR)/tests/furi_event_test.c
endif endif
APP_EXAMPLE_BLINK ?= 0 APP_EXAMPLE_BLINK ?= 0

View File

@ -1,6 +1,6 @@
#include "flipper.h" #include "flipper_v2.h"
static void event_cb(const void* value, size_t size, void* ctx) { static void event_cb(const void* value, void* ctx) {
xSemaphoreGive((SemaphoreHandle_t*)ctx); xSemaphoreGive((SemaphoreHandle_t*)ctx);
} }
@ -14,7 +14,9 @@ void backlight_control(void* p) {
SemaphoreHandle_t update = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor); SemaphoreHandle_t update = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor);
// open record // open record
furi_open_deprecated("input_events", false, false, event_cb, NULL, (void*)update); PubSub* event_record = furi_open("input_events");
assert(event_record != NULL);
subscribe_pubsub(event_record, event_cb, (void*)update);
// we ready to work // we ready to work
furiac_ready(); furiac_ready();

View File

@ -115,7 +115,7 @@ typedef struct {
InputEvent input; InputEvent input;
} value; } value;
EventType type; EventType type;
} Event; } AppEvent;
typedef enum { ModeRx, ModeTx } Mode; typedef enum { ModeRx, ModeTx } Mode;
@ -195,14 +195,14 @@ static void render_callback(CanvasApi* canvas, void* ctx) {
static void input_callback(InputEvent* input_event, void* ctx) { static void input_callback(InputEvent* input_event, void* ctx) {
osMessageQueueId_t event_queue = (QueueHandle_t)ctx; osMessageQueueId_t event_queue = (QueueHandle_t)ctx;
Event event; AppEvent event;
event.type = EventTypeKey; event.type = EventTypeKey;
event.value.input = *input_event; event.value.input = *input_event;
osMessageQueuePut(event_queue, &event, 0, 0); osMessageQueuePut(event_queue, &event, 0, 0);
} }
extern "C" void cc1101_workaround(void* p) { extern "C" void cc1101_workaround(void* p) {
osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(Event), NULL); osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(AppEvent), NULL);
assert(event_queue); assert(event_queue);
State _state; State _state;
@ -266,7 +266,7 @@ extern "C" void cc1101_workaround(void* p) {
const int16_t RSSI_THRESHOLD = -89; const int16_t RSSI_THRESHOLD = -89;
Event event; AppEvent event;
while(1) { while(1) {
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 150); osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 150);
State* state = (State*)acquire_mutex_block(&state_mutex); State* state = (State*)acquire_mutex_block(&state_mutex);

View File

@ -1,6 +1,6 @@
#include "u8g2/u8g2.h" #include "u8g2/u8g2.h"
#include "fatfs/ff.h" #include "fatfs/ff.h"
#include "flipper.h" #include "flipper_v2.h"
#include <stdio.h> #include <stdio.h>
// TODO currently we have small stack, so it will be static // TODO currently we have small stack, so it will be static
@ -26,7 +26,7 @@ typedef struct {
AppEventType type; AppEventType type;
} AppEvent; } AppEvent;
static void event_cb(const void* value, size_t size, void* ctx) { static void event_cb(const void* value, void* ctx) {
QueueHandle_t event_queue = (QueueHandle_t)ctx; QueueHandle_t event_queue = (QueueHandle_t)ctx;
AppEvent event; AppEvent event;
@ -56,9 +56,13 @@ void fatfs_list(void* p) {
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* event_record = PubSub* event_record = furi_open("input_events");
furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue);
if(event_record == NULL) { if(event_record == NULL) {
fuprintf(furi_log, "[widget][fatfs_list] cannot open input_events record\n");
furiac_exit(NULL);
}
PubSubItem* subscription = subscribe_pubsub(event_record, event_cb, event_queue);
if(subscription == NULL) {
fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n"); fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n");
furiac_exit(NULL); furiac_exit(NULL);
} }

View File

@ -1,13 +1,13 @@
#include "flipper.h" #include "flipper_v2.h"
#include <stdio.h> #include <stdio.h>
static void state_cb(const void* value, size_t size, void* ctx) { static void state_cb(const void* value, void* ctx) {
const InputState* state = value; const InputState* state = value;
printf("state: %02x\n", *state); printf("state: %02x\n", *state);
} }
static void event_cb(const void* value, size_t size, void* ctx) { static void event_cb(const void* value, void* ctx) {
const InputEvent* event = value; const InputEvent* event = value;
printf("event: %02x %s\n", event->input, event->state ? "pressed" : "released"); printf("event: %02x %s\n", event->input, event->state ? "pressed" : "released");
@ -15,10 +15,13 @@ static void event_cb(const void* value, size_t size, void* ctx) {
void application_input_dump(void* p) { void application_input_dump(void* p) {
// open record // open record
FuriRecordSubscriber* state_record = ValueManager* state_record = furi_open("input_state");
furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL); assert(state_record != NULL);
FuriRecordSubscriber* event_record = subscribe_pubsub(&state_record->pubsub, state_cb, NULL);
furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL);
PubSub* event_record = furi_open("input_events");
assert(event_record != NULL);
subscribe_pubsub(event_record, event_cb, NULL);
for(;;) { for(;;) {
delay(100); delay(100);

View File

@ -1,17 +1,17 @@
#include "gui_event.h" #include "gui_event.h"
#include <flipper.h> #include <flipper_v2.h>
#include <assert.h> #include <assert.h>
#define GUI_EVENT_MQUEUE_SIZE 8 #define GUI_EVENT_MQUEUE_SIZE 8
struct GuiEvent { struct GuiEvent {
FuriRecordSubscriber* input_event_record; PubSub* input_event_record;
osMessageQueueId_t mqueue; osMessageQueueId_t mqueue;
osMutexId_t lock_mutex; osMutexId_t lock_mutex;
}; };
void gui_event_input_events_callback(const void* value, size_t size, void* ctx) { void gui_event_input_events_callback(const void* value, void* ctx) {
assert(ctx); assert(ctx);
GuiEvent* gui_event = ctx; GuiEvent* gui_event = ctx;
@ -29,9 +29,10 @@ GuiEvent* gui_event_alloc() {
assert(gui_event->mqueue); assert(gui_event->mqueue);
// Input // Input
gui_event->input_event_record = furi_open_deprecated( gui_event->input_event_record = furi_open("input_events");
"input_events", false, false, gui_event_input_events_callback, NULL, gui_event);
assert(gui_event->input_event_record != NULL); assert(gui_event->input_event_record != NULL);
subscribe_pubsub(gui_event->input_event_record, gui_event_input_events_callback, gui_event);
// Lock mutex // Lock mutex
gui_event->lock_mutex = osMutexNew(NULL); gui_event->lock_mutex = osMutexNew(NULL);
assert(gui_event->lock_mutex); assert(gui_event->lock_mutex);

View File

@ -1,49 +1,47 @@
#include <input/input.h> #include <input/input.h>
#include <input_priv.h> #include <input_priv.h>
#include <stdio.h> #include <stdio.h>
#include <flipper.h> #include <flipper_v2.h>
#ifdef APP_NFC #ifdef APP_NFC
void st25r3916Isr(void); void st25r3916Isr(void);
#endif #endif
static volatile bool initialized = false; static volatile bool initialized = false;
static SemaphoreHandle_t event; static ValueManager input_state_record;
static PubSub input_events_record;
static Event event;
static InputState input_state = { static InputState input_state = {
false, false,
}; };
void input_task(void* p) { void input_task(void* p) {
uint32_t state_bits = 0; uint32_t state_bits = 0;
StaticSemaphore_t event_semaphore;
uint8_t debounce_counters[INPUT_COUNT]; uint8_t debounce_counters[INPUT_COUNT];
event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); if(!init_managed(&input_state_record, &input_state, sizeof(input_state))) {
printf("[input_task] cannot initialize ValueManager for input_state\n");
furiac_exit(NULL);
}
if(!init_pubsub(&input_events_record)) {
printf("[input_task] cannot initialize PubSub for input_events\n");
furiac_exit(NULL);
}
if(!init_event(&event)) {
printf("[input_task] cannot initialize Event\n");
furiac_exit(NULL);
}
if(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) { if(!furi_create("input_state", &input_state_record)) {
printf("[input_task] cannot create the input_state record\n"); printf("[input_task] cannot create the input_state record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* input_state_record = if(!furi_create("input_events", &input_events_record)) {
furi_open_deprecated("input_state", false, false, NULL, NULL, NULL);
if(input_state_record == NULL) {
printf("[input_task] cannot open the input_state record\n");
furiac_exit(NULL);
}
if(!furi_create_deprecated("input_events", NULL, 0)) {
printf("[input_task] cannot create the input_events record\n"); printf("[input_task] cannot create the input_events record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* input_events_record =
furi_open_deprecated("input_events", false, false, NULL, NULL, NULL);
if(input_events_record == NULL) {
printf("[input_task] cannot open the input_events record\n");
furiac_exit(NULL);
}
// we ready to work // we ready to work
furiac_ready(); furiac_ready();
initialized = true; initialized = true;
@ -82,7 +80,7 @@ void input_task(void* p) {
if(changed_bits != 0) { if(changed_bits != 0) {
// printf("[input] %02x -> %02x\n", state_bits, new_state_bits); // printf("[input] %02x -> %02x\n", state_bits, new_state_bits);
InputState new_state = _BITS2STATE(new_state_bits); InputState new_state = _BITS2STATE(new_state_bits);
furi_write(input_state_record, &new_state, sizeof(new_state)); write_managed(&input_state_record, &new_state, sizeof(new_state), osWaitForever);
state_bits = new_state_bits; state_bits = new_state_bits;
@ -90,13 +88,13 @@ void input_task(void* p) {
if((changed_bits & (1 << i)) != 0) { if((changed_bits & (1 << i)) != 0) {
bool state = (new_state_bits & (1 << i)) != 0; bool state = (new_state_bits & (1 << i)) != 0;
InputEvent event = {i, state}; InputEvent event = {i, state};
furi_write(input_events_record, &event, sizeof(event)); notify_pubsub(&input_events_record, &event);
} }
} }
} }
// Sleep: wait for event // Sleep: wait for event
xSemaphoreTake(event, portMAX_DELAY); wait_event(&event);
} else { } else {
osDelay(1); osDelay(1);
} }
@ -113,12 +111,5 @@ void HAL_GPIO_EXTI_Callback(uint16_t pin) {
if(!initialized) return; if(!initialized) return;
BaseType_t task_woken = pdFALSE; signal_event(&event);
// Ignore the result, as we do not care about repeated event during event processing.
xSemaphoreGiveFromISR(event, &task_woken);
if(task_woken) {
portYIELD_FROM_ISR(task_woken);
}
} }

View File

@ -14,7 +14,7 @@ typedef struct {
InputEvent input; InputEvent input;
} value; } value;
EventType type; EventType type;
} Event; } AppEvent;
typedef struct { typedef struct {
uint8_t mode_id; uint8_t mode_id;
@ -24,15 +24,15 @@ typedef struct {
uint8_t samsung_packet_id; uint8_t samsung_packet_id;
} State; } State;
typedef void (*ModeInput)(Event*, State*); typedef void (*ModeInput)(AppEvent*, State*);
typedef void (*ModeRender)(CanvasApi*, State*); typedef void (*ModeRender)(CanvasApi*, State*);
void input_carrier(Event* event, State* state); void input_carrier(AppEvent* event, State* state);
void render_carrier(CanvasApi* canvas, State* state); void render_carrier(CanvasApi* canvas, State* state);
void input_nec(Event* event, State* state); void input_nec(AppEvent* event, State* state);
void render_nec(CanvasApi* canvas, State* state); void render_nec(CanvasApi* canvas, State* state);
void render_carrier(CanvasApi* canvas, State* state); void render_carrier(CanvasApi* canvas, State* state);
void input_samsung(Event* event, State* state); void input_samsung(AppEvent* event, State* state);
void render_samsung(CanvasApi* canvas, State* state); void render_samsung(CanvasApi* canvas, State* state);
typedef struct { typedef struct {
@ -120,7 +120,7 @@ void render_samsung(CanvasApi* canvas, State* state) {
} }
} }
void input_carrier(Event* event, State* state) { void input_carrier(AppEvent* event, State* state) {
if(event->value.input.input == InputOk) { if(event->value.input.input == InputOk) {
if(event->value.input.state) { if(event->value.input.state) {
hal_pwm_set( hal_pwm_set(
@ -151,7 +151,7 @@ void input_carrier(Event* event, State* state) {
} }
} }
void input_nec(Event* event, State* state) { void input_nec(AppEvent* event, State* state) {
uint8_t packets_count = sizeof(nec_packets) / sizeof(nec_packets[0]); uint8_t packets_count = sizeof(nec_packets) / sizeof(nec_packets[0]);
if(event->value.input.input == InputOk) { if(event->value.input.input == InputOk) {
@ -180,7 +180,7 @@ void input_nec(Event* event, State* state) {
} }
} }
void input_samsung(Event* event, State* state) { void input_samsung(AppEvent* event, State* state) {
uint8_t packets_count = sizeof(samsung_packets) / sizeof(samsung_packets[0]); uint8_t packets_count = sizeof(samsung_packets) / sizeof(samsung_packets[0]);
if(event->value.input.input == InputOk) { if(event->value.input.input == InputOk) {
@ -226,14 +226,14 @@ static void render_callback(CanvasApi* canvas, void* ctx) {
static void input_callback(InputEvent* input_event, void* ctx) { static void input_callback(InputEvent* input_event, void* ctx) {
osMessageQueueId_t event_queue = (QueueHandle_t)ctx; osMessageQueueId_t event_queue = (QueueHandle_t)ctx;
Event event; AppEvent event;
event.type = EventTypeKey; event.type = EventTypeKey;
event.value.input = *input_event; event.value.input = *input_event;
osMessageQueuePut(event_queue, &event, 0, 0); osMessageQueuePut(event_queue, &event, 0, 0);
} }
void irda(void* p) { void irda(void* p) {
osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(Event), NULL); osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(AppEvent), NULL);
State _state; State _state;
uint8_t mode_count = sizeof(modes) / sizeof(modes[0]); uint8_t mode_count = sizeof(modes) / sizeof(modes[0]);
@ -264,7 +264,7 @@ void irda(void* p) {
} }
gui->add_widget(gui, widget, WidgetLayerFullscreen); gui->add_widget(gui, widget, WidgetLayerFullscreen);
Event event; AppEvent event;
while(1) { while(1) {
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
State* state = (State*)acquire_mutex_block(&state_mutex); State* state = (State*)acquire_mutex_block(&state_mutex);

View File

@ -0,0 +1,30 @@
#include "flipper_v2.h"
#include "minunit.h"
static void furi_concurent_app(void* p) {
Event* event = p;
signal_event(event);
furiac_exit(NULL);
}
void test_furi_event() {
Event event;
mu_check(init_event(&event));
// The event should not be signalled right after creation
mu_check(!wait_event_with_timeout(&event, 100));
// Create second app
FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", (void*)&event);
// The event should be signalled now
mu_check(wait_event_with_timeout(&event, 100));
// The event should not be signalled once it's processed
mu_check(!wait_event_with_timeout(&event, 100));
mu_check(delete_event(&event));
}

View File

@ -0,0 +1,145 @@
#include "flipper_v2.h"
#include "minunit.h"
#include <stdint.h>
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} Rgb;
static uint32_t rgb_final_state;
static void rgb_clear(void* ctx, void* state) {
Rgb* rgb = state;
rgb->red = 0;
rgb->green = 0;
rgb->blue = 0;
}
static void rgb_commit(void* ctx, void* state) {
Rgb* rgb = state;
rgb_final_state = ((uint32_t)rgb->red) | (((uint32_t)rgb->green) << 8) |
(((uint32_t)rgb->blue) << 16);
}
static void set_red_composer(void* ctx, void* state) {
Rgb* rgb = state;
uint8_t* red = ctx;
rgb->red = *red;
}
void test_furi_value_composer() {
Rgb rgb = {0, 0, 0};
ValueComposer composer;
Rgb layer1_rgb = {0, 0, 0};
ValueMutex layer1_mutex;
uint8_t layer2_red = 0;
rgb_final_state = 0xdeadbeef;
mu_check(init_composer(&composer, &rgb));
mu_check(init_mutex(&layer1_mutex, &layer1_rgb, sizeof(layer1_rgb)));
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0xdeadbeef, rgb_final_state);
ValueComposerHandle* layer1_handle =
add_compose_layer(&composer, COPY_COMPOSE, &layer1_mutex, UiLayerNotify);
mu_assert_pointers_not_eq(layer1_handle, NULL);
// RGB state should be updated with the layer1 state
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x000000, rgb_final_state);
layer2_red = 0xcc;
ValueComposerHandle* layer2_handle =
add_compose_layer(&composer, set_red_composer, &layer2_red, UiLayerAboveNotify);
mu_assert_pointers_not_eq(layer2_handle, NULL);
// RGB state should be updated with the layer1 and layer2 state, in order
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x0000cc, rgb_final_state);
// Change layer1 state
Rgb* state = acquire_mutex(&layer1_mutex, 0);
mu_assert_pointers_not_eq(state, NULL);
state->red = 0x12;
state->green = 0x34;
state->blue = 0x56;
release_mutex(&layer1_mutex, state);
// Nothing should happen, we need to trigger composition request first
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x0000cc, rgb_final_state);
request_compose(layer1_handle);
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x5634cc, rgb_final_state);
// Change layer2 state
layer2_red = 0xff;
// Nothing should happen, we need to trigger composition request first
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x5634cc, rgb_final_state);
request_compose(layer2_handle);
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x5634ff, rgb_final_state);
// Remove layer1
mu_check(remove_compose_layer(layer1_handle));
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x0000ff, rgb_final_state);
// Remove layer2
mu_check(remove_compose_layer(layer2_handle));
perform_compose(&composer, rgb_clear, rgb_commit, NULL);
mu_assert_int_eq(0x000000, rgb_final_state);
mu_check(delete_composer(&composer));
}
static const uint32_t notify_value_0 = 0x12345678;
static const uint32_t notify_value_1 = 0x11223344;
static uint32_t pubsub_value = 0;
void test_value_manager_handler(void* arg, void* ctx) {
pubsub_value = *(uint32_t*)arg;
}
void test_furi_value_manager() {
uint32_t value = 0;
ValueManager managed;
mu_check(init_managed(&managed, &value, sizeof(value)));
pubsub_value = 0;
PubSubItem* test_pubsub_item;
test_pubsub_item = subscribe_pubsub(&managed.pubsub, test_value_manager_handler, 0);
mu_assert_pointers_not_eq(test_pubsub_item, NULL);
mu_check(write_managed(&managed, (void*)&notify_value_0, sizeof(notify_value_0), 100));
mu_assert_int_eq(pubsub_value, notify_value_0);
uint32_t* ptr = acquire_mutex(&managed.value, 100);
mu_assert_pointers_not_eq(ptr, NULL);
*ptr = notify_value_1;
mu_check(commit_managed(&managed, ptr));
mu_assert_int_eq(pubsub_value, notify_value_1);
mu_check(delete_managed(&managed));
}

View File

@ -37,6 +37,8 @@ void test_furi_valuemutex() {
//acquire mutex blocking case //acquire mutex blocking case
//write mutex blocking case //write mutex blocking case
//read mutex blocking case //read mutex blocking case
mu_check(delete_mutex(&valuemutex));
} }
/* /*
@ -119,4 +121,6 @@ void test_furi_concurrent_access() {
mu_assert_pointers_eq(second_app->handler, NULL); mu_assert_pointers_eq(second_app->handler, NULL);
mu_assert_int_eq(value.a, value.b); mu_assert_int_eq(value.a, value.b);
mu_check(delete_mutex(&mutex));
} }

View File

@ -12,6 +12,9 @@ void test_furi_create_open();
void test_furi_valuemutex(); void test_furi_valuemutex();
void test_furi_concurrent_access(); void test_furi_concurrent_access();
void test_furi_pubsub(); void test_furi_pubsub();
void test_furi_value_composer();
void test_furi_value_manager();
void test_furi_event();
void test_furi_memmgr(); void test_furi_memmgr();
@ -60,6 +63,15 @@ MU_TEST(mu_test_furi_memmgr) {
test_furi_memmgr(); test_furi_memmgr();
} }
MU_TEST(mu_test_furi_value_expanders) {
test_furi_value_composer();
test_furi_value_manager();
}
MU_TEST(mu_test_furi_event) {
test_furi_event();
}
MU_TEST_SUITE(test_suite) { MU_TEST_SUITE(test_suite) {
MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
@ -72,6 +84,8 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_valuemutex); MU_RUN_TEST(mu_test_furi_valuemutex);
MU_RUN_TEST(mu_test_furi_concurrent_access); MU_RUN_TEST(mu_test_furi_concurrent_access);
MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_pubsub);
MU_RUN_TEST(mu_test_furi_value_expanders);
MU_RUN_TEST(mu_test_furi_event);
MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_memmgr);
} }

24
core/api-basic/event.c Normal file
View File

@ -0,0 +1,24 @@
#include "event.h"
#include <string.h>
bool init_event(Event* event) {
event->semaphore_id = osSemaphoreNew(1, 0, NULL);
return event->semaphore_id != NULL;
}
bool delete_event(Event* event) {
return osSemaphoreDelete(event->semaphore_id) == osOK;
}
void signal_event(Event* event) {
// Ignore the result, as we do not care about repeated event signalling.
osSemaphoreRelease(event->semaphore_id);
}
void wait_event(Event* event) {
wait_event_with_timeout(event, osWaitForever);
}
bool wait_event_with_timeout(Event* event, uint32_t timeout_ms) {
return osSemaphoreAcquire(event->semaphore_id, timeout_ms) == osOK;
}

36
core/api-basic/event.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "cmsis_os.h"
typedef struct {
osSemaphoreId_t semaphore_id;
} Event;
/*
Creates Event.
*/
bool init_event(Event* event);
/*
Free resources allocated by `init_event`.
This function doesn't free the memory occupied by `Event` itself.
*/
bool delete_event(Event* event);
/*
Signals the event.
If the event is already in "signalled" state, nothing happens.
*/
void signal_event(Event* event);
/*
Waits until the event is signalled.
*/
void wait_event(Event* event);
/*
Waits with a timeout until the event is signalled.
*/
bool wait_event_with_timeout(Event* event, uint32_t timeout_ms);

View File

@ -1,4 +1,5 @@
#include "pubsub.h" #include "pubsub.h"
#include "flipper_v2.h"
bool init_pubsub(PubSub* pubsub) { bool init_pubsub(PubSub* pubsub) {
// mutex without name, // mutex without name,

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "flipper_v2.h" #include "cmsis_os.h"
#include "m-list.h" #include "m-list.h"
/* /*

View File

@ -0,0 +1,177 @@
#include "value-expanders.h"
bool init_composer(ValueComposer* composer, void* value) {
if(!init_mutex(&composer->value, value, 0)) return false;
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
list_composer_cb_init(composer->layers[i]);
}
// mutex without name,
// no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
// with dynamic memory allocation
const osMutexAttr_t value_mutex_attr = {
.name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
composer->mutex = osMutexNew(&value_mutex_attr);
if(composer->mutex == NULL) return false;
if(!init_event(&composer->request)) return false;
return true;
}
bool delete_composer(ValueComposer* composer) {
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
bool result = true;
result &= delete_mutex(&composer->value);
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
list_composer_cb_clear(composer->layers[i]);
}
result &= osMutexDelete(composer->mutex) == osOK;
return result;
} else {
return false;
}
}
ValueComposerHandle*
add_compose_layer(ValueComposer* composer, ValueComposerCallback cb, void* ctx, UiLayer layer) {
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
// put uninitialized item to the list
ValueComposerHandle* handle = list_composer_cb_push_raw(composer->layers[layer]);
handle->cb = cb;
handle->ctx = ctx;
handle->layer = layer;
handle->composer = composer;
// TODO unregister handle on app exit
//flapp_on_exit(remove_compose_layer, handle);
osMutexRelease(composer->mutex);
// Layers changed, request composition
signal_event(&composer->request);
return handle;
} else {
return NULL;
}
}
bool remove_compose_layer(ValueComposerHandle* handle) {
ValueComposer* composer = handle->composer;
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
bool result = false;
// iterate over items
list_composer_cb_it_t it;
for(list_composer_cb_it(it, composer->layers[handle->layer]); !list_composer_cb_end_p(it);
list_composer_cb_next(it)) {
const ValueComposerHandle* item = list_composer_cb_cref(it);
// if the iterator is equal to our element
if(item == handle) {
list_composer_cb_remove(composer->layers[handle->layer], it);
result = true;
break;
}
}
osMutexRelease(composer->mutex);
// Layers changed, request composition
signal_event(&composer->request);
return result;
} else {
return false;
}
}
void request_compose(ValueComposerHandle* handle) {
ValueComposer* composer = handle->composer;
signal_event(&composer->request);
}
void perform_compose(
ValueComposer* composer,
ValueComposerCallback start_cb,
ValueComposerCallback end_cb,
void* ctx) {
if(!wait_event_with_timeout(&composer->request, 0)) return;
void* state = acquire_mutex(&composer->value, 0);
if(state == NULL) return;
if(start_cb != NULL) start_cb(ctx, state);
perform_compose_internal(composer, state);
if(end_cb != NULL) end_cb(ctx, state);
release_mutex(&composer->value, state);
}
void perform_compose_internal(ValueComposer* composer, void* state) {
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
// Compose all levels for now
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
// iterate over items
list_composer_cb_it_t it;
for(list_composer_cb_it(it, composer->layers[i]); !list_composer_cb_end_p(it);
list_composer_cb_next(it)) {
const ValueComposerHandle* h = list_composer_cb_cref(it);
h->cb(h->ctx, state);
}
}
osMutexRelease(composer->mutex);
}
}
void COPY_COMPOSE(void* ctx, void* state) {
read_mutex((ValueMutex*)ctx, state, 0, osWaitForever);
}
bool init_managed(ValueManager* managed, void* value, size_t size) {
if(!init_pubsub(&managed->pubsub)) return false;
if(!init_mutex(&managed->value, value, size)) {
delete_pubsub(&managed->pubsub);
return false;
}
return true;
}
bool delete_managed(ValueManager* managed) {
bool result = true;
result &= delete_mutex(&managed->value);
result &= delete_pubsub(&managed->pubsub);
return result;
}
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(&managed->value, timeout);
if(value == NULL) return false;
memcpy(value, data, len);
notify_pubsub(&managed->pubsub, value);
if(!release_mutex(&managed->value, value)) return false;
return true;
}
bool commit_managed(ValueManager* managed, void* value) {
if(value != managed->value.value) return false;
notify_pubsub(&managed->pubsub, value);
if(!release_mutex(&managed->value, value)) return false;
return true;
}

View File

@ -1,24 +0,0 @@
#include "value-expanders.h"
bool commit_managed(ValueManager* managed, void* value) {
if(value != managed->mutex->value) return false;
notify_pubsub(&managed->pubsub, value);
if(!osMutexGive(managed->mutex)) return false;
return true;
}
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(managed->mutex, timeout);
if(value == NULL) return false;
memcpy(value, data, len):
notify_pubsub(&managed->pubsub, value);
if(!release_mutex(managed->mutex, value)) return false;
return true;
}

View File

@ -1,26 +1,76 @@
#pragma once #pragma once
#include "flipper.h" #include "flipper.h"
#include "valuemutex.h"
#include "pubsub.h"
#include "event.h"
#include "m-list.h"
/* /*
== Value composer == == Value composer ==
*/ */
typedef void(ValueComposerCallback)(void* ctx, void* state); typedef struct ValueComposer ValueComposer;
void COPY_COMPOSE(void* ctx, void* state) { typedef void (*ValueComposerCallback)(void* ctx, void* state);
read_mutex((ValueMutex*)ctx, state, 0);
}
typedef enum { UiLayerBelowNotify UiLayerNotify, UiLayerAboveNotify } UiLayer; typedef enum { UiLayerBelowNotify, UiLayerNotify, UiLayerAboveNotify } UiLayer;
typedef struct {
ValueComposerCallback cb;
void* ctx;
UiLayer layer;
ValueComposer* composer;
} ValueComposerHandle;
LIST_DEF(list_composer_cb, ValueComposerHandle, M_POD_OPLIST);
struct ValueComposer {
ValueMutex value;
list_composer_cb_t layers[3];
osMutexId_t mutex;
Event request;
};
void COPY_COMPOSE(void* ctx, void* state);
bool init_composer(ValueComposer* composer, void* value);
/*
Free resources allocated by `init_composer`.
This function doesn't free the memory occupied by `ValueComposer` itself.
*/
bool delete_composer(ValueComposer* composer);
ValueComposerHandle* ValueComposerHandle*
add_compose_layer(ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer); add_compose_layer(ValueComposer* composer, ValueComposerCallback cb, void* ctx, UiLayer layer);
bool remove_compose_layer(ValueComposerHandle* handle); bool remove_compose_layer(ValueComposerHandle* handle);
void request_compose(ValueComposerHandle* handle); void request_compose(ValueComposerHandle* handle);
/*
Perform composition if requested.
`start_cb` and `end_cb` will be called before and after all layer callbacks, respectively.
Both `start_cb` and `end_cb` can be NULL. They can be used to set initial state (e.g. clear screen)
and commit the final state.
*/
void perform_compose(
ValueComposer* composer,
ValueComposerCallback start_cb,
ValueComposerCallback end_cb,
void* ctx);
/*
Perform composition.
This function should be called with value mutex acquired.
This function is here for convenience, so that developers can write their own compose loops.
See `perform_compose` function body for an example.
*/
void perform_compose_internal(ValueComposer* composer, void* state);
// See [LED](LED-API) or [Display](Display-API) API for examples. // See [LED](LED-API) or [Display](Display-API) API for examples.
/* /*
@ -39,6 +89,14 @@ typedef struct {
PubSub pubsub; PubSub pubsub;
} ValueManager; } ValueManager;
bool init_managed(ValueManager* managed, void* value, size_t size);
/*
Free resources allocated by `init_managed`.
This function doesn't free the memory occupied by `ValueManager` itself.
*/
bool delete_managed(ValueManager* managed);
/* /*
acquire value, changes it and send notify with current value. acquire value, changes it and send notify with current value.
*/ */

View File

@ -17,6 +17,14 @@ bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
return true; return true;
} }
bool delete_mutex(ValueMutex* valuemutex) {
if(osMutexAcquire(valuemutex->mutex, osWaitForever) == osOK) {
return osMutexDelete(valuemutex->mutex) == osOK;
} else {
return false;
}
}
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) { if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) {
return valuemutex->value; return valuemutex->value;

View File

@ -21,6 +21,12 @@ Creates ValueMutex.
*/ */
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size); bool init_mutex(ValueMutex* valuemutex, void* value, size_t size);
/*
Free resources allocated by `init_mutex`.
This function doesn't free the memory occupied by `ValueMutex` itself.
*/
bool delete_mutex(ValueMutex* valuemutex);
/* /*
Call for work with data stored in mutex. Call for work with data stored in mutex.
Returns pointer to data if success, NULL otherwise. Returns pointer to data if success, NULL otherwise.

View File

@ -11,6 +11,8 @@ extern "C" {
#include "cmsis_os2.h" #include "cmsis_os2.h"
#include "api-basic/valuemutex.h" #include "api-basic/valuemutex.h"
#include "api-basic/pubsub.h" #include "api-basic/pubsub.h"
#include "api-basic/value-expanders.h"
#include "api-basic/event.h"
#include "api-basic/memmgr.h" #include "api-basic/memmgr.h"

View File

@ -97,3 +97,12 @@ osStatus_t osMutexRelease (osMutexId_t mutex_id);
osStatus_t osMutexDelete (osMutexId_t mutex_id); osStatus_t osMutexDelete (osMutexId_t mutex_id);
#define osWaitForever portMAX_DELAY #define osWaitForever portMAX_DELAY
typedef StaticSemaphore_t osSemaphoreDef_t;
typedef SemaphoreHandle_t osSemaphoreId_t;
typedef struct {} osSemaphoreAttr_t;
osSemaphoreId_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr);
osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout);
osStatus_t osSemaphoreRelease(osSemaphoreId_t semaphore_id);
osStatus_t osSemaphoreDelete(osSemaphoreId_t semaphore_id);

View File

@ -265,3 +265,55 @@ osStatus_t osMutexDelete (osMutexId_t mutex_id) {
return osError; return osError;
} }
} }
osSemaphoreId_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr) {
if(max_count != 1) {
// Non-binary semaphors are not supported at the moment
return osErrorParameter;
}
if(attr != NULL) {
// Attributes are not supported at the moment
return osErrorParameter;
}
SemaphoreHandle_t handle = osMutexNew(NULL);
if(handle == NULL) return NULL;
if(initial_count == 0) {
xSemaphoreTake(handle, 0);
}
return handle;
}
osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout) {
if(semaphore_id == NULL) {
return osErrorParameter;
}
if(xSemaphoreTake(semaphore_id, timeout) == pdTRUE) {
return osOK;
} else {
return osErrorTimeout;
}
}
osStatus_t osSemaphoreRelease(osSemaphoreId_t semaphore_id) {
if(semaphore_id == NULL) {
return osErrorParameter;
}
if(xSemaphoreGive(semaphore_id) == pdTRUE) {
return osOK;
} else {
return osErrorTimeout;
}
}
osStatus_t osSemaphoreDelete(osSemaphoreId_t semaphore_id) {
if(semaphore_id == NULL) {
return osErrorParameter;
}
return osMutexDelete(semaphore_id);
}