flipperzero-firmware/wiki/examples/IPC-example.md
coreglitch 5b6ab7faf3
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
2020-09-01 13:34:23 +03:00

4.1 KiB
Raw Blame History

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.

#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:

    +==========+
    |          |
    |          |
    |          |
    +==========+
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:

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:

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) 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):

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":

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:

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:

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.