146 lines
4.1 KiB
Markdown
146 lines
4.1 KiB
Markdown
|
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)
|