Furi (#24)
* furiac start and thread create implementation" * create and kill task * rename debug, add header * remove write.c * kill itself * furi exit/switch * success switch and exit * WIP furi records * add furi record interface * rename furi app control file * record implementation in progress * wip furi implementation * add automatic tests for FURI AC * differ build tests * small changes * FURI record tests description * change furi statuses * FURI record test blank * exit after all application ends * delay: print then wait * fix FURI implementatnion building * pipe record test * concurrent access * uncomplete mute-test * update FURI documentation
This commit is contained in:
parent
04035ce52d
commit
1759787334
81
applications/app_example/app_example.c
Normal file
81
applications/app_example/app_example.c
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "flipper.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
void furi_widget(void* param);
|
||||||
|
void furi_test_app(void* param);
|
||||||
|
void furi_next_test_app(void* param);
|
||||||
|
|
||||||
|
/*
|
||||||
|
widget simply print ping message
|
||||||
|
*/
|
||||||
|
void furi_widget(void* param) {
|
||||||
|
FILE* debug_uart = get_debug();
|
||||||
|
|
||||||
|
fprintf(debug_uart, "start furi widget: %s\n", (char*)param);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
fprintf(debug_uart, "furi widget\n");
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
it simply start, then start child widget, wait about 1 sec (with ping evey 200 ms),
|
||||||
|
kill the widget, continue with 500 ms ping.
|
||||||
|
*/
|
||||||
|
void furi_test_app(void* param) {
|
||||||
|
|
||||||
|
uint8_t cnt = 0;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
fprintf(debug_uart, "furi test app %d\n", cnt);
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
if(cnt == 2) {
|
||||||
|
fprintf(debug_uart, "go to next app\n");
|
||||||
|
furiac_switch(furi_next_test_app, "next_test", NULL);
|
||||||
|
fprintf(debug_uart, "unsuccessful switch\n");
|
||||||
|
while(1) {
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_next_test_app(void* param) {
|
||||||
|
FILE* debug_uart = get_debug();
|
||||||
|
|
||||||
|
fprintf(debug_uart, "start next test app\n");
|
||||||
|
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
fprintf(debug_uart, "exit next app\n");
|
||||||
|
furiac_exit(NULL);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
// this code must not be called
|
||||||
|
fprintf(debug_uart, "next app: something went wrong\n");
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FILE* debug_uart = get_debug();
|
||||||
|
|
||||||
|
fprintf(debug_uart, "hello Flipper!\n");
|
||||||
|
|
||||||
|
GpioPin red_led = {LED_RED_GPIO_Port, LED_RED_Pin};
|
||||||
|
|
||||||
|
app_gpio_init(red_led, GpioModeOutput);
|
||||||
|
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
delay(100);
|
||||||
|
app_gpio_write(red_led, true);
|
||||||
|
delay(100);
|
||||||
|
app_gpio_write(red_led, false);
|
||||||
|
}
|
||||||
|
*/
|
2
applications/app_example/app_example.h
Normal file
2
applications/app_example/app_example.h
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
void furi_test_app(void*);
|
13
applications/startup.h
Normal file
13
applications/startup.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "furi.h"
|
||||||
|
#include "tests/test_index.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FlipperApplication app;
|
||||||
|
const char* name;
|
||||||
|
} FlipperStartupApp;
|
||||||
|
|
||||||
|
const FlipperStartupApp FLIPPER_STARTUP[] = {
|
||||||
|
{.app = flipper_test_app, .name = "test app"}
|
||||||
|
};
|
455
applications/tests/furi_record_test.c
Normal file
455
applications/tests/furi_record_test.c
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "flipper.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
TEST: pipe record
|
||||||
|
|
||||||
|
1. create pipe record
|
||||||
|
2. Open/subscribe to it
|
||||||
|
3. write data
|
||||||
|
4. check that subscriber get data
|
||||||
|
5. try to read, get error
|
||||||
|
6. close record
|
||||||
|
7. try to write, get error
|
||||||
|
*/
|
||||||
|
|
||||||
|
static uint8_t pipe_record_value = 0;
|
||||||
|
|
||||||
|
void pipe_record_cb(const void* value, size_t size) {
|
||||||
|
// hold value to static var
|
||||||
|
pipe_record_value = *((uint8_t*)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_pipe_record(FILE* debug_uart) {
|
||||||
|
// 1. create pipe record
|
||||||
|
if(!furi_create("test/pipe", NULL, 0)) {
|
||||||
|
fprintf(debug_uart, "cannot create record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Open/subscribe to it
|
||||||
|
FuriRecordHandler pipe_record = furi_open(
|
||||||
|
"test/pipe", false, false, pipe_record_cb, NULL
|
||||||
|
);
|
||||||
|
if(pipe_record.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t WRITE_VALUE = 1;
|
||||||
|
// 3. write data
|
||||||
|
if(!furi_write(&pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "cannot write to record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. check that subscriber get data
|
||||||
|
if(pipe_record_value != WRITE_VALUE) {
|
||||||
|
fprintf(debug_uart, "wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. try to read, get error
|
||||||
|
uint8_t read_value = 0;
|
||||||
|
if(furi_read(&pipe_record, &read_value, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "reading from pipe record not allowed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. close record
|
||||||
|
furi_close(&pipe_record);
|
||||||
|
|
||||||
|
// 7. try to write, get error
|
||||||
|
if(furi_write(&pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "writing to closed record not allowed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TEST: holding data
|
||||||
|
|
||||||
|
1. Create holding record
|
||||||
|
2. Open/Subscribe on it
|
||||||
|
3. Write data
|
||||||
|
4. Check that subscriber get data
|
||||||
|
5. Read and check data
|
||||||
|
6. Try to write/read wrong size of data
|
||||||
|
*/
|
||||||
|
|
||||||
|
static uint8_t holding_record_value = 0;
|
||||||
|
|
||||||
|
void holding_record_cb(const void* value, size_t size) {
|
||||||
|
// hold value to static var
|
||||||
|
holding_record_value = *((uint8_t*)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_holding_data(FILE* debug_uart) {
|
||||||
|
// 1. Create holding record
|
||||||
|
uint8_t holder = 0;
|
||||||
|
if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
|
||||||
|
fprintf(debug_uart, "cannot create record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Open/Subscribe on it
|
||||||
|
FuriRecordHandler holding_record = furi_open(
|
||||||
|
"test/holding", false, false, holding_record_cb, NULL
|
||||||
|
);
|
||||||
|
if(holding_record.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t WRITE_VALUE = 1;
|
||||||
|
// 3. write data
|
||||||
|
if(!furi_write(&holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "cannot write to record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. check that subscriber get data
|
||||||
|
if(holding_record_value != WRITE_VALUE) {
|
||||||
|
fprintf(debug_uart, "wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Read and check data
|
||||||
|
uint8_t read_value = 0;
|
||||||
|
if(!furi_read(&holding_record, &read_value, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "cannot read from record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(read_value != WRITE_VALUE) {
|
||||||
|
fprintf(debug_uart, "wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Try to write/read wrong size of data
|
||||||
|
if(furi_write(&holding_record, &WRITE_VALUE, 100)) {
|
||||||
|
fprintf(debug_uart, "overflowed write not allowed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_read(&holding_record, &read_value, 100)) {
|
||||||
|
fprintf(debug_uart, "overflowed read not allowed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TEST: concurrent access
|
||||||
|
|
||||||
|
1. Create holding record
|
||||||
|
2. Open it twice
|
||||||
|
3. Change value simultaneously in two app and check integrity
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO this test broke because mutex in furi is not implemented
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// a and b must be equal
|
||||||
|
uint8_t a;
|
||||||
|
uint8_t b;
|
||||||
|
} ConcurrentValue;
|
||||||
|
|
||||||
|
void furi_concurent_app(void* p) {
|
||||||
|
FILE* debug_uart = (FILE*)p;
|
||||||
|
|
||||||
|
FuriRecordHandler holding_record = furi_open(
|
||||||
|
"test/concurrent", false, false, NULL, NULL
|
||||||
|
);
|
||||||
|
if(holding_record.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open record\n");
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < 10; i++) {
|
||||||
|
ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record);
|
||||||
|
|
||||||
|
if(value == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot take record\n");
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
// emulate read-modify-write broken by context switching
|
||||||
|
uint8_t a = value->a;
|
||||||
|
uint8_t b = value->b;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
delay(2); // this is only for test, do not add delay between take/give in prod!
|
||||||
|
value->a = a;
|
||||||
|
value->b = b;
|
||||||
|
furi_give(&holding_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_concurrent_access(FILE* debug_uart) {
|
||||||
|
// 1. Create holding record
|
||||||
|
ConcurrentValue holder = {.a = 0, .b = 0};
|
||||||
|
if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
|
||||||
|
fprintf(debug_uart, "cannot create record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Open it
|
||||||
|
FuriRecordHandler holding_record = furi_open(
|
||||||
|
"test/concurrent", false, false, NULL, NULL
|
||||||
|
);
|
||||||
|
if(holding_record.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create second app for interact with it
|
||||||
|
FuriApp* second_app = furiac_start(
|
||||||
|
furi_concurent_app, "furi concurent app", (void*)debug_uart
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. multiply ConcurrentValue::a
|
||||||
|
for(size_t i = 0; i < 4; i++) {
|
||||||
|
ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record);
|
||||||
|
|
||||||
|
if(value == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot take record\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// emulate read-modify-write broken by context switching
|
||||||
|
uint8_t a = value->a;
|
||||||
|
uint8_t b = value->b;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
value->a = a;
|
||||||
|
delay(10); // this is only for test, do not add delay between take/give in prod!
|
||||||
|
value->b = b;
|
||||||
|
furi_give(&holding_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(20);
|
||||||
|
|
||||||
|
if(second_app->handler != NULL) {
|
||||||
|
fprintf(debug_uart, "second app still alive\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(holder.a != holder.b) {
|
||||||
|
fprintf(debug_uart, "broken integrity: a=%d, b=%d\n", holder.a, holder.b);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TEST: non-existent data
|
||||||
|
1. Try to open non-existent record
|
||||||
|
2. Check for NULL handler
|
||||||
|
3. Try to write/read, get error
|
||||||
|
|
||||||
|
TODO: implement this test
|
||||||
|
*/
|
||||||
|
bool furi_nonexistent_data(FILE* debug_uart) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TEST: mute algorithm
|
||||||
|
1. Create "parent" application:
|
||||||
|
1. Create pipe record
|
||||||
|
2. Open watch handler: no_mute=false, solo=false, subscribe to data.
|
||||||
|
|
||||||
|
2. Open handler A: no_mute=false, solo=false, NULL subscriber. Subscribe to state.
|
||||||
|
Try to write data to A and check subscriber.
|
||||||
|
|
||||||
|
3. Open handler B: no_mute=true, solo=true, NULL subscriber.
|
||||||
|
Check A state cb get FlipperRecordStateMute.
|
||||||
|
Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
Try to write data to B and check that subscriber get data.
|
||||||
|
|
||||||
|
TODO: test 3 not pass beacuse state callback not implemented
|
||||||
|
|
||||||
|
4. Open hadler C: no_mute=false, solo=true, NULL subscriber.
|
||||||
|
Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
|
||||||
|
Try to write data to C and check that subscriber get data.
|
||||||
|
|
||||||
|
5. Open handler D: no_mute=false, solo=false, NULL subscriber.
|
||||||
|
Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
|
||||||
|
Try to write data to C and check that subscriber get data. (not muted because D open without solo)
|
||||||
|
Try to write data to D and check that subscriber get data.
|
||||||
|
|
||||||
|
6. Close C, close B.
|
||||||
|
Check A state cb get FlipperRecordStateUnmute
|
||||||
|
Try to write data to A and check that subscriber get data. (unmuted)
|
||||||
|
Try to write data to D and check that subscriber get data.
|
||||||
|
|
||||||
|
TODO: test 6 not pass beacuse cleanup is not implemented
|
||||||
|
TODO: test 6 not pass because mute algorithm is unfinished.
|
||||||
|
|
||||||
|
7. Exit "parent application"
|
||||||
|
Check A state cb get FlipperRecordStateDeleted
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// hold value to static var
|
||||||
|
mute_last_value = *((uint8_t*)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mute_record_state_cb(FlipperRecordState state) {
|
||||||
|
mute_last_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_mute_parent_app(void* p) {
|
||||||
|
FILE* debug_uart = (FILE*)p;
|
||||||
|
|
||||||
|
// 1. Create pipe record
|
||||||
|
if(!furi_create("test/mute", NULL, 0)) {
|
||||||
|
fprintf(debug_uart, "cannot create record\n");
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Open watch handler: solo=false, no_mute=false, subscribe to data
|
||||||
|
FuriRecordHandler watch_handler = furi_open(
|
||||||
|
"test/mute", false, false, mute_record_cb, NULL
|
||||||
|
);
|
||||||
|
if(watch_handler.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open watch handler\n");
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
// TODO we don't have thread sleep
|
||||||
|
delay(100000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_mute_algorithm(FILE* debug_uart) {
|
||||||
|
// 1. Create "parent" application:
|
||||||
|
FuriApp* parent_app = furiac_start(
|
||||||
|
furi_mute_parent_app, "parent app", (void*)debug_uart
|
||||||
|
);
|
||||||
|
|
||||||
|
delay(2); // wait creating record
|
||||||
|
|
||||||
|
// 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
|
||||||
|
FuriRecordHandler handler_a = furi_open(
|
||||||
|
"test/mute", false, false, NULL, mute_record_state_cb
|
||||||
|
);
|
||||||
|
if(handler_a.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open handler A\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t test_counter = 1;
|
||||||
|
|
||||||
|
// Try to write data to A and check subscriber
|
||||||
|
if(!furi_write(&handler_a, &test_counter, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "write to A failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mute_last_value != test_counter) {
|
||||||
|
fprintf(debug_uart, "value A mismatch: %d vs %d\n", mute_last_value, test_counter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
|
||||||
|
FuriRecordHandler handler_b = furi_open(
|
||||||
|
"test/mute", true, true, NULL, NULL
|
||||||
|
);
|
||||||
|
if(handler_b.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open handler B\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check A state cb get FlipperRecordStateMute.
|
||||||
|
if(mute_last_state != FlipperRecordStateMute) {
|
||||||
|
fprintf(debug_uart, "A state is not FlipperRecordStateMute: %d\n", mute_last_state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_counter = 2;
|
||||||
|
|
||||||
|
// Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
if(furi_write(&handler_a, &test_counter, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "A not muted\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mute_last_value == test_counter) {
|
||||||
|
fprintf(debug_uart, "value A must be muted\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_counter = 3;
|
||||||
|
|
||||||
|
|
||||||
|
// Try to write data to B and check that subscriber get data.
|
||||||
|
if(!furi_write(&handler_b, &test_counter, sizeof(uint8_t))) {
|
||||||
|
fprintf(debug_uart, "write to B failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mute_last_value != test_counter) {
|
||||||
|
fprintf(debug_uart, "value B mismatch: %d vs %d\n", mute_last_value, test_counter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
|
||||||
|
FuriRecordHandler handler_c = furi_open(
|
||||||
|
"test/mute", true, false, NULL, NULL
|
||||||
|
);
|
||||||
|
if(handler_c.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open handler C\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
// TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
|
||||||
|
// TODO: Try to write data to C and check that subscriber get data.
|
||||||
|
|
||||||
|
// 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
|
||||||
|
FuriRecordHandler handler_d = furi_open(
|
||||||
|
"test/mute", false, false, NULL, NULL
|
||||||
|
);
|
||||||
|
if(handler_d.record == NULL) {
|
||||||
|
fprintf(debug_uart, "cannot open handler D\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Try to write data to A and check that subscriber get no data. (muted)
|
||||||
|
// TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
|
||||||
|
// TODO: Try to write data to C and check that subscriber get data. (not muted because D open without solo)
|
||||||
|
// TODO: Try to write data to D and check that subscriber get data.
|
||||||
|
|
||||||
|
// 6. Close C, close B.
|
||||||
|
// TODO: Check A state cb get FlipperRecordStateUnmute
|
||||||
|
// TODO: Try to write data to A and check that subscriber get data. (unmuted)
|
||||||
|
// TODO: Try to write data to D and check that subscriber get data.
|
||||||
|
|
||||||
|
// 7. Exit "parent application"
|
||||||
|
if(!furiac_kill(parent_app)) {
|
||||||
|
fprintf(debug_uart, "kill parent_app fail\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check A state cb get FlipperRecordStateDeleted
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
130
applications/tests/furiac_test.c
Normal file
130
applications/tests/furiac_test.c
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "flipper.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test: creating and killing task
|
||||||
|
|
||||||
|
1. create task
|
||||||
|
2. delay 10 ms
|
||||||
|
3. kill task
|
||||||
|
4. check that value changes
|
||||||
|
5. delay 2 ms
|
||||||
|
6. check that value stay unchanged
|
||||||
|
*/
|
||||||
|
|
||||||
|
void create_kill_app(void* p) {
|
||||||
|
// this app simply increase counter
|
||||||
|
uint8_t* counter = (uint8_t*)p;
|
||||||
|
while(1) {
|
||||||
|
*counter = *counter + 1;
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_ac_create_kill(FILE* debug_uart) {
|
||||||
|
uint8_t counter = 0;
|
||||||
|
|
||||||
|
uint8_t value_a = counter;
|
||||||
|
|
||||||
|
FuriApp* widget = furiac_start(create_kill_app, "create_kill_app", (void*)&counter);
|
||||||
|
if(widget == NULL) {
|
||||||
|
fprintf(debug_uart, "create widget fail\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
if(!furiac_kill(widget)) {
|
||||||
|
fprintf(debug_uart, "kill widget fail\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value_a == counter) {
|
||||||
|
fprintf(debug_uart, "counter unchanged\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_a = counter;
|
||||||
|
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
if(value_a != counter) {
|
||||||
|
fprintf(debug_uart, "counter changes after kill (counter = %d vs %d)\n", value_a, counter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test: switch between tasks
|
||||||
|
1. init s
|
||||||
|
2. create task A, add 'A" to sequence'
|
||||||
|
3. switch to task B, add 'B' to sequence
|
||||||
|
4. exit from task B -> switch to A and add 'A' to sequence
|
||||||
|
5. cleanup: exit from task A
|
||||||
|
6. check sequence
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TEST_SWITCH_CONTEXT_SEQ_SIZE 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char sequence[TEST_SWITCH_CONTEXT_SEQ_SIZE];
|
||||||
|
size_t count;
|
||||||
|
} TestSwitchSequence;
|
||||||
|
|
||||||
|
void task_a(void*);
|
||||||
|
void task_b(void*);
|
||||||
|
|
||||||
|
void task_a(void *p) {
|
||||||
|
// simply starts, add 'A' letter to sequence and switch
|
||||||
|
// if sequence counter = 0, call task B, exit otherwise
|
||||||
|
|
||||||
|
TestSwitchSequence* seq = (TestSwitchSequence*)p;
|
||||||
|
|
||||||
|
seq->sequence[seq->count] = 'A';
|
||||||
|
seq->count++;
|
||||||
|
|
||||||
|
if(seq->count == 1) {
|
||||||
|
furiac_switch(task_b, "task B", p);
|
||||||
|
|
||||||
|
// if switch unsuccessfull, this code will executed
|
||||||
|
seq->sequence[seq->count] = 'x';
|
||||||
|
seq->count++;
|
||||||
|
} else {
|
||||||
|
// add '/' symbol on exit
|
||||||
|
seq->sequence[seq->count] = '/';
|
||||||
|
seq->count++;
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// application simply add 'B' end exit
|
||||||
|
void task_b(void* p) {
|
||||||
|
TestSwitchSequence* seq = (TestSwitchSequence*)p;
|
||||||
|
|
||||||
|
seq->sequence[seq->count] = 'B';
|
||||||
|
seq->count++;
|
||||||
|
|
||||||
|
furiac_exit(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_ac_switch_exit(FILE* debug_uart) {
|
||||||
|
// init sequence
|
||||||
|
TestSwitchSequence seq;
|
||||||
|
seq.count = 0;
|
||||||
|
|
||||||
|
furiac_start(task_a, "task A", (void*)&seq);
|
||||||
|
// TODO how to check that all child task ends?
|
||||||
|
|
||||||
|
delay(10); // wait while task do its work
|
||||||
|
|
||||||
|
if(strcmp(seq.sequence, "ABA/") != 0) {
|
||||||
|
fprintf(debug_uart, "wrong sequence: %s\n", seq.sequence);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
60
applications/tests/test_index.c
Normal file
60
applications/tests/test_index.c
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "flipper.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
bool furi_ac_create_kill(FILE* debug_uart);
|
||||||
|
bool furi_ac_switch_exit(FILE* debug_uart);
|
||||||
|
|
||||||
|
bool furi_pipe_record(FILE* debug_uart);
|
||||||
|
bool furi_holding_data(FILE* debug_uart);
|
||||||
|
bool furi_concurrent_access(FILE* debug_uart);
|
||||||
|
bool furi_nonexistent_data(FILE* debug_uart);
|
||||||
|
bool furi_mute_algorithm(FILE* debug_uart);
|
||||||
|
|
||||||
|
void flipper_test_app(void* p) {
|
||||||
|
FILE* debug_uart = get_debug();
|
||||||
|
|
||||||
|
if(furi_ac_create_kill(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_ac_create_kill PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_ac_create_kill FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_ac_switch_exit(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_ac_switch_exit PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_ac_switch_exit FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_pipe_record(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_pipe_record PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_pipe_record FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_holding_data(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_holding_data PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_holding_data FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_concurrent_access(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_concurrent_access PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_concurrent_access FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_nonexistent_data(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_nonexistent_data PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_nonexistent_data FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(furi_mute_algorithm(debug_uart)) {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_mute_algorithm PASSED\n");
|
||||||
|
} else {
|
||||||
|
fprintf(debug_uart, "[TEST] furi_mute_algorithm FAILED\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
furiac_exit(NULL);
|
||||||
|
}
|
2
applications/tests/test_index.h
Normal file
2
applications/tests/test_index.h
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
void flipper_test_app(void* p);
|
31
core/app.cpp
31
core/app.cpp
@ -2,23 +2,28 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
FILE* get_debug();
|
#include "startup.h"
|
||||||
|
#include "furi.h"
|
||||||
|
#include "debug.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void app() {
|
extern "C" void app() {
|
||||||
FILE* debug_uart = get_debug();
|
// FURI startup
|
||||||
|
FuriApp* handlers[sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0])];
|
||||||
|
|
||||||
fprintf(debug_uart, "hello Flipper!\n");
|
for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
|
||||||
|
handlers[i] = furiac_start(FLIPPER_STARTUP[i].app, FLIPPER_STARTUP[i].name, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
GpioPin red_led = {LED_RED_GPIO_Port, LED_RED_Pin};
|
bool is_alive = false;
|
||||||
|
do {
|
||||||
app_gpio_init(red_led, GpioModeOutput);
|
is_alive = false;
|
||||||
|
for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
|
||||||
|
if(handlers[i]->handler != NULL) {
|
||||||
while(1) {
|
is_alive = true;
|
||||||
delay(100);
|
|
||||||
app_gpio_write(red_led, true);
|
|
||||||
delay(100);
|
|
||||||
app_gpio_write(red_led, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delay(500);
|
||||||
|
// TODO add deferred event queue here
|
||||||
|
} while(is_alive);
|
||||||
|
}
|
1
core/debug.h
Normal file
1
core/debug.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
FILE* get_debug();
|
@ -1,8 +1,15 @@
|
|||||||
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "flipper_hal.h"
|
#include "flipper_hal.h"
|
||||||
#include "cmsis_os.h"
|
#include "cmsis_os.h"
|
||||||
|
#include "furi.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Arduino defines
|
// Arduino defines
|
||||||
|
|
||||||
|
216
core/furi.c
216
core/furi.c
@ -0,0 +1,216 @@
|
|||||||
|
#include "furi.h"
|
||||||
|
#include "cmsis_os.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_RECORD_COUNT 32
|
||||||
|
|
||||||
|
static FuriRecord records[MAX_RECORD_COUNT];
|
||||||
|
static size_t current_buffer_idx = 0;
|
||||||
|
|
||||||
|
// find record pointer by name
|
||||||
|
static FuriRecord* find_record(const char* name) {
|
||||||
|
if(name == NULL) return NULL;
|
||||||
|
|
||||||
|
FuriRecord* res = NULL;
|
||||||
|
for(size_t i = 0; i < MAX_RECORD_COUNT; i++) {
|
||||||
|
if(records[i].name != NULL && strcmp(name, records[i].name) == 0) {
|
||||||
|
res = &records[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_create(const char* name, void* value, size_t size) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] creating %s record\n", name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(current_buffer_idx >= MAX_RECORD_COUNT) {
|
||||||
|
// max record count exceed
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] max record count exceed\n");
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
records[current_buffer_idx].mute_counter = 0;
|
||||||
|
records[current_buffer_idx].mutex = xSemaphoreCreateMutexStatic(
|
||||||
|
&records[current_buffer_idx].mutex_buffer
|
||||||
|
);
|
||||||
|
records[current_buffer_idx].value = value;
|
||||||
|
records[current_buffer_idx].size = size;
|
||||||
|
records[current_buffer_idx].name = name;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
|
||||||
|
records[current_buffer_idx].subscribers[i].allocated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriRecordHandler furi_open(
|
||||||
|
const char* name,
|
||||||
|
bool solo,
|
||||||
|
bool no_mute,
|
||||||
|
FlipperRecordCallback value_callback,
|
||||||
|
FlipperRecordStateCallback state_callback
|
||||||
|
) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] opening %s record\n", name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get furi record by name
|
||||||
|
FuriRecord* record = find_record(name);
|
||||||
|
|
||||||
|
if(record == NULL) {
|
||||||
|
// cannot find record
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] cannot find record %s\n", name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate subscriber
|
||||||
|
FuriRecordSubscriber* subscriber = NULL;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
|
||||||
|
if(!records[current_buffer_idx].subscribers[i].allocated) {
|
||||||
|
subscriber = &records[current_buffer_idx].subscribers[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscriber == NULL) {
|
||||||
|
// cannot add subscriber (full)
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] cannot add subscriber (full)\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase mute_counter
|
||||||
|
if(solo) {
|
||||||
|
record->mute_counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set all parameters
|
||||||
|
subscriber->allocated = true;
|
||||||
|
subscriber->mute_counter = record->mute_counter;
|
||||||
|
subscriber->no_mute = no_mute;
|
||||||
|
subscriber->cb = value_callback;
|
||||||
|
subscriber->state_cb = state_callback;
|
||||||
|
|
||||||
|
// register record in application
|
||||||
|
FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
|
||||||
|
|
||||||
|
current_task->records[current_task->records_count] = record;
|
||||||
|
current_task->records_count++;
|
||||||
|
|
||||||
|
FuriRecordHandler res = {.record = record, .subscriber = subscriber};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void furi_close(FuriRecordHandler* handler) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] closing %s record\n", handler->record->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// deallocate subscriber
|
||||||
|
handler->subscriber->allocated = false;
|
||||||
|
|
||||||
|
// set mute counter to next max value
|
||||||
|
uint8_t max_mute_counter = 0;
|
||||||
|
for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
|
||||||
|
if(handler->record->subscribers[i].allocated) {
|
||||||
|
if(handler->record->subscribers[i].mute_counter > max_mute_counter) {
|
||||||
|
max_mute_counter = handler->record->subscribers[i].mute_counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler->record->mute_counter = max_mute_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void furi_notify(FuriRecordHandler* handler, const void* value, size_t size) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* furi_take(FuriRecordHandler* handler) {
|
||||||
|
// take mutex
|
||||||
|
|
||||||
|
return handler->record->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void furi_give(FuriRecordHandler* handler) {
|
||||||
|
// release mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_read(FuriRecordHandler* handler, void* value, size_t size) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] read from %s\n", handler->record->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(handler == NULL || handler->record == NULL || value == NULL) return false;
|
||||||
|
|
||||||
|
if(size > handler->record->size) return false;
|
||||||
|
|
||||||
|
// return false if read from pipe
|
||||||
|
if(handler->record->value == NULL) return false;
|
||||||
|
|
||||||
|
furi_take(handler);
|
||||||
|
memcpy(value, handler->record->value, size);
|
||||||
|
furi_give(handler);
|
||||||
|
furi_notify(handler, value, size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_write(FuriRecordHandler* handler, const void* value, size_t size) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURI] write to %s\n", handler->record->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(handler == NULL || handler->record == NULL || value == NULL) return false;
|
||||||
|
|
||||||
|
// check if closed
|
||||||
|
if(!handler->subscriber->allocated) return false;
|
||||||
|
|
||||||
|
if(handler->record->value != NULL && size > handler->record->size) return false;
|
||||||
|
|
||||||
|
// check mute
|
||||||
|
if(
|
||||||
|
handler->record->mute_counter != handler->subscriber->mute_counter
|
||||||
|
&& !handler->subscriber->no_mute
|
||||||
|
) return false;
|
||||||
|
|
||||||
|
if(handler->record->value != NULL) {
|
||||||
|
// real write to value
|
||||||
|
furi_take(handler);
|
||||||
|
memcpy(handler->record->value, value, size);
|
||||||
|
furi_give(handler);
|
||||||
|
|
||||||
|
// notify subscribers
|
||||||
|
furi_notify(handler, handler->record->value, handler->record->size);
|
||||||
|
} else {
|
||||||
|
furi_notify(handler, value, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
158
core/furi.h
158
core/furi.h
@ -0,0 +1,158 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cmsis_os.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MAX_TASK_RECORDS 8
|
||||||
|
#define MAX_RECORD_SUBSCRIBERS 8
|
||||||
|
|
||||||
|
/// application is just a function
|
||||||
|
typedef void(*FlipperApplication)(void*);
|
||||||
|
|
||||||
|
/// pointer to value callback function
|
||||||
|
typedef void(*FlipperRecordCallback)(const void*, size_t);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FlipperRecordStateMute, ///< record open and mute this handler
|
||||||
|
FlipperRecordStateUnmute, ///< record unmuted
|
||||||
|
FlipperRecordStateDeleted ///< record owner halt
|
||||||
|
} FlipperRecordState;
|
||||||
|
|
||||||
|
/// pointer to state callback function
|
||||||
|
typedef void(*FlipperRecordStateCallback)(FlipperRecordState);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool allocated;
|
||||||
|
FlipperRecordCallback cb; ///< value cb
|
||||||
|
FlipperRecordStateCallback state_cb; ///< state cb
|
||||||
|
uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
|
||||||
|
bool no_mute;
|
||||||
|
} FuriRecordSubscriber;
|
||||||
|
|
||||||
|
/// FURI record handler
|
||||||
|
typedef struct {
|
||||||
|
const char* name;
|
||||||
|
void* value;
|
||||||
|
size_t size;
|
||||||
|
StaticSemaphore_t mutex_buffer;
|
||||||
|
SemaphoreHandle_t mutex;
|
||||||
|
uint8_t mute_counter;
|
||||||
|
FuriRecordSubscriber subscribers[MAX_RECORD_SUBSCRIBERS];
|
||||||
|
} FuriRecord;
|
||||||
|
|
||||||
|
/// FURI record handler for use after open
|
||||||
|
typedef struct {
|
||||||
|
FuriRecord* record; ///< full record (for read/write/take/give value)
|
||||||
|
FuriRecordSubscriber* subscriber; ///< current handler info
|
||||||
|
} FuriRecordHandler;
|
||||||
|
|
||||||
|
/// store info about active task
|
||||||
|
typedef struct {
|
||||||
|
const char* name;
|
||||||
|
FlipperApplication application;
|
||||||
|
const char* prev_name;
|
||||||
|
FlipperApplication prev;
|
||||||
|
TaskHandle_t handler;
|
||||||
|
uint8_t records_count; ///< count of records which task open
|
||||||
|
FuriRecord* records[MAX_TASK_RECORDS]; ///< list of records which task open
|
||||||
|
} FuriApp;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Simply starts application.
|
||||||
|
It call app entrypoint with param passed as argument.
|
||||||
|
Useful for daemon applications and pop-up.
|
||||||
|
*/
|
||||||
|
FuriApp* furiac_start(FlipperApplication app, const char* name, void* param);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Swtich to other application.
|
||||||
|
FURI stop current app, call app entrypoint with param passed as
|
||||||
|
argument and save current application entrypoint to prev field
|
||||||
|
in current application registry.
|
||||||
|
Useful for UI or "active" application.
|
||||||
|
*/
|
||||||
|
void furiac_switch(FlipperApplication app, char* name, void* param);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Stop current application
|
||||||
|
(stop thread and clear application's stack), start application
|
||||||
|
from prev entry in current application registry, cleanup current
|
||||||
|
application registry.
|
||||||
|
*/
|
||||||
|
void furiac_exit(void* param);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Stop specified app without returning to prev application.
|
||||||
|
*/
|
||||||
|
bool furiac_kill(FuriApp* app);
|
||||||
|
|
||||||
|
// find task pointer by handle
|
||||||
|
FuriApp* find_task(TaskHandle_t handler);
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates named FURI record.
|
||||||
|
\param[in] name you can open this record anywhere
|
||||||
|
\param[in] value pointer to data.
|
||||||
|
\param[in] size size of data.
|
||||||
|
If NULL, create FURI Pipe (only callbacks management, no data/mutex)
|
||||||
|
|
||||||
|
Returns false if registry have not enough memory for creating.
|
||||||
|
*/
|
||||||
|
bool furi_create(const char* name, void* value, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Opens existing FURI record by name.
|
||||||
|
Returns NULL if record does not exist.
|
||||||
|
\param[in] solo if true another applications handlers set into "muted" state.
|
||||||
|
When appication has exited or record has closed, all handlers is unmuted.
|
||||||
|
It may be useful for concurrently acces to resources like framebuffer or beeper.
|
||||||
|
\param[in] no_mute if true, another applications cannot mute this handler.
|
||||||
|
*/
|
||||||
|
FuriRecordHandler furi_open(
|
||||||
|
const char* name,
|
||||||
|
bool solo,
|
||||||
|
bool no_mute,
|
||||||
|
FlipperRecordCallback value_callback,
|
||||||
|
FlipperRecordStateCallback state_callback
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
|
||||||
|
*/
|
||||||
|
void furi_close(FuriRecordHandler* handler);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
read message from record.
|
||||||
|
Returns true if success, false otherwise (closed/non-existent record)
|
||||||
|
Also return false if you try to read from FURI pipe
|
||||||
|
|
||||||
|
TODO: enum return value with execution status
|
||||||
|
*/
|
||||||
|
bool furi_read(FuriRecordHandler* record, void* data, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
write message to record.
|
||||||
|
Returns true if success, false otherwise (closed/non-existent record or muted).
|
||||||
|
|
||||||
|
TODO: enum return value with execution status
|
||||||
|
*/
|
||||||
|
bool furi_write(FuriRecordHandler* record, const void* data, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
lock value mutex.
|
||||||
|
It can be useful if records contain pointer to buffer which you want to change.
|
||||||
|
You must call furi_give after operation on data and
|
||||||
|
you shouldn't block executing between take and give calls
|
||||||
|
|
||||||
|
Returns pointer to data, NULL if closed/non-existent record or muted
|
||||||
|
|
||||||
|
TODO: enum return value with execution status
|
||||||
|
*/
|
||||||
|
void* furi_take(FuriRecordHandler* record);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
unlock value mutex.
|
||||||
|
*/
|
||||||
|
void furi_give(FuriRecordHandler* record);
|
139
core/furi_ac.c
Normal file
139
core/furi_ac.c
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
#include "furi.h"
|
||||||
|
#include "cmsis_os.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_STACK_SIZE 1024 // Stack size in bytes
|
||||||
|
#define MAX_TASK_COUNT 8
|
||||||
|
|
||||||
|
static StaticTask_t task_info_buffer[MAX_TASK_COUNT];
|
||||||
|
static StackType_t stack_buffer[MAX_TASK_COUNT][DEFAULT_STACK_SIZE / 4];
|
||||||
|
static FuriApp task_buffer[MAX_TASK_COUNT];
|
||||||
|
|
||||||
|
static size_t current_buffer_idx = 0;
|
||||||
|
|
||||||
|
// find task pointer by handle
|
||||||
|
FuriApp* find_task(TaskHandle_t handler) {
|
||||||
|
FuriApp* res = NULL;
|
||||||
|
for(size_t i = 0; i < MAX_TASK_COUNT; i++) {
|
||||||
|
if(task_equal(task_buffer[i].handler, handler)) {
|
||||||
|
res = &task_buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriApp* furiac_start(FlipperApplication app, const char* name, void* param) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] start %s\n", name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TODO check first free item (.handler == NULL) and use it
|
||||||
|
|
||||||
|
if(current_buffer_idx >= MAX_TASK_COUNT) {
|
||||||
|
// max task count exceed
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] max task count exceed\n");
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create task on static stack memory
|
||||||
|
task_buffer[current_buffer_idx].handler = xTaskCreateStatic(
|
||||||
|
(TaskFunction_t)app,
|
||||||
|
(const char * const)name,
|
||||||
|
DEFAULT_STACK_SIZE / 4, // freertos specify stack size in words
|
||||||
|
(void * const) param,
|
||||||
|
tskIDLE_PRIORITY + 3, // normal priority
|
||||||
|
stack_buffer[current_buffer_idx],
|
||||||
|
&task_info_buffer[current_buffer_idx]
|
||||||
|
);
|
||||||
|
|
||||||
|
// save task
|
||||||
|
task_buffer[current_buffer_idx].application = app;
|
||||||
|
task_buffer[current_buffer_idx].prev_name = NULL;
|
||||||
|
task_buffer[current_buffer_idx].prev = NULL;
|
||||||
|
task_buffer[current_buffer_idx].records_count = 0;
|
||||||
|
task_buffer[current_buffer_idx].name = name;
|
||||||
|
|
||||||
|
current_buffer_idx++;
|
||||||
|
|
||||||
|
return &task_buffer[current_buffer_idx - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furiac_kill(FuriApp* app) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] kill %s\n", app->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// check handler
|
||||||
|
if(app == NULL || app->handler == NULL) return false;
|
||||||
|
|
||||||
|
// kill task
|
||||||
|
vTaskDelete(app->handler);
|
||||||
|
|
||||||
|
// cleanup its registry
|
||||||
|
// TODO realy free memory
|
||||||
|
app->handler = NULL;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void furiac_exit(void* param) {
|
||||||
|
// get current task handler
|
||||||
|
FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
|
||||||
|
|
||||||
|
// run prev
|
||||||
|
if(current_task != NULL) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] exit %s\n", current_task->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(current_task->prev != NULL) {
|
||||||
|
furiac_start(current_task->prev, current_task->prev_name, param);
|
||||||
|
} else {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] no prev\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup registry
|
||||||
|
// TODO realy free memory
|
||||||
|
current_task->handler = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill itself
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void furiac_switch(FlipperApplication app, char* name, void* param) {
|
||||||
|
// get current task handler
|
||||||
|
FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
|
||||||
|
|
||||||
|
if(current_task == NULL) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] no current task found\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[FURIAC] switch %s to %s\n", current_task->name, name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// run next
|
||||||
|
FuriApp* next = furiac_start(app, name, param);
|
||||||
|
|
||||||
|
if(next != NULL) {
|
||||||
|
// save current application pointer as prev
|
||||||
|
next->prev = current_task->application;
|
||||||
|
next->prev_name = current_task->name;
|
||||||
|
|
||||||
|
// kill itself
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
}
|
@ -154,7 +154,8 @@ AS_INCLUDES = \
|
|||||||
# C includes
|
# C includes
|
||||||
C_INCLUDES = \
|
C_INCLUDES = \
|
||||||
-IInc \
|
-IInc \
|
||||||
-I../app \
|
-I../applications \
|
||||||
|
-I../core \
|
||||||
-IDrivers/STM32L4xx_HAL_Driver/Inc \
|
-IDrivers/STM32L4xx_HAL_Driver/Inc \
|
||||||
-IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \
|
-IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \
|
||||||
-IMiddlewares/Third_Party/FreeRTOS/Source/include \
|
-IMiddlewares/Third_Party/FreeRTOS/Source/include \
|
||||||
|
@ -1,3 +1,30 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
void osDelay(uint32_t ms);
|
void osDelay(uint32_t ms);
|
||||||
|
|
||||||
|
// some FreeRTOS types
|
||||||
|
typedef void(*TaskFunction_t)(void*);
|
||||||
|
typedef uint32_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;
|
||||||
|
|
||||||
|
#define tskIDLE_PRIORITY 0
|
||||||
|
|
||||||
|
TaskHandle_t xTaskCreateStatic(
|
||||||
|
TaskFunction_t pxTaskCode,
|
||||||
|
const char * const pcName,
|
||||||
|
const uint32_t ulStackDepth,
|
||||||
|
void * const pvParameters,
|
||||||
|
UBaseType_t uxPriority,
|
||||||
|
StackType_t * const puxStackBuffer,
|
||||||
|
StaticTask_t * const pxTaskBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
void vTaskDelete(TaskHandle_t xTask);
|
||||||
|
TaskHandle_t xTaskGetCurrentTaskHandle(void);
|
||||||
|
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer);
|
||||||
|
bool task_equal(TaskHandle_t a, TaskHandle_t b);
|
||||||
|
@ -25,11 +25,17 @@ Src/main.c
|
|||||||
|
|
||||||
CPP_SOURCES = ../core/app.cpp
|
CPP_SOURCES = ../core/app.cpp
|
||||||
|
|
||||||
C_SOURCES += ../core/write.c
|
C_SOURCES += ../core/debug.c
|
||||||
|
C_SOURCES += ../core/furi.c
|
||||||
|
C_SOURCES += ../core/furi_ac.c
|
||||||
C_SOURCES += Src/flipper_hal.c
|
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_SOURCES += ../applications/tests/furiac_test.c
|
||||||
|
C_SOURCES += ../applications/tests/furi_record_test.c
|
||||||
|
C_SOURCES += ../applications/tests/test_index.c
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# binaries
|
# binaries
|
||||||
#######################################
|
#######################################
|
||||||
@ -56,10 +62,11 @@ C_DEFS = \
|
|||||||
# C includes
|
# C includes
|
||||||
C_INCLUDES = \
|
C_INCLUDES = \
|
||||||
-IInc \
|
-IInc \
|
||||||
-I../app
|
-I../applications \
|
||||||
|
-I../core
|
||||||
|
|
||||||
# compile gcc flags
|
# compile gcc flags
|
||||||
CFLAGS = $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
|
CFLAGS = $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -pthread
|
||||||
|
|
||||||
ifeq ($(DEBUG), 1)
|
ifeq ($(DEBUG), 1)
|
||||||
CFLAGS += -g -gdwarf-2
|
CFLAGS += -g -gdwarf-2
|
||||||
@ -78,7 +85,7 @@ CPPFLAGS = -fno-threadsafe-statics
|
|||||||
# libraries
|
# libraries
|
||||||
LIBS = -lc -lm
|
LIBS = -lc -lm
|
||||||
LIBDIR =
|
LIBDIR =
|
||||||
LDFLAGS = $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
|
LDFLAGS = $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -pthread
|
||||||
|
|
||||||
# default action: build all
|
# default action: build all
|
||||||
all: $(BUILD_DIR)/$(TARGET)
|
all: $(BUILD_DIR)/$(TARGET)
|
||||||
|
@ -1,8 +1,85 @@
|
|||||||
#include "cmsis_os.h"
|
#include "cmsis_os.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
void osDelay(uint32_t ms) {
|
void osDelay(uint32_t ms) {
|
||||||
usleep(ms * 1000);
|
|
||||||
printf("[DELAY] %d ms\n", ms);
|
printf("[DELAY] %d ms\n", ms);
|
||||||
|
usleep(ms * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporary struct to pass function ptr and param to wrapper
|
||||||
|
typedef struct {
|
||||||
|
TaskFunction_t func;
|
||||||
|
void * param;
|
||||||
|
} PthreadTask;
|
||||||
|
|
||||||
|
void* pthread_wrapper(void* p) {
|
||||||
|
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0x00);
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0x00);
|
||||||
|
|
||||||
|
PthreadTask* task = (PthreadTask*)p;
|
||||||
|
|
||||||
|
task->func(task->param);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskHandle_t xTaskCreateStatic(
|
||||||
|
TaskFunction_t pxTaskCode,
|
||||||
|
const char * const pcName,
|
||||||
|
const uint32_t ulStackDepth,
|
||||||
|
void * const pvParameters,
|
||||||
|
UBaseType_t uxPriority,
|
||||||
|
StackType_t * const puxStackBuffer,
|
||||||
|
StaticTask_t * const pxTaskBuffer
|
||||||
|
) {
|
||||||
|
TaskHandle_t thread = malloc(sizeof(TaskHandle_t));
|
||||||
|
PthreadTask* task = malloc(sizeof(PthreadTask));
|
||||||
|
|
||||||
|
task->func = pxTaskCode;
|
||||||
|
task->param = pvParameters;
|
||||||
|
|
||||||
|
pthread_create(thread, NULL, pthread_wrapper, (void*)task);
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vTaskDelete(TaskHandle_t xTask) {
|
||||||
|
|
||||||
|
if(xTask == NULL) {
|
||||||
|
// kill itself
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe thread already join
|
||||||
|
if (pthread_kill(*xTask, 0) == ESRCH) return;
|
||||||
|
|
||||||
|
// send thread_child signal to stop it сигнал, который ее завершает
|
||||||
|
pthread_cancel(*xTask);
|
||||||
|
|
||||||
|
// wait for join and close descriptor
|
||||||
|
pthread_join(*xTask, 0x00);
|
||||||
|
|
||||||
|
// cleanup thread handler
|
||||||
|
*xTask = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskHandle_t xTaskGetCurrentTaskHandle(void) {
|
||||||
|
TaskHandle_t thread = malloc(sizeof(TaskHandle_t));
|
||||||
|
*thread = pthread_self();
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool task_equal(TaskHandle_t a, TaskHandle_t b) {
|
||||||
|
if(a == NULL || b == NULL) return false;
|
||||||
|
|
||||||
|
return pthread_equal(*a, *b) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer) {
|
||||||
|
// TODO add posix mutex init
|
||||||
|
return NULL;
|
||||||
}
|
}
|
@ -8,29 +8,55 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f
|
|||||||
|
|
||||||
### Start and change application wrokflow
|
### Start and change application wrokflow
|
||||||
|
|
||||||
* `FuriApp furiac_start(void(app*)(void*), char* name, void* param)` simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
|
**`FuriApp* furiac_start(void(app*)(void*), char* name, void* param)`**
|
||||||
* `FuriApp furiac_switch(void(app*)(void*), char* name, void* param)` swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
|
|
||||||
|
simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
|
||||||
|
|
||||||
|
|
||||||
|
**`FuriApp furiac_switch(void(app*)(void*), char* name, void* param)`**
|
||||||
|
|
||||||
|
swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
|
||||||
|
|
||||||
### Exit application
|
### Exit application
|
||||||
|
|
||||||
* `furiac_exit(void* param)` stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
|
**`void furiac_exit(void* param)`**
|
||||||
* `furiac_kill(FuriApp app)` stop specified `app` without returning to `prev` application.
|
|
||||||
|
stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
|
||||||
|
|
||||||
|
|
||||||
|
**`bool furiac_kill(FuriApp app)`**
|
||||||
|
|
||||||
|
stop specified `app` without returning to `prev` application.
|
||||||
|
|
||||||
# Data exchange
|
# Data exchange
|
||||||
|
|
||||||
* `FuriRecord furi_create(char* name)` creates named FURI record. Returns NULL if registry have not enough memory for creating.
|
**`bool furi_create(char* name, void* value, size_t size)`**
|
||||||
* `FuriRecord furi_open(char* name, bool solo, bool no_mute)` opens existing FURI record by name. Returns NULL if record does not exist. If `solo` is true **another applications handlers set into "muted" state**. When appication has exited or record has closed, all handlers is unmuted. It may be useful for concurrently acces to resources like framebuffer or beeper. If `no_mute` is true, another applications cannot mute this handler.
|
|
||||||
* `bool furi_close(FuriRecord record)` close handler and unmute anothers.
|
creates named FURI record. Returns NULL if registry have not enough memory for creating. If value is NULL, create FURI Pipe (only callbacks management, no data/mutex).
|
||||||
* `bool furi_read(FuriRecord record, void* data, size_t size)` read message from record. Returns true if success, false otherwise.
|
|
||||||
* `bool furi_write(FuriRecord record, const void* data, size_t size)` write message to record. Returns true if success, false otherwise (handler gone or muted).
|
**`FuriRecordHandler furi_open(char* name, bool solo, bool no_mute, void(*FlipperRecordCallback)(const void*, size_t), void(*FlipperRecordStateCallback)(FlipperRecordState))`**
|
||||||
* `bool furi_take(FuriRecord record, void* data, size_t size)` works as `furi_read` but lock global mutex. It can be useful if records contain pointer to buffer which you want to change. You must call `furi_give` after operation on data and you cannot block executing between `take` and `give` calls
|
|
||||||
* `bool furi_give(FuriRecord record, const void* data, size_t size)` works as `furi_wrte` but unlock global mutex.
|
opens existing FURI record by name. Returns NULL if record does not exist. If `solo` is true **another applications handlers set into "muted" state**. When appication has exited or record has closed, all handlers is unmuted. It may be useful for concurrently acces to resources like framebuffer or beeper. If `no_mute` is true, another applications cannot mute this handler.
|
||||||
* `bool furi_global_take()` lock global mutex (as `furi_take` but without read)
|
|
||||||
* `boold furi_global_give()` unlock global mutex ((as `furi_give` but without write))
|
**`bool furi_close(FuriRecordHandler* record)`**
|
||||||
* `bool furi_unmute(FuriRecord record)` unmutes muted record.
|
|
||||||
* `bool furi_mute(FuriRecord record)` mutes unmuted record.
|
close handler and unmute anothers.
|
||||||
* `bool furi_subscribe(FuriRecord record, void(cb*)(const void* data, size_t size))` set record change callback.
|
|
||||||
* `bool furi_state_subscribe(FuriRecord record, void(cb*)(bool muted))` set record state change callback (mute/unmute). For example, you can unmute itself after some application open same record, or redraw your application UI when popup application ends.
|
**`bool furi_read(FuriRecordHandler* record, void* data, size_t size)`**
|
||||||
|
|
||||||
|
read message from record. Returns true if success, false otherwise.
|
||||||
|
|
||||||
|
**`bool furi_write(FuriRecordHandler* record, const void* data, size_t size)`**
|
||||||
|
|
||||||
|
write message to record. Returns true if success, false otherwise (handler gone or muted).
|
||||||
|
|
||||||
|
**`void* furi_take(FuriRecordHandler* record)` works as `furi_read`**
|
||||||
|
|
||||||
|
lock value mutex. It can be useful if records contain pointer to buffer which you want to change. You must call `furi_give` after operation on data and you cannot block executing between `take` and `give` calls
|
||||||
|
|
||||||
|
**`bool furi_give(FuriRecordHandler* record)`**
|
||||||
|
|
||||||
|
unlock value mutex works as `furi_wrte` but unlock global mutex.
|
||||||
|
|
||||||
# Usage example
|
# Usage example
|
||||||
_Diagram below describes furi states_
|
_Diagram below describes furi states_
|
||||||
@ -44,11 +70,5 @@ _Diagram below describes furi states_
|
|||||||
* If "Your cool app" needs some backend app, it call this by `furiac_start` and then kill by `furiac_kill`
|
* If "Your cool app" needs some backend app, it call this by `furiac_start` and then kill by `furiac_kill`
|
||||||
* If background task needs to show popup message (for example "Low battery") it can call new app or simply open "/ui/fb" record.
|
* If background task needs to show popup message (for example "Low battery") it can call new app or simply open "/ui/fb" record.
|
||||||
* When "/ui/fb" record is opened by popup message, FURI mute framebuffer handle in "Your cool app". This prevent to overwrite popup message by application drawing.
|
* When "/ui/fb" record is opened by popup message, FURI mute framebuffer handle in "Your cool app". This prevent to overwrite popup message by application drawing.
|
||||||
* "Status bar" framebuffer handle also is muted, but it call `furi_unmute` and unmute itself.
|
* "Status bar" framebuffer handle not is muted, beacuse open framebuffer with no_mute=true.
|
||||||
* After popup message is closed by `furiac_exit` or closing "/ui/fb", FURI unmute previous muted "Your cool app" framebuffer handle.
|
* After popup message is closed by `furiac_exit` or closing "/ui/fb", FURI unmute previous muted "Your cool app" framebuffer handle.
|
||||||
|
|
||||||
_Status bar also get mute and unmute itself every time when Home screen, Menu or "Your cool app" open framebuffer but diagramm not show it_
|
|
||||||
|
|
||||||
# Data storage
|
|
||||||
|
|
||||||
* `furi_create_var(char* name)` create static-like value handler. You can use all furi_ calls for
|
|
Loading…
Reference in New Issue
Block a user