Przeglądaj źródła

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
coreglitch 5 lat temu
rodzic
commit
5b6ab7faf3

+ 158 - 0
applications/examples/ipc.c

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

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

+ 13 - 13
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");

+ 3 - 0
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
 }

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

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

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

+ 2 - 2
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;
 	}
 

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

+ 47 - 3
target_lo/Inc/cmsis_os.h

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

+ 14 - 4
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

+ 78 - 0
target_lo/Src/lo_os.c

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

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

+ 145 - 0
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)

+ 1 - 4
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

+ 3 - 0
wiki_static/application_examples/example_ipc.gif

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:af329bb9df3fcf8d74084753c6ab4ef10707a3227cdb3e7224ca76a8e81145e4
+size 180549