Просмотр исходного кода

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>
Vadim Kaushan 5 лет назад
Родитель
Сommit
bb68fca20b

+ 2 - 0
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

+ 5 - 3
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();

+ 4 - 4
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);

+ 8 - 4
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 <stdio.h>
 
 // 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);
     }

+ 10 - 7
applications/examples/input_dump.c

@@ -1,13 +1,13 @@
-#include "flipper.h"
+#include "flipper_v2.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;
 
     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);

+ 6 - 5
applications/gui/gui_event.c

@@ -1,17 +1,17 @@
 #include "gui_event.h"
 
-#include <flipper.h>
+#include <flipper_v2.h>
 #include <assert.h>
 
 #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);

+ 20 - 29
applications/input/input.c

@@ -1,46 +1,44 @@
 #include <input/input.h>
 #include <input_priv.h>
 #include <stdio.h>
-#include <flipper.h>
+#include <flipper_v2.h>
 
 #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(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) {
-        printf("[input_task] cannot create the input_state record\n");
+    if(!init_managed(&input_state_record, &input_state, sizeof(input_state))) {
+        printf("[input_task] cannot initialize ValueManager for input_state\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");
+    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_events", NULL, 0)) {
-        printf("[input_task] cannot create the input_events record\n");
+    if(!furi_create("input_state", &input_state_record)) {
+        printf("[input_task] cannot create the input_state 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");
+    if(!furi_create("input_events", &input_events_record)) {
+        printf("[input_task] cannot create the input_events record\n");
         furiac_exit(NULL);
     }
 
@@ -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);
 }

+ 11 - 11
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);

+ 30 - 0
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));
+}

+ 145 - 0
applications/tests/furi_value_expanders_test.c

@@ -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));
+}

+ 4 - 0
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));
 }

+ 14 - 0
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);
 }

+ 24 - 0
core/api-basic/event.c

@@ -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 - 0
core/api-basic/event.h

@@ -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);

+ 1 - 0
core/api-basic/pubsub.c

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

+ 1 - 1
core/api-basic/pubsub.h

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

+ 177 - 0
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;
+}

+ 0 - 24
core/api-basic/value-expanders.c.unimplemented

@@ -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;
-}

+ 64 - 6
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.
 */

+ 8 - 0
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;

+ 6 - 0
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.

+ 2 - 0
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"
 

+ 9 - 0
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);

+ 52 - 0
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);
+}