diff --git a/applications/examples/ipc.c b/applications/examples/ipc.c new file mode 100644 index 00000000..5066fdd2 --- /dev/null +++ b/applications/examples/ipc.c @@ -0,0 +1,158 @@ +#include "flipper.h" +#include + +#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++; + } +} \ No newline at end of file diff --git a/applications/startup.h b/applications/startup.h index 02685c98..354f9d4a 100644 --- a/applications/startup.h +++ b/applications/startup.h @@ -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 }; \ No newline at end of file diff --git a/applications/tests/furi_record_test.c b/applications/tests/furi_record_test.c index cf608d51..77700604 100644 --- a/applications/tests/furi_record_test.c +++ b/applications/tests/furi_record_test.c @@ -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"); diff --git a/core/flipper.h b/core/flipper.h index 591761a2..96776d28 100644 --- a/core/flipper.h +++ b/core/flipper.h @@ -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 } diff --git a/core/furi.c b/core/furi.c index fc1e91ec..fc356438 100644 --- a/core/furi.c +++ b/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,7 +95,16 @@ FuriRecordSubscriber* furi_open( printf("[FURI] cannot find record %s\n", name); #endif - return NULL; + // create record if not exist + if(!furi_create(name, NULL, 0)) { + return NULL; + } + + record = find_record(name); + + if(record == NULL) { + return NULL; + } } // allocate subscriber @@ -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); diff --git a/core/furi.h b/core/furi.h index 00db3406..c66bad98 100644 --- a/core/furi.h +++ b/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); diff --git a/core/log.c b/core/log.c index 8e989652..1435bbec 100644 --- a/core/log.c +++ b/core/log.c @@ -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); } \ No newline at end of file diff --git a/core/tty_uart.c b/core/tty_uart.c index 20ef2caf..4e10e880 100644 --- a/core/tty_uart.c +++ b/core/tty_uart.c @@ -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; } diff --git a/target_f1/Makefile b/target_f1/Makefile index 42fddb0f..72803473 100644 --- a/target_f1/Makefile +++ b/target_f1/Makefile @@ -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 diff --git a/target_lo/Inc/cmsis_os.h b/target_lo/Inc/cmsis_os.h index 91873ab5..61b1a342 100644 --- a/target_lo/Inc/cmsis_os.h +++ b/target_lo/Inc/cmsis_os.h @@ -1,3 +1,5 @@ +#pragma once + #include "main.h" #include @@ -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); diff --git a/target_lo/Makefile b/target_lo/Makefile index 0a498f61..c0e74fff 100644 --- a/target_lo/Makefile +++ b/target_lo/Makefile @@ -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 diff --git a/target_lo/Src/lo_os.c b/target_lo/Src/lo_os.c index b2d9316e..f45b9522 100644 --- a/target_lo/Src/lo_os.c +++ b/target_lo/Src/lo_os.c @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include void osDelay(uint32_t ms) { // printf("[DELAY] %d ms\n", ms); @@ -82,4 +85,79 @@ bool task_equal(TaskHandle_t a, TaskHandle_t b) { 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; } \ No newline at end of file diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md index 280c3f0c..2e8ed80e 100644 --- a/wiki/_Sidebar.md +++ b/wiki/_Sidebar.md @@ -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 diff --git a/wiki/examples/IPC-example.md b/wiki/examples/IPC-example.md new file mode 100644 index 00000000..1b3d7b5e --- /dev/null +++ b/wiki/examples/IPC-example.md @@ -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`. + +![](https://github.com/Flipper-Zero/flipperzero-firmware-community/raw/master/wiki_static/application_examples/example_ipc.gif) diff --git a/wiki/fw/Application-examples.md b/wiki/fw/Application-examples.md index 2e907691..346e6f6c 100644 --- a/wiki/fw/Application-examples.md +++ b/wiki/fw/Application-examples.md @@ -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 diff --git a/wiki_static/application_examples/example_ipc.gif b/wiki_static/application_examples/example_ipc.gif new file mode 100644 index 00000000..4a2b5f58 --- /dev/null +++ b/wiki_static/application_examples/example_ipc.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af329bb9df3fcf8d74084753c6ab4ef10707a3227cdb3e7224ca76a8e81145e4 +size 180549