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:
parent
f7882dbff4
commit
5b6ab7faf3
158
applications/examples/ipc.c
Normal file
158
applications/examples/ipc.c
Normal 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++;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
};
|
};
|
@ -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");
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
48
core/furi.c
48
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);
|
||||||
|
13
core/furi.h
13
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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
@ -83,3 +86,78 @@ 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;
|
||||||
|
}
|
@ -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
wiki/examples/IPC-example.md
Normal file
145
wiki/examples/IPC-example.md
Normal 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)
|
@ -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
wiki_static/application_examples/example_ipc.gif
Normal file
3
wiki_static/application_examples/example_ipc.gif
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:af329bb9df3fcf8d74084753c6ab4ef10707a3227cdb3e7224ca76a8e81145e4
|
||||||
|
size 180549
|
Loading…
Reference in New Issue
Block a user