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_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
|
||||
};
|
@ -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");
|
||||
|
@ -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
|
||||
}
|
||||
|
48
core/furi.c
48
core/furi.c
@ -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);
|
||||
|
13
core/furi.h
13
core/furi.h
@ -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);
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
||||
|
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.
|
||||
|
||||
# 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
|
||||
|
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