Просмотр исходного кода

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 лет назад
Родитель
Сommit
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_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
 };
 };

+ 13 - 13
applications/tests/furi_record_test.c

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

+ 3 - 0
core/flipper.h

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

+ 45 - 3
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,7 +95,16 @@ FuriRecordSubscriber* furi_open(
             printf("[FURI] cannot find record %s\n", name);
             printf("[FURI] cannot find record %s\n", name);
         #endif
         #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
     // allocate subscriber
@@ -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);

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

+ 1 - 1
core/log.c

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

+ 2 - 2
core/tty_uart.c

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

+ 11 - 0
target_f1/Makefile

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

+ 47 - 3
target_lo/Inc/cmsis_os.h

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

+ 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_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

+ 78 - 0
target_lo/Src/lo_os.c

@@ -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);
@@ -82,4 +85,79 @@ bool task_equal(TaskHandle_t a, TaskHandle_t b) {
 SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer) {
 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;
 }
 }

+ 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)
 * [Blink](Blink-app)
 * [UART write](UART-write)
 * [UART write](UART-write)
+* [Inter-process communication](IPC-example)
 
 
 # Hardware
 # 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.
 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 - 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