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_uart_write(void* p); | ||||
| void application_ipc_display(void* p); | ||||
| void application_ipc_widget(void* p); | ||||
| 
 | ||||
| const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||
|     #ifdef TEST | ||||
| @ -25,4 +27,8 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||
|     #ifdef EXAMPLE_UART_WRITE | ||||
|     {.app = application_uart_write, .name = "uart write"}, | ||||
|     #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; | ||||
| 
 | ||||
| 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
 | ||||
|     pipe_record_value = *((uint8_t*)value); | ||||
| } | ||||
| @ -31,7 +31,7 @@ bool test_furi_pipe_record(FuriRecordSubscriber* log) { | ||||
| 
 | ||||
|     // 2. Open/subscribe to it 
 | ||||
|     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) { | ||||
|         fuprintf(log, "cannot open record\n"); | ||||
| @ -83,7 +83,7 @@ TEST: holding data | ||||
| 
 | ||||
| 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
 | ||||
|     holding_record_value = *((uint8_t*)value); | ||||
| } | ||||
| @ -98,7 +98,7 @@ bool test_furi_holding_data(FuriRecordSubscriber* log) { | ||||
| 
 | ||||
|     // 2. Open/Subscribe on it
 | ||||
|     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) { | ||||
|         fuprintf(log, "cannot open record\n"); | ||||
| @ -164,7 +164,7 @@ void furi_concurent_app(void* p) { | ||||
|     FuriRecordSubscriber* log = (FuriRecordSubscriber*)p; | ||||
| 
 | ||||
|     FuriRecordSubscriber* holding_record = furi_open( | ||||
|         "test/concurrent", false, false, NULL, NULL | ||||
|         "test/concurrent", false, false, NULL, NULL, NULL | ||||
|     ); | ||||
|     if(holding_record == NULL) { | ||||
|         fuprintf(log, "cannot open record\n"); | ||||
| @ -203,7 +203,7 @@ bool test_furi_concurrent_access(FuriRecordSubscriber* log) { | ||||
| 
 | ||||
|     // 2. Open it
 | ||||
|     FuriRecordSubscriber* holding_record = furi_open( | ||||
|         "test/concurrent", false, false, NULL, NULL | ||||
|         "test/concurrent", false, false, NULL, NULL, NULL | ||||
|     ); | ||||
|     if(holding_record == NULL) { | ||||
|         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 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
 | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| @ -327,7 +327,7 @@ void furi_mute_parent_app(void* p) { | ||||
| 
 | ||||
|     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
 | ||||
|     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) { | ||||
|         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.
 | ||||
|     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) { | ||||
|         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.
 | ||||
|     FuriRecordSubscriber* handler_b = furi_open( | ||||
|         "test/mute", true, true, NULL, NULL | ||||
|         "test/mute", true, true, NULL, NULL, NULL | ||||
|     ); | ||||
|     if(handler_b == NULL) { | ||||
|         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.
 | ||||
|     FuriRecordSubscriber* handler_c = furi_open( | ||||
|         "test/mute", true, false, NULL, NULL | ||||
|         "test/mute", true, false, NULL, NULL, NULL | ||||
|     ); | ||||
|     if(handler_c == NULL) { | ||||
|         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.
 | ||||
|     FuriRecordSubscriber* handler_d = furi_open( | ||||
|         "test/mute", false, false, NULL, NULL | ||||
|         "test/mute", false, false, NULL, NULL, NULL | ||||
|     ); | ||||
|     if(handler_d == NULL) { | ||||
|         fuprintf(log, "cannot open handler D\n"); | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| @ -6,6 +8,7 @@ extern "C" { | ||||
|     #include "flipper_hal.h" | ||||
|     #include "cmsis_os.h" | ||||
|     #include "furi.h" | ||||
|     #include "log.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  | ||||
							
								
								
									
										46
									
								
								core/furi.c
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								core/furi.c
									
									
									
									
									
								
							| @ -27,11 +27,27 @@ static FuriRecord* find_record(const char* name) { | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| // TODO: change open-create to only open
 | ||||
| bool furi_create(const char* name, void* value, size_t size) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         printf("[FURI] creating %s record\n", name); | ||||
|     #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) { | ||||
|         // max record count exceed
 | ||||
|         #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++) { | ||||
|         records[current_buffer_idx].subscribers[i].allocated = false; | ||||
|         records[current_buffer_idx].subscribers[i].ctx = NULL; | ||||
|     } | ||||
| 
 | ||||
|     current_buffer_idx++; | ||||
| @ -62,7 +79,8 @@ FuriRecordSubscriber* furi_open( | ||||
|     bool solo, | ||||
|     bool no_mute, | ||||
|     FlipperRecordCallback value_callback, | ||||
|     FlipperRecordStateCallback state_callback | ||||
|     FlipperRecordStateCallback state_callback, | ||||
|     void* ctx | ||||
| ) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         printf("[FURI] opening %s record\n", name); | ||||
| @ -77,9 +95,18 @@ FuriRecordSubscriber* furi_open( | ||||
|             printf("[FURI] cannot find record %s\n", name); | ||||
|         #endif | ||||
| 
 | ||||
|         // create record if not exist
 | ||||
|         if(!furi_create(name, NULL, 0)) { | ||||
|             return NULL; | ||||
|         } | ||||
| 
 | ||||
|         record = find_record(name); | ||||
| 
 | ||||
|         if(record == NULL) { | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // allocate subscriber
 | ||||
|     FuriRecordSubscriber* subscriber = NULL; | ||||
| 
 | ||||
| @ -111,6 +138,7 @@ FuriRecordSubscriber* furi_open( | ||||
|     subscriber->cb = value_callback; | ||||
|     subscriber->state_cb = state_callback; | ||||
|     subscriber->record = record; | ||||
|     subscriber->ctx = ctx; | ||||
| 
 | ||||
|     // register record in application
 | ||||
|     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++) { | ||||
|         if(handler->record->subscribers[i].allocated) { | ||||
|             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) { | ||||
|     if(handler == NULL || handler->record == NULL) return NULL; | ||||
|     // take mutex
 | ||||
| 
 | ||||
|     return handler->record->value; | ||||
| } | ||||
| 
 | ||||
| void furi_give(FuriRecordSubscriber* handler) { | ||||
|     if(handler == NULL || handler->record == NULL) return; | ||||
| 
 | ||||
|     // 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) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         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*); | ||||
| 
 | ||||
| /// pointer to value callback function
 | ||||
| typedef void(*FlipperRecordCallback)(const void*, size_t); | ||||
| typedef void(*FlipperRecordCallback)(const void*, size_t, void*); | ||||
| 
 | ||||
| typedef enum { | ||||
|     FlipperRecordStateMute, ///< record open and mute this handler
 | ||||
| @ -20,7 +20,7 @@ typedef enum { | ||||
| } FlipperRecordState; | ||||
| 
 | ||||
| /// pointer to state callback function
 | ||||
| typedef void(*FlipperRecordStateCallback)(FlipperRecordState); | ||||
| typedef void(*FlipperRecordStateCallback)(FlipperRecordState, void*); | ||||
| 
 | ||||
| struct _FuriRecord; | ||||
| 
 | ||||
| @ -31,6 +31,7 @@ typedef struct { | ||||
|     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
 | ||||
|     bool no_mute; | ||||
|     struct _FuriRecord* record; ///< parent record
 | ||||
|     void* ctx; | ||||
| } FuriRecordSubscriber; | ||||
| 
 | ||||
| /// FURI record handler
 | ||||
| @ -114,7 +115,8 @@ FuriRecordSubscriber* furi_open( | ||||
|     bool solo, | ||||
|     bool no_mute, | ||||
|     FlipperRecordCallback value_callback, | ||||
|     FlipperRecordStateCallback state_callback | ||||
|     FlipperRecordStateCallback state_callback, | ||||
|     void* ctx | ||||
| ); | ||||
| 
 | ||||
| /*!
 | ||||
| @ -155,3 +157,8 @@ void* furi_take(FuriRecordSubscriber* record); | ||||
| unlock value mutex. | ||||
| */ | ||||
| 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() { | ||||
|     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; | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| @ -12,7 +12,7 @@ bool register_tty_uart() { | ||||
| 		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; | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -137,6 +137,11 @@ C_SOURCES += ../applications/examples/uart_write.c | ||||
| C_DEFS += -DEXAMPLE_UART_WRITE | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(EXAMPLE_IPC), 1) | ||||
| C_SOURCES += ../applications/examples/ipc.c | ||||
| C_DEFS += -DEXAMPLE_IPC | ||||
| endif | ||||
| 
 | ||||
| # User application
 | ||||
| 
 | ||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||
| @ -256,13 +261,19 @@ rust_lib: | ||||
| 	$(RUST_LIB_CMD) | ||||
| 
 | ||||
| example_blink: | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	EXAMPLE_BLINK=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 
 | ||||
| example_uart_write: | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	EXAMPLE_UART_WRITE=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 
 | ||||
| example_ipc: | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	EXAMPLE_IPC=1 make | ||||
| 
 | ||||
| test: | ||||
| 	TEST=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "main.h" | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| @ -5,12 +7,33 @@ void osDelay(uint32_t ms); | ||||
| 
 | ||||
| // some FreeRTOS types
 | ||||
| typedef void(*TaskFunction_t)(void*); | ||||
| typedef uint32_t UBaseType_t; | ||||
| typedef size_t UBaseType_t; | ||||
| typedef uint32_t StackType_t; | ||||
| typedef uint32_t StaticTask_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 | ||||
| 
 | ||||
| @ -28,3 +51,24 @@ void vTaskDelete(TaskHandle_t xTask); | ||||
| TaskHandle_t xTaskGetCurrentTaskHandle(void); | ||||
| SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer); | ||||
| 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_hal.c | ||||
| 
 | ||||
| C_DEFS += -DFURI_DEBUG | ||||
| # C_DEFS += -DFURI_DEBUG
 | ||||
| 
 | ||||
| # Core
 | ||||
| 
 | ||||
| @ -63,6 +63,11 @@ C_SOURCES += ../applications/examples/uart_write.c | ||||
| C_DEFS += -DEXAMPLE_UART_WRITE | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(EXAMPLE_IPC), 1) | ||||
| C_SOURCES += ../applications/examples/ipc.c | ||||
| C_DEFS += -DEXAMPLE_IPC | ||||
| endif | ||||
| 
 | ||||
| # User application
 | ||||
| 
 | ||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||
| @ -139,19 +144,24 @@ rust_lib: | ||||
| 	$(RUST_LIB_CMD) | ||||
| 
 | ||||
| example_blink: | ||||
| 	rm -f $(BUILD_DIR)/app.o | ||||
| 	EXAMPLE_BLINK=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	$(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| 
 | ||||
| example_uart_write: | ||||
| 	rm -f $(BUILD_DIR)/app.o | ||||
| 	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) | ||||
| 
 | ||||
| test: | ||||
| 	rm -f $(BUILD_DIR)/app.o | ||||
| 	TEST=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	$(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| .PHONY: all rust_lib example_blink example_uart_write test | ||||
|  | ||||
| @ -4,6 +4,9 @@ | ||||
| #include <pthread.h> | ||||
| #include <errno.h> | ||||
| #include <signal.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/ipc.h> | ||||
| #include <sys/msg.h> | ||||
| 
 | ||||
| void osDelay(uint32_t ms) { | ||||
|     // printf("[DELAY] %d ms\n", ms);
 | ||||
| @ -83,3 +86,78 @@ SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer) | ||||
|     // TODO add posix mutex init
 | ||||
|     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) | ||||
| * [UART write](UART-write) | ||||
| * [Inter-process communication](IPC-example) | ||||
| 
 | ||||
| # 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. | ||||
| 
 | ||||
| # General agreements | ||||
| @ -27,3 +23,4 @@ void application_name(void* p) { | ||||
| 
 | ||||
| * **[Blink](Blink-app)** show how to create app and control GPIO | ||||
| * **[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