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
16 changed files with 538 additions and 33 deletions

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