diff --git a/applications/applications.mk b/applications/applications.mk index f527dd89..2dd264cd 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -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_pubsub_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 APP_EXAMPLE_BLINK ?= 0 diff --git a/applications/backlight-control/backlight-control.c b/applications/backlight-control/backlight-control.c index ef22f3df..014f2f13 100644 --- a/applications/backlight-control/backlight-control.c +++ b/applications/backlight-control/backlight-control.c @@ -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); } @@ -14,7 +14,9 @@ void backlight_control(void* p) { SemaphoreHandle_t update = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor); // 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 furiac_ready(); diff --git a/applications/cc1101-workaround/cc1101-workaround.cpp b/applications/cc1101-workaround/cc1101-workaround.cpp index ecac480a..34fb4814 100644 --- a/applications/cc1101-workaround/cc1101-workaround.cpp +++ b/applications/cc1101-workaround/cc1101-workaround.cpp @@ -115,7 +115,7 @@ typedef struct { InputEvent input; } value; EventType type; -} Event; +} AppEvent; 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) { osMessageQueueId_t event_queue = (QueueHandle_t)ctx; - Event event; + AppEvent event; event.type = EventTypeKey; event.value.input = *input_event; osMessageQueuePut(event_queue, &event, 0, 0); } 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); State _state; @@ -266,7 +266,7 @@ extern "C" void cc1101_workaround(void* p) { const int16_t RSSI_THRESHOLD = -89; - Event event; + AppEvent event; while(1) { osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 150); State* state = (State*)acquire_mutex_block(&state_mutex); diff --git a/applications/examples/fatfs_list.c b/applications/examples/fatfs_list.c index 58e6730c..de24124c 100644 --- a/applications/examples/fatfs_list.c +++ b/applications/examples/fatfs_list.c @@ -1,6 +1,6 @@ #include "u8g2/u8g2.h" #include "fatfs/ff.h" -#include "flipper.h" +#include "flipper_v2.h" #include // TODO currently we have small stack, so it will be static @@ -26,7 +26,7 @@ typedef struct { AppEventType type; } 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; AppEvent event; @@ -56,9 +56,13 @@ void fatfs_list(void* p) { furiac_exit(NULL); } - FuriRecordSubscriber* event_record = - furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue); + PubSub* event_record = furi_open("input_events"); 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"); furiac_exit(NULL); } diff --git a/applications/examples/input_dump.c b/applications/examples/input_dump.c index 9f8d3f67..1ab70c32 100644 --- a/applications/examples/input_dump.c +++ b/applications/examples/input_dump.c @@ -1,13 +1,13 @@ -#include "flipper.h" +#include "flipper_v2.h" #include -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; 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; 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) { // open record - FuriRecordSubscriber* state_record = - furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL); - FuriRecordSubscriber* event_record = - furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL); + ValueManager* state_record = furi_open("input_state"); + assert(state_record != NULL); + subscribe_pubsub(&state_record->pubsub, state_cb, NULL); + + PubSub* event_record = furi_open("input_events"); + assert(event_record != NULL); + subscribe_pubsub(event_record, event_cb, NULL); for(;;) { delay(100); diff --git a/applications/gui/gui_event.c b/applications/gui/gui_event.c index 7c727206..b9dfffc9 100644 --- a/applications/gui/gui_event.c +++ b/applications/gui/gui_event.c @@ -1,17 +1,17 @@ #include "gui_event.h" -#include +#include #include #define GUI_EVENT_MQUEUE_SIZE 8 struct GuiEvent { - FuriRecordSubscriber* input_event_record; + PubSub* input_event_record; osMessageQueueId_t mqueue; 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); GuiEvent* gui_event = ctx; @@ -29,9 +29,10 @@ GuiEvent* gui_event_alloc() { assert(gui_event->mqueue); // Input - gui_event->input_event_record = furi_open_deprecated( - "input_events", false, false, gui_event_input_events_callback, NULL, gui_event); + gui_event->input_event_record = furi_open("input_events"); assert(gui_event->input_event_record != NULL); + subscribe_pubsub(gui_event->input_event_record, gui_event_input_events_callback, gui_event); + // Lock mutex gui_event->lock_mutex = osMutexNew(NULL); assert(gui_event->lock_mutex); diff --git a/applications/input/input.c b/applications/input/input.c index eec75eb1..612abcbc 100644 --- a/applications/input/input.c +++ b/applications/input/input.c @@ -1,49 +1,47 @@ #include #include #include -#include +#include #ifdef APP_NFC void st25r3916Isr(void); #endif 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 = { false, }; void input_task(void* p) { uint32_t state_bits = 0; - StaticSemaphore_t event_semaphore; 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"); furiac_exit(NULL); } - FuriRecordSubscriber* input_state_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)) { + if(!furi_create("input_events", &input_events_record)) { printf("[input_task] cannot create the input_events record\n"); 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 furiac_ready(); initialized = true; @@ -82,7 +80,7 @@ void input_task(void* p) { if(changed_bits != 0) { // printf("[input] %02x -> %02x\n", state_bits, 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; @@ -90,13 +88,13 @@ void input_task(void* p) { if((changed_bits & (1 << i)) != 0) { bool state = (new_state_bits & (1 << i)) != 0; InputEvent event = {i, state}; - furi_write(input_events_record, &event, sizeof(event)); + notify_pubsub(&input_events_record, &event); } } } // Sleep: wait for event - xSemaphoreTake(event, portMAX_DELAY); + wait_event(&event); } else { osDelay(1); } @@ -113,12 +111,5 @@ void HAL_GPIO_EXTI_Callback(uint16_t pin) { if(!initialized) return; - BaseType_t task_woken = pdFALSE; - - // 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); - } + signal_event(&event); } diff --git a/applications/irda/irda.c b/applications/irda/irda.c index a6e79207..7e6c3ce8 100644 --- a/applications/irda/irda.c +++ b/applications/irda/irda.c @@ -14,7 +14,7 @@ typedef struct { InputEvent input; } value; EventType type; -} Event; +} AppEvent; typedef struct { uint8_t mode_id; @@ -24,15 +24,15 @@ typedef struct { uint8_t samsung_packet_id; } State; -typedef void (*ModeInput)(Event*, State*); +typedef void (*ModeInput)(AppEvent*, 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 input_nec(Event* event, State* state); +void input_nec(AppEvent* event, State* state); void render_nec(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); 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.state) { 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]); 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]); 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) { osMessageQueueId_t event_queue = (QueueHandle_t)ctx; - Event event; + AppEvent event; event.type = EventTypeKey; event.value.input = *input_event; osMessageQueuePut(event_queue, &event, 0, 0); } void irda(void* p) { - osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(Event), NULL); + osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(AppEvent), NULL); State _state; uint8_t mode_count = sizeof(modes) / sizeof(modes[0]); @@ -264,7 +264,7 @@ void irda(void* p) { } gui->add_widget(gui, widget, WidgetLayerFullscreen); - Event event; + AppEvent event; while(1) { osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); State* state = (State*)acquire_mutex_block(&state_mutex); diff --git a/applications/tests/furi_event_test.c b/applications/tests/furi_event_test.c new file mode 100644 index 00000000..f6b9a5e0 --- /dev/null +++ b/applications/tests/furi_event_test.c @@ -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)); +} diff --git a/applications/tests/furi_value_expanders_test.c b/applications/tests/furi_value_expanders_test.c new file mode 100644 index 00000000..8a795a4e --- /dev/null +++ b/applications/tests/furi_value_expanders_test.c @@ -0,0 +1,145 @@ +#include "flipper_v2.h" +#include "minunit.h" +#include + +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*)¬ify_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)); +} diff --git a/applications/tests/furi_valuemutex_test.c b/applications/tests/furi_valuemutex_test.c index 6279d188..88bd6673 100644 --- a/applications/tests/furi_valuemutex_test.c +++ b/applications/tests/furi_valuemutex_test.c @@ -37,6 +37,8 @@ void test_furi_valuemutex() { //acquire mutex blocking case //write 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_int_eq(value.a, value.b); + + mu_check(delete_mutex(&mutex)); } \ No newline at end of file diff --git a/applications/tests/minunit_test.c b/applications/tests/minunit_test.c index 472dfa7d..9d6a93f4 100644 --- a/applications/tests/minunit_test.c +++ b/applications/tests/minunit_test.c @@ -12,6 +12,9 @@ void test_furi_create_open(); void test_furi_valuemutex(); void test_furi_concurrent_access(); void test_furi_pubsub(); +void test_furi_value_composer(); +void test_furi_value_manager(); +void test_furi_event(); void test_furi_memmgr(); @@ -60,6 +63,15 @@ MU_TEST(mu_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_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_concurrent_access); 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); } diff --git a/core/api-basic/event.c b/core/api-basic/event.c new file mode 100644 index 00000000..1fc9cbfd --- /dev/null +++ b/core/api-basic/event.c @@ -0,0 +1,24 @@ +#include "event.h" +#include + +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; +} diff --git a/core/api-basic/event.h b/core/api-basic/event.h new file mode 100644 index 00000000..7905676e --- /dev/null +++ b/core/api-basic/event.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#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); diff --git a/core/api-basic/pubsub.c b/core/api-basic/pubsub.c index 9b06705a..8161ebf2 100644 --- a/core/api-basic/pubsub.c +++ b/core/api-basic/pubsub.c @@ -1,4 +1,5 @@ #include "pubsub.h" +#include "flipper_v2.h" bool init_pubsub(PubSub* pubsub) { // mutex without name, diff --git a/core/api-basic/pubsub.h b/core/api-basic/pubsub.h index 1c235159..bc866a9f 100644 --- a/core/api-basic/pubsub.h +++ b/core/api-basic/pubsub.h @@ -1,6 +1,6 @@ #pragma once -#include "flipper_v2.h" +#include "cmsis_os.h" #include "m-list.h" /* diff --git a/core/api-basic/value-expanders.c b/core/api-basic/value-expanders.c new file mode 100644 index 00000000..845ea4dc --- /dev/null +++ b/core/api-basic/value-expanders.c @@ -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; +} diff --git a/core/api-basic/value-expanders.c.unimplemented b/core/api-basic/value-expanders.c.unimplemented deleted file mode 100644 index 17e3a3e1..00000000 --- a/core/api-basic/value-expanders.c.unimplemented +++ /dev/null @@ -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; -} diff --git a/core/api-basic/value-expanders.h b/core/api-basic/value-expanders.h index 70adf856..57ef4c13 100644 --- a/core/api-basic/value-expanders.h +++ b/core/api-basic/value-expanders.h @@ -1,26 +1,76 @@ #pragma once #include "flipper.h" +#include "valuemutex.h" +#include "pubsub.h" +#include "event.h" +#include "m-list.h" /* == Value composer == */ -typedef void(ValueComposerCallback)(void* ctx, void* state); +typedef struct ValueComposer ValueComposer; -void COPY_COMPOSE(void* ctx, void* state) { - read_mutex((ValueMutex*)ctx, state, 0); -} +typedef void (*ValueComposerCallback)(void* ctx, void* state); -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* -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); 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. /* @@ -39,6 +89,14 @@ typedef struct { PubSub pubsub; } 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. */ diff --git a/core/api-basic/valuemutex.c b/core/api-basic/valuemutex.c index 564cb901..eb6c20bf 100644 --- a/core/api-basic/valuemutex.c +++ b/core/api-basic/valuemutex.c @@ -17,6 +17,14 @@ bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { 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) { if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) { return valuemutex->value; diff --git a/core/api-basic/valuemutex.h b/core/api-basic/valuemutex.h index 6ba7b919..5309cb9b 100644 --- a/core/api-basic/valuemutex.h +++ b/core/api-basic/valuemutex.h @@ -21,6 +21,12 @@ Creates ValueMutex. */ 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. Returns pointer to data if success, NULL otherwise. diff --git a/core/flipper_v2.h b/core/flipper_v2.h index cb6b63e8..7e72f9d9 100644 --- a/core/flipper_v2.h +++ b/core/flipper_v2.h @@ -11,6 +11,8 @@ extern "C" { #include "cmsis_os2.h" #include "api-basic/valuemutex.h" #include "api-basic/pubsub.h" +#include "api-basic/value-expanders.h" +#include "api-basic/event.h" #include "api-basic/memmgr.h" diff --git a/firmware/targets/local/Inc/cmsis_os.h b/firmware/targets/local/Inc/cmsis_os.h index c1c574ab..0fa7ef68 100644 --- a/firmware/targets/local/Inc/cmsis_os.h +++ b/firmware/targets/local/Inc/cmsis_os.h @@ -97,3 +97,12 @@ osStatus_t osMutexRelease (osMutexId_t mutex_id); osStatus_t osMutexDelete (osMutexId_t mutex_id); #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); diff --git a/firmware/targets/local/Src/lo_os.c b/firmware/targets/local/Src/lo_os.c index 94c570a3..6b0440eb 100644 --- a/firmware/targets/local/Src/lo_os.c +++ b/firmware/targets/local/Src/lo_os.c @@ -265,3 +265,55 @@ osStatus_t osMutexDelete (osMutexId_t mutex_id) { 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); +}