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:
coreglitch 2020-09-01 16:34:23 +06:00 committed by GitHub
parent f7882dbff4
commit 5b6ab7faf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 538 additions and 33 deletions

158
applications/examples/ipc.c Normal file
View 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++;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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`.
![](https://github.com/Flipper-Zero/flipperzero-firmware-community/raw/master/wiki_static/application_examples/example_ipc.gif)

View File

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

View File

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