Example ipc (#60)
* add blank example * add ipc example code, need to change FURI API * add ipc example code, need to change FURI API * change core API, add context * check handler at take * fix important bugs in furi * drawing example * add posix mq * fix unsigned demo counter * create at open * working local demo * russian version of IPC example * english version * add gif
This commit is contained in:
		
							parent
							
								
									f7882dbff4
								
							
						
					
					
						commit
						5b6ab7faf3
					
				
							
								
								
									
										158
									
								
								applications/examples/ipc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								applications/examples/ipc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | |||||||
|  | #include "flipper.h" | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | #define FB_WIDTH 10 | ||||||
|  | #define FB_HEIGHT 3 | ||||||
|  | #define FB_SIZE (FB_WIDTH * FB_HEIGHT) | ||||||
|  | 
 | ||||||
|  | // context structure used for pass some object from app thread to callback
 | ||||||
|  | typedef struct { | ||||||
|  |     SemaphoreHandle_t events; // queue to pass events from callback to app thread
 | ||||||
|  |     FuriRecordSubscriber* log; // app logger
 | ||||||
|  | } IpcCtx; | ||||||
|  | 
 | ||||||
|  | static void handle_fb_change(const void* fb, size_t fb_size, void* raw_ctx) { | ||||||
|  |     IpcCtx* ctx = (IpcCtx*)raw_ctx; // make right type
 | ||||||
|  | 
 | ||||||
|  |     fuprintf(ctx->log, "[cb] framebuffer updated\n"); | ||||||
|  | 
 | ||||||
|  |     // send event to app thread
 | ||||||
|  |     xSemaphoreGive(ctx->events); | ||||||
|  | 
 | ||||||
|  |     // Attention! Please, do not make blocking operation like IO and waits inside callback
 | ||||||
|  |     // Remember that callback execute in calling thread/context
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void print_fb(char* fb, FuriRecordSubscriber* log) { | ||||||
|  |     if(fb == NULL) return; | ||||||
|  |      | ||||||
|  |     /* draw framebuffer like this:
 | ||||||
|  |     +==========+ | ||||||
|  |     |          | | ||||||
|  |     |          | | ||||||
|  |     |          | | ||||||
|  |     +==========+ | ||||||
|  |     */ | ||||||
|  | 
 | ||||||
|  |     char row_buffer[FB_WIDTH + 1]; | ||||||
|  |     row_buffer[FB_WIDTH] = '\0'; | ||||||
|  | 
 | ||||||
|  |     // FB layout is hardcoded here
 | ||||||
|  |     fuprintf(log, "+==========+\n"); | ||||||
|  |     for(uint8_t i = 0; i < FB_HEIGHT; i++) { | ||||||
|  |         strncpy(row_buffer, &fb[FB_WIDTH * i], FB_WIDTH); | ||||||
|  |         fuprintf(log, "|%s|\n", row_buffer); | ||||||
|  |     } | ||||||
|  |     fuprintf(log, "+==========+\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void application_ipc_display(void* p) { | ||||||
|  |     // get logger
 | ||||||
|  |     FuriRecordSubscriber* log = get_default_log(); | ||||||
|  | 
 | ||||||
|  |     // create ASCII "framebuffer"
 | ||||||
|  |     // FB_WIDTH x FB_HEIGHT char buffer
 | ||||||
|  |     char _framebuffer[FB_SIZE]; | ||||||
|  | 
 | ||||||
|  |     // init framebuffer by spaces
 | ||||||
|  |     for(size_t i = 0; i < FB_SIZE; i++) { | ||||||
|  |         _framebuffer[i] = ' '; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // create record
 | ||||||
|  |     if(!furi_create("test_fb", (void*)_framebuffer, FB_SIZE)) { | ||||||
|  |         fuprintf(log, "[display] cannot create fb record\n"); | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     StaticSemaphore_t event_descriptor; | ||||||
|  |     // create stack-based counting semaphore
 | ||||||
|  |     SemaphoreHandle_t events = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor); | ||||||
|  | 
 | ||||||
|  |     if(events == NULL) { | ||||||
|  |         fuprintf(log, "[display] cannot create event semaphore\n"); | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // save log and event queue in context structure
 | ||||||
|  |     IpcCtx ctx = {.events = events, .log = log}; | ||||||
|  | 
 | ||||||
|  |     // subscribe to record. ctx will be passed to handle_fb_change
 | ||||||
|  |     FuriRecordSubscriber* fb_record = furi_open( | ||||||
|  |         "test_fb", false, false, handle_fb_change, NULL, &ctx | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     if(fb_record == NULL) { | ||||||
|  |         fuprintf(log, "[display] cannot open fb record\n"); | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #ifdef HW_DISPLAY | ||||||
|  |     // on Flipper target -- open screen
 | ||||||
|  | 
 | ||||||
|  |     // draw border
 | ||||||
|  | 
 | ||||||
|  |     #else | ||||||
|  |     // on Local target -- print "blank screen"
 | ||||||
|  |     { | ||||||
|  |         void* fb = furi_take(fb_record); | ||||||
|  |         print_fb((char*)fb, log); | ||||||
|  |         furi_give(fb_record); | ||||||
|  |     } | ||||||
|  |     #endif | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         // wait for event
 | ||||||
|  |         if(xSemaphoreTake(events, portMAX_DELAY) == pdTRUE) { | ||||||
|  |             fuprintf(log, "[display] get fb update\n\n"); | ||||||
|  | 
 | ||||||
|  |             #ifdef HW_DISPLAY | ||||||
|  |             // on Flipper target draw the screen
 | ||||||
|  |             #else | ||||||
|  |             // on local target just print
 | ||||||
|  |             { | ||||||
|  |                 void* fb = furi_take(fb_record); | ||||||
|  |                 print_fb((char*)fb, log); | ||||||
|  |                 furi_give(fb_record); | ||||||
|  |             } | ||||||
|  |             #endif | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Widget application
 | ||||||
|  | void application_ipc_widget(void* p) { | ||||||
|  |     FuriRecordSubscriber* log = get_default_log(); | ||||||
|  | 
 | ||||||
|  |     // open record
 | ||||||
|  |     FuriRecordSubscriber* fb_record = furi_open( | ||||||
|  |         "test_fb", false, false, NULL, NULL, NULL | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     if(fb_record == NULL) { | ||||||
|  |         fuprintf(log, "[widget] cannot create fb record\n"); | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint8_t counter = 0; | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         delay(120); | ||||||
|  | 
 | ||||||
|  |         // write some ascii demo here: '#'' symbol run on overall screen
 | ||||||
|  |         char* fb = (char*)furi_take(fb_record); | ||||||
|  | 
 | ||||||
|  |         if(fb == NULL) furiac_exit(NULL); | ||||||
|  | 
 | ||||||
|  |         for(size_t i = 0; i < FB_SIZE; i++) { | ||||||
|  |             fb[i] = ' '; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         fb[counter % FB_SIZE] = '#'; | ||||||
|  | 
 | ||||||
|  |         furi_commit(fb_record); | ||||||
|  | 
 | ||||||
|  |         counter++; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -13,6 +13,8 @@ void flipper_test_app(void* p); | |||||||
| 
 | 
 | ||||||
| void application_blink(void* p); | void application_blink(void* p); | ||||||
| void application_uart_write(void* p); | void application_uart_write(void* p); | ||||||
|  | void application_ipc_display(void* p); | ||||||
|  | void application_ipc_widget(void* p); | ||||||
| 
 | 
 | ||||||
| const FlipperStartupApp FLIPPER_STARTUP[] = { | const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||||
|     #ifdef TEST |     #ifdef TEST | ||||||
| @ -25,4 +27,8 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { | |||||||
|     #ifdef EXAMPLE_UART_WRITE |     #ifdef EXAMPLE_UART_WRITE | ||||||
|     {.app = application_uart_write, .name = "uart write"}, |     {.app = application_uart_write, .name = "uart write"}, | ||||||
|     #endif |     #endif | ||||||
|  |     #ifdef EXAMPLE_IPC | ||||||
|  |     {.app = application_ipc_display, .name = "ipc display"}, | ||||||
|  |     {.app = application_ipc_widget, .name = "ipc widget"}, | ||||||
|  |     #endif | ||||||
| }; | }; | ||||||
| @ -17,7 +17,7 @@ TEST: pipe record | |||||||
| 
 | 
 | ||||||
| static uint8_t pipe_record_value = 0; | static uint8_t pipe_record_value = 0; | ||||||
| 
 | 
 | ||||||
| void pipe_record_cb(const void* value, size_t size) { | void pipe_record_cb(const void* value, size_t size, void* ctx) { | ||||||
|     // hold value to static var
 |     // hold value to static var
 | ||||||
|     pipe_record_value = *((uint8_t*)value); |     pipe_record_value = *((uint8_t*)value); | ||||||
| } | } | ||||||
| @ -31,7 +31,7 @@ bool test_furi_pipe_record(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 2. Open/subscribe to it 
 |     // 2. Open/subscribe to it 
 | ||||||
|     FuriRecordSubscriber* pipe_record = furi_open( |     FuriRecordSubscriber* pipe_record = furi_open( | ||||||
|         "test/pipe", false, false, pipe_record_cb, NULL |         "test/pipe", false, false, pipe_record_cb, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(pipe_record == NULL) { |     if(pipe_record == NULL) { | ||||||
|         fuprintf(log, "cannot open record\n"); |         fuprintf(log, "cannot open record\n"); | ||||||
| @ -83,7 +83,7 @@ TEST: holding data | |||||||
| 
 | 
 | ||||||
| static uint8_t holding_record_value = 0; | static uint8_t holding_record_value = 0; | ||||||
| 
 | 
 | ||||||
| void holding_record_cb(const void* value, size_t size) { | void holding_record_cb(const void* value, size_t size, void* ctx) { | ||||||
|     // hold value to static var
 |     // hold value to static var
 | ||||||
|     holding_record_value = *((uint8_t*)value); |     holding_record_value = *((uint8_t*)value); | ||||||
| } | } | ||||||
| @ -98,7 +98,7 @@ bool test_furi_holding_data(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 2. Open/Subscribe on it
 |     // 2. Open/Subscribe on it
 | ||||||
|     FuriRecordSubscriber* holding_record = furi_open( |     FuriRecordSubscriber* holding_record = furi_open( | ||||||
|         "test/holding", false, false, holding_record_cb, NULL |         "test/holding", false, false, holding_record_cb, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(holding_record == NULL) { |     if(holding_record == NULL) { | ||||||
|         fuprintf(log, "cannot open record\n"); |         fuprintf(log, "cannot open record\n"); | ||||||
| @ -164,7 +164,7 @@ void furi_concurent_app(void* p) { | |||||||
|     FuriRecordSubscriber* log = (FuriRecordSubscriber*)p; |     FuriRecordSubscriber* log = (FuriRecordSubscriber*)p; | ||||||
| 
 | 
 | ||||||
|     FuriRecordSubscriber* holding_record = furi_open( |     FuriRecordSubscriber* holding_record = furi_open( | ||||||
|         "test/concurrent", false, false, NULL, NULL |         "test/concurrent", false, false, NULL, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(holding_record == NULL) { |     if(holding_record == NULL) { | ||||||
|         fuprintf(log, "cannot open record\n"); |         fuprintf(log, "cannot open record\n"); | ||||||
| @ -203,7 +203,7 @@ bool test_furi_concurrent_access(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 2. Open it
 |     // 2. Open it
 | ||||||
|     FuriRecordSubscriber* holding_record = furi_open( |     FuriRecordSubscriber* holding_record = furi_open( | ||||||
|         "test/concurrent", false, false, NULL, NULL |         "test/concurrent", false, false, NULL, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(holding_record == NULL) { |     if(holding_record == NULL) { | ||||||
|         fuprintf(log, "cannot open record\n"); |         fuprintf(log, "cannot open record\n"); | ||||||
| @ -307,12 +307,12 @@ TODO: test 7 not pass beacuse cleanup is not implemented | |||||||
| static uint8_t mute_last_value = 0; | static uint8_t mute_last_value = 0; | ||||||
| static FlipperRecordState mute_last_state = 255; | static FlipperRecordState mute_last_state = 255; | ||||||
| 
 | 
 | ||||||
| void mute_record_cb(const void* value, size_t size) { | void mute_record_cb(const void* value, size_t size, void* ctx) { | ||||||
|     // hold value to static var
 |     // hold value to static var
 | ||||||
|     mute_last_value = *((uint8_t*)value); |     mute_last_value = *((uint8_t*)value); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void mute_record_state_cb(FlipperRecordState state) { | void mute_record_state_cb(FlipperRecordState state, void* ctx) { | ||||||
|     mute_last_state = state; |     mute_last_state = state; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -327,7 +327,7 @@ void furi_mute_parent_app(void* p) { | |||||||
| 
 | 
 | ||||||
|     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
 |     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
 | ||||||
|     FuriRecordSubscriber* watch_handler = furi_open( |     FuriRecordSubscriber* watch_handler = furi_open( | ||||||
|         "test/mute", false, false, mute_record_cb, NULL |         "test/mute", false, false, mute_record_cb, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(watch_handler == NULL) { |     if(watch_handler == NULL) { | ||||||
|         fuprintf(log, "cannot open watch handler\n"); |         fuprintf(log, "cannot open watch handler\n"); | ||||||
| @ -350,7 +350,7 @@ bool test_furi_mute_algorithm(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
 |     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
 | ||||||
|     FuriRecordSubscriber* handler_a = furi_open( |     FuriRecordSubscriber* handler_a = furi_open( | ||||||
|         "test/mute", false, false, NULL, mute_record_state_cb |         "test/mute", false, false, NULL, mute_record_state_cb, NULL | ||||||
|     ); |     ); | ||||||
|     if(handler_a == NULL) { |     if(handler_a == NULL) { | ||||||
|         fuprintf(log, "cannot open handler A\n"); |         fuprintf(log, "cannot open handler A\n"); | ||||||
| @ -372,7 +372,7 @@ bool test_furi_mute_algorithm(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 |     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 | ||||||
|     FuriRecordSubscriber* handler_b = furi_open( |     FuriRecordSubscriber* handler_b = furi_open( | ||||||
|         "test/mute", true, true, NULL, NULL |         "test/mute", true, true, NULL, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(handler_b == NULL) { |     if(handler_b == NULL) { | ||||||
|         fuprintf(log, "cannot open handler B\n"); |         fuprintf(log, "cannot open handler B\n"); | ||||||
| @ -415,7 +415,7 @@ bool test_furi_mute_algorithm(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 |     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 | ||||||
|     FuriRecordSubscriber* handler_c = furi_open( |     FuriRecordSubscriber* handler_c = furi_open( | ||||||
|         "test/mute", true, false, NULL, NULL |         "test/mute", true, false, NULL, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(handler_c == NULL) { |     if(handler_c == NULL) { | ||||||
|         fuprintf(log, "cannot open handler C\n"); |         fuprintf(log, "cannot open handler C\n"); | ||||||
| @ -428,7 +428,7 @@ bool test_furi_mute_algorithm(FuriRecordSubscriber* log) { | |||||||
| 
 | 
 | ||||||
|     // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
 |     // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
 | ||||||
|     FuriRecordSubscriber* handler_d = furi_open( |     FuriRecordSubscriber* handler_d = furi_open( | ||||||
|         "test/mute", false, false, NULL, NULL |         "test/mute", false, false, NULL, NULL, NULL | ||||||
|     ); |     ); | ||||||
|     if(handler_d == NULL) { |     if(handler_d == NULL) { | ||||||
|         fuprintf(log, "cannot open handler D\n"); |         fuprintf(log, "cannot open handler D\n"); | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| @ -6,6 +8,7 @@ extern "C" { | |||||||
|     #include "flipper_hal.h" |     #include "flipper_hal.h" | ||||||
|     #include "cmsis_os.h" |     #include "cmsis_os.h" | ||||||
|     #include "furi.h" |     #include "furi.h" | ||||||
|  |     #include "log.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								core/furi.c
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								core/furi.c
									
									
									
									
									
								
							| @ -27,11 +27,27 @@ static FuriRecord* find_record(const char* name) { | |||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO: change open-create to only open
 | ||||||
| bool furi_create(const char* name, void* value, size_t size) { | bool furi_create(const char* name, void* value, size_t size) { | ||||||
|     #ifdef FURI_DEBUG |     #ifdef FURI_DEBUG | ||||||
|         printf("[FURI] creating %s record\n", name); |         printf("[FURI] creating %s record\n", name); | ||||||
|     #endif |     #endif | ||||||
| 
 | 
 | ||||||
|  |     FuriRecord* record = find_record(name); | ||||||
|  | 
 | ||||||
|  |     if(record != NULL) { | ||||||
|  |         #ifdef FURI_DEBUG | ||||||
|  |             printf("[FURI] record already exist\n"); | ||||||
|  |         #endif | ||||||
|  | 
 | ||||||
|  |         record->value = value; | ||||||
|  |         record->size = size; | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // record not exist, create new
 | ||||||
|  | 
 | ||||||
|     if(current_buffer_idx >= MAX_RECORD_COUNT) { |     if(current_buffer_idx >= MAX_RECORD_COUNT) { | ||||||
|         // max record count exceed
 |         // max record count exceed
 | ||||||
|         #ifdef FURI_DEBUG |         #ifdef FURI_DEBUG | ||||||
| @ -50,6 +66,7 @@ bool furi_create(const char* name, void* value, size_t size) { | |||||||
| 
 | 
 | ||||||
|     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { |     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { | ||||||
|         records[current_buffer_idx].subscribers[i].allocated = false; |         records[current_buffer_idx].subscribers[i].allocated = false; | ||||||
|  |         records[current_buffer_idx].subscribers[i].ctx = NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     current_buffer_idx++; |     current_buffer_idx++; | ||||||
| @ -62,7 +79,8 @@ FuriRecordSubscriber* furi_open( | |||||||
|     bool solo, |     bool solo, | ||||||
|     bool no_mute, |     bool no_mute, | ||||||
|     FlipperRecordCallback value_callback, |     FlipperRecordCallback value_callback, | ||||||
|     FlipperRecordStateCallback state_callback |     FlipperRecordStateCallback state_callback, | ||||||
|  |     void* ctx | ||||||
| ) { | ) { | ||||||
|     #ifdef FURI_DEBUG |     #ifdef FURI_DEBUG | ||||||
|         printf("[FURI] opening %s record\n", name); |         printf("[FURI] opening %s record\n", name); | ||||||
| @ -77,9 +95,18 @@ FuriRecordSubscriber* furi_open( | |||||||
|             printf("[FURI] cannot find record %s\n", name); |             printf("[FURI] cannot find record %s\n", name); | ||||||
|         #endif |         #endif | ||||||
| 
 | 
 | ||||||
|  |         // create record if not exist
 | ||||||
|  |         if(!furi_create(name, NULL, 0)) { | ||||||
|             return NULL; |             return NULL; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         record = find_record(name); | ||||||
|  | 
 | ||||||
|  |         if(record == NULL) { | ||||||
|  |             return NULL; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // allocate subscriber
 |     // allocate subscriber
 | ||||||
|     FuriRecordSubscriber* subscriber = NULL; |     FuriRecordSubscriber* subscriber = NULL; | ||||||
| 
 | 
 | ||||||
| @ -111,6 +138,7 @@ FuriRecordSubscriber* furi_open( | |||||||
|     subscriber->cb = value_callback; |     subscriber->cb = value_callback; | ||||||
|     subscriber->state_cb = state_callback; |     subscriber->state_cb = state_callback; | ||||||
|     subscriber->record = record; |     subscriber->record = record; | ||||||
|  |     subscriber->ctx = ctx; | ||||||
| 
 | 
 | ||||||
|     // register record in application
 |     // register record in application
 | ||||||
|     FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle()); |     FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle()); | ||||||
| @ -152,22 +180,36 @@ static void furi_notify(FuriRecordSubscriber* handler, const void* value, size_t | |||||||
|     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { |     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { | ||||||
|         if(handler->record->subscribers[i].allocated) { |         if(handler->record->subscribers[i].allocated) { | ||||||
|             if(handler->record->subscribers[i].cb != NULL) { |             if(handler->record->subscribers[i].cb != NULL) { | ||||||
|                 handler->record->subscribers[i].cb(value, size); |                 handler->record->subscribers[i].cb( | ||||||
|  |                     value, | ||||||
|  |                     size, | ||||||
|  |                     handler->record->subscribers[i].ctx | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void* furi_take(FuriRecordSubscriber* handler) { | void* furi_take(FuriRecordSubscriber* handler) { | ||||||
|  |     if(handler == NULL || handler->record == NULL) return NULL; | ||||||
|     // take mutex
 |     // take mutex
 | ||||||
| 
 | 
 | ||||||
|     return handler->record->value; |     return handler->record->value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_give(FuriRecordSubscriber* handler) { | void furi_give(FuriRecordSubscriber* handler) { | ||||||
|  |     if(handler == NULL || handler->record == NULL) return; | ||||||
|  | 
 | ||||||
|     // release mutex
 |     // release mutex
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void furi_commit(FuriRecordSubscriber* handler) { | ||||||
|  |     if(handler == NULL || handler->record == NULL) return; | ||||||
|  | 
 | ||||||
|  |     furi_give(handler); | ||||||
|  |     furi_notify(handler, handler->record->value, handler->record->size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool furi_read(FuriRecordSubscriber* handler, void* value, size_t size) { | bool furi_read(FuriRecordSubscriber* handler, void* value, size_t size) { | ||||||
|     #ifdef FURI_DEBUG |     #ifdef FURI_DEBUG | ||||||
|         printf("[FURI] read from %s\n", handler->record->name); |         printf("[FURI] read from %s\n", handler->record->name); | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								core/furi.h
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								core/furi.h
									
									
									
									
									
								
							| @ -11,7 +11,7 @@ | |||||||
| typedef void(*FlipperApplication)(void*); | typedef void(*FlipperApplication)(void*); | ||||||
| 
 | 
 | ||||||
| /// pointer to value callback function
 | /// pointer to value callback function
 | ||||||
| typedef void(*FlipperRecordCallback)(const void*, size_t); | typedef void(*FlipperRecordCallback)(const void*, size_t, void*); | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     FlipperRecordStateMute, ///< record open and mute this handler
 |     FlipperRecordStateMute, ///< record open and mute this handler
 | ||||||
| @ -20,7 +20,7 @@ typedef enum { | |||||||
| } FlipperRecordState; | } FlipperRecordState; | ||||||
| 
 | 
 | ||||||
| /// pointer to state callback function
 | /// pointer to state callback function
 | ||||||
| typedef void(*FlipperRecordStateCallback)(FlipperRecordState); | typedef void(*FlipperRecordStateCallback)(FlipperRecordState, void*); | ||||||
| 
 | 
 | ||||||
| struct _FuriRecord; | struct _FuriRecord; | ||||||
| 
 | 
 | ||||||
| @ -31,6 +31,7 @@ typedef struct { | |||||||
|     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
 |     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
 | ||||||
|     bool no_mute; |     bool no_mute; | ||||||
|     struct _FuriRecord* record; ///< parent record
 |     struct _FuriRecord* record; ///< parent record
 | ||||||
|  |     void* ctx; | ||||||
| } FuriRecordSubscriber; | } FuriRecordSubscriber; | ||||||
| 
 | 
 | ||||||
| /// FURI record handler
 | /// FURI record handler
 | ||||||
| @ -114,7 +115,8 @@ FuriRecordSubscriber* furi_open( | |||||||
|     bool solo, |     bool solo, | ||||||
|     bool no_mute, |     bool no_mute, | ||||||
|     FlipperRecordCallback value_callback, |     FlipperRecordCallback value_callback, | ||||||
|     FlipperRecordStateCallback state_callback |     FlipperRecordStateCallback state_callback, | ||||||
|  |     void* ctx | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| @ -155,3 +157,8 @@ void* furi_take(FuriRecordSubscriber* record); | |||||||
| unlock value mutex. | unlock value mutex. | ||||||
| */ | */ | ||||||
| void furi_give(FuriRecordSubscriber* record); | void furi_give(FuriRecordSubscriber* record); | ||||||
|  | 
 | ||||||
|  | /*!
 | ||||||
|  | unlock value mutex and notify subscribers that data is chaned. | ||||||
|  | */ | ||||||
|  | void furi_commit(FuriRecordSubscriber* handler); | ||||||
|  | |||||||
| @ -21,5 +21,5 @@ void fuprintf(FuriRecordSubscriber* f, const char * format, ...) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FuriRecordSubscriber* get_default_log() { | FuriRecordSubscriber* get_default_log() { | ||||||
|     return furi_open("tty", false, false, NULL, NULL); |     return furi_open("tty", false, false, NULL, NULL, NULL); | ||||||
| } | } | ||||||
| @ -3,7 +3,7 @@ | |||||||
| 
 | 
 | ||||||
| extern UART_HandleTypeDef DEBUG_UART; | extern UART_HandleTypeDef DEBUG_UART; | ||||||
| 
 | 
 | ||||||
| void handle_uart_write(const void* data, size_t size) { | void handle_uart_write(const void* data, size_t size, void* ctx) { | ||||||
| 	HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)data, (uint16_t)size, HAL_MAX_DELAY); | 	HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)data, (uint16_t)size, HAL_MAX_DELAY); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -12,7 +12,7 @@ bool register_tty_uart() { | |||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	if(furi_open("tty", false, false, handle_uart_write, NULL) == NULL) { | 	if(furi_open("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -137,6 +137,11 @@ C_SOURCES += ../applications/examples/uart_write.c | |||||||
| C_DEFS += -DEXAMPLE_UART_WRITE | C_DEFS += -DEXAMPLE_UART_WRITE | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | ifeq ($(EXAMPLE_IPC), 1) | ||||||
|  | C_SOURCES += ../applications/examples/ipc.c | ||||||
|  | C_DEFS += -DEXAMPLE_IPC | ||||||
|  | endif | ||||||
|  | 
 | ||||||
| # User application
 | # User application
 | ||||||
| 
 | 
 | ||||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||||
| @ -256,13 +261,19 @@ rust_lib: | |||||||
| 	$(RUST_LIB_CMD) | 	$(RUST_LIB_CMD) | ||||||
| 
 | 
 | ||||||
| example_blink: | example_blink: | ||||||
|  | 	rm $(BUILD_DIR)/app.o | ||||||
| 	EXAMPLE_BLINK=1 make | 	EXAMPLE_BLINK=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o | 	rm $(BUILD_DIR)/app.o | ||||||
| 
 | 
 | ||||||
| example_uart_write: | example_uart_write: | ||||||
|  | 	rm $(BUILD_DIR)/app.o | ||||||
| 	EXAMPLE_UART_WRITE=1 make | 	EXAMPLE_UART_WRITE=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o | 	rm $(BUILD_DIR)/app.o | ||||||
| 
 | 
 | ||||||
|  | example_ipc: | ||||||
|  | 	rm $(BUILD_DIR)/app.o | ||||||
|  | 	EXAMPLE_IPC=1 make | ||||||
|  | 
 | ||||||
| test: | test: | ||||||
| 	TEST=1 make | 	TEST=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o | 	rm $(BUILD_DIR)/app.o | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
| #include "main.h" | #include "main.h" | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| 
 | 
 | ||||||
| @ -5,12 +7,33 @@ void osDelay(uint32_t ms); | |||||||
| 
 | 
 | ||||||
| // some FreeRTOS types
 | // some FreeRTOS types
 | ||||||
| typedef void(*TaskFunction_t)(void*); | typedef void(*TaskFunction_t)(void*); | ||||||
| typedef uint32_t UBaseType_t; | typedef size_t UBaseType_t; | ||||||
| typedef uint32_t StackType_t; | typedef uint32_t StackType_t; | ||||||
| typedef uint32_t StaticTask_t; | typedef uint32_t StaticTask_t; | ||||||
| typedef pthread_t* TaskHandle_t; | typedef pthread_t* TaskHandle_t; | ||||||
| typedef uint32_t StaticSemaphore_t; | 
 | ||||||
| typedef void* SemaphoreHandle_t; | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SemaphoreTypeCounting | ||||||
|  | } SemaphoreType; | ||||||
|  | typedef struct { | ||||||
|  |     SemaphoreType type; | ||||||
|  |     uint8_t take_counter; | ||||||
|  |     uint8_t give_counter; | ||||||
|  | } StaticSemaphore_t; | ||||||
|  | typedef StaticSemaphore_t* SemaphoreHandle_t; | ||||||
|  | 
 | ||||||
|  | typedef uint32_t StaticQueue_t; | ||||||
|  | typedef StaticQueue_t* QueueHandle_t; | ||||||
|  | 
 | ||||||
|  | #define portMAX_DELAY -1 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     pdTRUE = 1, | ||||||
|  |     pdFALSE = 0 | ||||||
|  | } BaseType_t; | ||||||
|  | 
 | ||||||
|  | typedef int32_t TickType_t; | ||||||
| 
 | 
 | ||||||
| #define tskIDLE_PRIORITY 0 | #define tskIDLE_PRIORITY 0 | ||||||
| 
 | 
 | ||||||
| @ -28,3 +51,24 @@ void vTaskDelete(TaskHandle_t xTask); | |||||||
| TaskHandle_t xTaskGetCurrentTaskHandle(void); | TaskHandle_t xTaskGetCurrentTaskHandle(void); | ||||||
| SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer); | SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer); | ||||||
| bool task_equal(TaskHandle_t a, TaskHandle_t b); | bool task_equal(TaskHandle_t a, TaskHandle_t b); | ||||||
|  | 
 | ||||||
|  | QueueHandle_t xQueueCreateStatic( | ||||||
|  |     UBaseType_t uxQueueLength, | ||||||
|  |     UBaseType_t uxItemSize, | ||||||
|  |     uint8_t* pucQueueStorageBuffer, | ||||||
|  |     StaticQueue_t* pxQueueBuffer | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | SemaphoreHandle_t xSemaphoreCreateCountingStatic( | ||||||
|  |     UBaseType_t uxMaxCount, | ||||||
|  |     UBaseType_t uxInitialCount, | ||||||
|  |     StaticSemaphore_t *pxSemaphoreBuffer | ||||||
|  | ); | ||||||
|  | BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); | ||||||
|  | BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore); | ||||||
|  | 
 | ||||||
|  | BaseType_t xQueueSend( | ||||||
|  |     QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ C_SOURCES += Src/flipper_hal.c | |||||||
| C_SOURCES += Src/lo_os.c | C_SOURCES += Src/lo_os.c | ||||||
| C_SOURCES += Src/lo_hal.c | C_SOURCES += Src/lo_hal.c | ||||||
| 
 | 
 | ||||||
| C_DEFS += -DFURI_DEBUG | # C_DEFS += -DFURI_DEBUG
 | ||||||
| 
 | 
 | ||||||
| # Core
 | # Core
 | ||||||
| 
 | 
 | ||||||
| @ -63,6 +63,11 @@ C_SOURCES += ../applications/examples/uart_write.c | |||||||
| C_DEFS += -DEXAMPLE_UART_WRITE | C_DEFS += -DEXAMPLE_UART_WRITE | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | ifeq ($(EXAMPLE_IPC), 1) | ||||||
|  | C_SOURCES += ../applications/examples/ipc.c | ||||||
|  | C_DEFS += -DEXAMPLE_IPC | ||||||
|  | endif | ||||||
|  | 
 | ||||||
| # User application
 | # User application
 | ||||||
| 
 | 
 | ||||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||||
| @ -139,19 +144,24 @@ rust_lib: | |||||||
| 	$(RUST_LIB_CMD) | 	$(RUST_LIB_CMD) | ||||||
| 
 | 
 | ||||||
| example_blink: | example_blink: | ||||||
|  | 	rm -f $(BUILD_DIR)/app.o | ||||||
| 	EXAMPLE_BLINK=1 make | 	EXAMPLE_BLINK=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o |  | ||||||
| 	$(BUILD_DIR)/$(TARGET) | 	$(BUILD_DIR)/$(TARGET) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| example_uart_write: | example_uart_write: | ||||||
|  | 	rm -f $(BUILD_DIR)/app.o | ||||||
| 	EXAMPLE_UART_WRITE=1 make | 	EXAMPLE_UART_WRITE=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o | 	$(BUILD_DIR)/$(TARGET) | ||||||
|  | 
 | ||||||
|  | example_ipc: | ||||||
|  | 	rm -f $(BUILD_DIR)/app.o | ||||||
|  | 	EXAMPLE_IPC=1 make | ||||||
| 	$(BUILD_DIR)/$(TARGET) | 	$(BUILD_DIR)/$(TARGET) | ||||||
| 
 | 
 | ||||||
| test: | test: | ||||||
|  | 	rm -f $(BUILD_DIR)/app.o | ||||||
| 	TEST=1 make | 	TEST=1 make | ||||||
| 	rm $(BUILD_DIR)/app.o |  | ||||||
| 	$(BUILD_DIR)/$(TARGET) | 	$(BUILD_DIR)/$(TARGET) | ||||||
| 
 | 
 | ||||||
| .PHONY: all rust_lib example_blink example_uart_write test | .PHONY: all rust_lib example_blink example_uart_write test | ||||||
|  | |||||||
| @ -4,6 +4,9 @@ | |||||||
| #include <pthread.h> | #include <pthread.h> | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <sys/ipc.h> | ||||||
|  | #include <sys/msg.h> | ||||||
| 
 | 
 | ||||||
| void osDelay(uint32_t ms) { | void osDelay(uint32_t ms) { | ||||||
|     // printf("[DELAY] %d ms\n", ms);
 |     // printf("[DELAY] %d ms\n", ms);
 | ||||||
| @ -83,3 +86,78 @@ SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer) | |||||||
|     // TODO add posix mutex init
 |     // TODO add posix mutex init
 | ||||||
|     return NULL; |     return NULL; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | BaseType_t xQueueSend( | ||||||
|  |     QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait | ||||||
|  | ) { | ||||||
|  |     // TODO: add implementation
 | ||||||
|  |     return pdTRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BaseType_t xQueueReceive( | ||||||
|  |     QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait | ||||||
|  | ) { | ||||||
|  |     // TODO: add implementation
 | ||||||
|  |     osDelay(100); | ||||||
|  | 
 | ||||||
|  |     return pdFALSE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint32_t queue_global_id = 0; | ||||||
|  | 
 | ||||||
|  | QueueHandle_t xQueueCreateStatic( | ||||||
|  |     UBaseType_t uxQueueLength, | ||||||
|  |     UBaseType_t uxItemSize, | ||||||
|  |     uint8_t* pucQueueStorageBuffer, | ||||||
|  |     StaticQueue_t *pxQueueBuffer | ||||||
|  | ) { | ||||||
|  |     // TODO: check this implementation
 | ||||||
|  |     int* msgid = malloc(sizeof(int)); | ||||||
|  | 
 | ||||||
|  |     key_t key = queue_global_id; | ||||||
|  |     queue_global_id++; | ||||||
|  | 
 | ||||||
|  |     *msgid = msgget(key, IPC_CREAT); | ||||||
|  | 
 | ||||||
|  |     return (QueueHandle_t)msgid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SemaphoreHandle_t xSemaphoreCreateCountingStatic( | ||||||
|  |     UBaseType_t uxMaxCount, | ||||||
|  |     UBaseType_t uxInitialCount, | ||||||
|  |     StaticSemaphore_t* pxSemaphoreBuffer | ||||||
|  | ) { | ||||||
|  |     pxSemaphoreBuffer->take_counter = 0; | ||||||
|  |     pxSemaphoreBuffer->give_counter = 0; | ||||||
|  |     return pxSemaphoreBuffer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait) { | ||||||
|  |     if(xSemaphore == NULL) return false; | ||||||
|  |      | ||||||
|  |     // TODO: need to add inter-process sync or use POSIX primitives
 | ||||||
|  |     xSemaphore->take_counter++; | ||||||
|  | 
 | ||||||
|  |     TickType_t ticks = xTicksToWait; | ||||||
|  | 
 | ||||||
|  |     while( | ||||||
|  |         xSemaphore->take_counter != xSemaphore->give_counter | ||||||
|  |         && (ticks > 0 || xTicksToWait == portMAX_DELAY) | ||||||
|  |     ) { | ||||||
|  |         osDelay(1); | ||||||
|  |         ticks--; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(xTicksToWait != 0 && ticks == 0) return pdFALSE; | ||||||
|  | 
 | ||||||
|  |     return pdTRUE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore) { | ||||||
|  |     if(xSemaphore == NULL) return false; | ||||||
|  | 
 | ||||||
|  |     // TODO: need to add inter-process sync or use POSIX primitives
 | ||||||
|  |     xSemaphore->give_counter++; | ||||||
|  | 
 | ||||||
|  |     return pdTRUE; | ||||||
|  | } | ||||||
| @ -18,6 +18,7 @@ _please, do not edit wiki directly in web-interface. Read [contrubution guide](C | |||||||
| 
 | 
 | ||||||
| * [Blink](Blink-app) | * [Blink](Blink-app) | ||||||
| * [UART write](UART-write) | * [UART write](UART-write) | ||||||
|  | * [Inter-process communication](IPC-example) | ||||||
| 
 | 
 | ||||||
| # Hardware | # Hardware | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										145
									
								
								wiki/examples/IPC-example.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								wiki/examples/IPC-example.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | |||||||
|  | In this example we show how to interact data between different applications. | ||||||
|  | As we already know, we can use FURI Record for this purpose: one application create named record and other application opens it by name. | ||||||
|  | 
 | ||||||
|  | We will simulate the display. The first application (called `application_ipc_display`) will be display driver, and the second app (called `application_ipc_widget`) will be draw such simple demo on the screen. | ||||||
|  | 
 | ||||||
|  | # Dsiplay definition | ||||||
|  | 
 | ||||||
|  | For work with the display we create simple framebuffer and write some "pixels" into it. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | #define FB_WIDTH 10 | ||||||
|  | #define FB_HEIGHT 3 | ||||||
|  | #define FB_SIZE (FB_WIDTH * FB_HEIGHT) | ||||||
|  | 
 | ||||||
|  | char _framebuffer[FB_SIZE]; | ||||||
|  | 
 | ||||||
|  | for(size_t i = 0; i < FB_SIZE; i++) { | ||||||
|  |     _framebuffer[i] = ' '; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | On local target we just draw framebuffer content like this: | ||||||
|  | ``` | ||||||
|  |     +==========+ | ||||||
|  |     |          | | ||||||
|  |     |          | | ||||||
|  |     |          | | ||||||
|  |     +==========+ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | fuprintf(log, "+==========+\n"); | ||||||
|  | for(uint8_t i = 0; i < FB_HEIGHT; i++) { | ||||||
|  |     strncpy(row_buffer, &fb[FB_WIDTH * i], FB_WIDTH); | ||||||
|  |     fuprintf(log, "|%s|\n", row_buffer); | ||||||
|  | } | ||||||
|  | fuprintf(log, "+==========+\n"); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | _Notice: after creating display emulator this example should be changed to work with real 128×64 display using u8g2_ | ||||||
|  | 
 | ||||||
|  | # Demo "widget" application | ||||||
|  | 
 | ||||||
|  | The application opens record with framebuffer: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | FuriRecordSubscriber* fb_record = furi_open( | ||||||
|  |     "test_fb", false, false, NULL, NULL, NULL | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then it clear display and draw "pixel" every 120 ms, and pixel move across the screen. | ||||||
|  | 
 | ||||||
|  | For do that we should tale framebuffer: | ||||||
|  | 
 | ||||||
|  | `char* fb = (char*)furi_take(fb_record);` | ||||||
|  | 
 | ||||||
|  | Write some data: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | if(fb == NULL) furiac_exit(NULL); | ||||||
|  | 
 | ||||||
|  | for(size_t i = 0; i < FB_SIZE; i++) { | ||||||
|  |     fb[i] = ' '; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fb[counter % FB_SIZE] = '#'; | ||||||
|  | ``` | ||||||
|  | And give framebuffer (use `furi_commit` to notify another apps that record was changed): | ||||||
|  | 
 | ||||||
|  | `furi_commit(fb_record);` | ||||||
|  | 
 | ||||||
|  | `counter` is increasing on every iteration to make "pixel" moving. | ||||||
|  | 
 | ||||||
|  | # Display driver | ||||||
|  | 
 | ||||||
|  | The driver application creates framebuffer after start (see [Display definition](#Display-definition)) and creates new FURI record with framebuffer pointer: | ||||||
|  | 
 | ||||||
|  | `furi_create("test_fb", (void*)_framebuffer, FB_SIZE)` | ||||||
|  | 
 | ||||||
|  | Next it opens this record and subscribe to its changing: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | FuriRecordSubscriber* fb_record = furi_open( | ||||||
|  |     "test_fb", false, false, handle_fb_change, NULL, &ctx | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The handler is called any time when some app writes to framebuffer record (by calling `furi_commit`): | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | static void handle_fb_change(const void* fb, size_t fb_size, void* raw_ctx) { | ||||||
|  |     IpcCtx* ctx = (IpcCtx*)raw_ctx; // make right type | ||||||
|  | 
 | ||||||
|  |     fuprintf(ctx->log, "[cb] framebuffer updated\n"); | ||||||
|  | 
 | ||||||
|  |     // send event to app thread | ||||||
|  |     xSemaphoreGive(ctx->events); | ||||||
|  | 
 | ||||||
|  |     // Attention! Please, do not make blocking operation like IO and waits inside callback | ||||||
|  |     // Remember that callback execute in calling thread/context | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | That callback execute in calling thread/context, so handler pass control flow to app thread by semaphore. App thread wait for semaphore and then do the "rendering": | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | if(xSemaphoreTake(events, portMAX_DELAY) == pdTRUE) { | ||||||
|  |     fuprintf(log, "[display] get fb update\n\n"); | ||||||
|  | 
 | ||||||
|  |     #ifdef HW_DISPLAY | ||||||
|  |     // on Flipper target draw the screen | ||||||
|  |     #else | ||||||
|  |     // on local target just print | ||||||
|  |     { | ||||||
|  |         void* fb = furi_take(fb_record); | ||||||
|  |         print_fb((char*)fb, log); | ||||||
|  |         furi_give(fb_record); | ||||||
|  |     } | ||||||
|  |     #endif | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | A structure containing the context is used so that the callback can access the semaphore. This structure is passed as an argument to `furi_open` and goes to the handler: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     SemaphoreHandle_t events; // queue to pass events from callback to app thread | ||||||
|  |     FuriRecordSubscriber* log; // app logger | ||||||
|  | } IpcCtx; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | We just have to create a semaphore and define a context: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | StaticSemaphore_t event_descriptor; | ||||||
|  | // create stack-based counting semaphore | ||||||
|  | SemaphoreHandle_t events = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor); | ||||||
|  | 
 | ||||||
|  | IpcCtx ctx = {.events = events, .log = log}; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You can find full example code in `applications/examples/ipc.c`, and run it by `docker-compose exec dev make -C target_lo example_ipc`. | ||||||
|  | 
 | ||||||
|  |  | ||||||
| @ -1,7 +1,3 @@ | |||||||
| One of the most important component of Flipper Core is [FURI](FURI) (Flipper Universal Registry Implementation). It helps control the applications flow, make dynamic linking and interaction between applications. |  | ||||||
| 
 |  | ||||||
| In fact, FURI is just wrapper around RTOS thread management and mutexes, and callback management. |  | ||||||
| 
 |  | ||||||
| In this article we create few application, interact between apps, use OS functions and interact with HAL. | In this article we create few application, interact between apps, use OS functions and interact with HAL. | ||||||
| 
 | 
 | ||||||
| # General agreements | # General agreements | ||||||
| @ -27,3 +23,4 @@ void application_name(void* p) { | |||||||
| 
 | 
 | ||||||
| * **[Blink](Blink-app)** show how to create app and control GPIO | * **[Blink](Blink-app)** show how to create app and control GPIO | ||||||
| * **[UART write](UART-write)** operate with FURI pipe and print some messages | * **[UART write](UART-write)** operate with FURI pipe and print some messages | ||||||
|  | * **[Inter-process communication](IPC-example)** describes how to interact between application through FURI | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								wiki_static/application_examples/example_ipc.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								wiki_static/application_examples/example_ipc.gif
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | version https://git-lfs.github.com/spec/v1 | ||||||
|  | oid sha256:af329bb9df3fcf8d74084753c6ab4ef10707a3227cdb3e7224ca76a8e81145e4 | ||||||
|  | size 180549 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 coreglitch
						coreglitch