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:
		
							parent
							
								
									69d97afea8
								
							
						
					
					
						commit
						bb68fca20b
					
				| @ -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 | ||||||
|  | |||||||
| @ -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(); | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								applications/tests/furi_event_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/tests/furi_event_test.c
									
									
									
									
									
										Normal 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)); | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								applications/tests/furi_value_expanders_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								applications/tests/furi_value_expanders_test.c
									
									
									
									
									
										Normal 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*)¬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)); | ||||||
|  | } | ||||||
| @ -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)); | ||||||
| } | } | ||||||
| @ -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
									
								
							
							
						
						
									
										24
									
								
								core/api-basic/event.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										36
									
								
								core/api-basic/event.h
									
									
									
									
									
										Normal 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); | ||||||
| @ -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,
 | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "flipper_v2.h" | #include "cmsis_os.h" | ||||||
| #include "m-list.h" | #include "m-list.h" | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  | |||||||
							
								
								
									
										177
									
								
								core/api-basic/value-expanders.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								core/api-basic/value-expanders.c
									
									
									
									
									
										Normal 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; | ||||||
|  | } | ||||||
| @ -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; |  | ||||||
| } |  | ||||||
| @ -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. | ||||||
| */ | */ | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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. | ||||||
|  | |||||||
| @ -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" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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); | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Vadim Kaushan
						Vadim Kaushan