* 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:
coreglitch 2020-08-24 21:31:22 +06:00 committed by GitHub
parent 04035ce52d
commit 1759787334
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1448 additions and 47 deletions

View 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);
}
*/

View File

@ -0,0 +1,2 @@
void furi_test_app(void*);

13
applications/startup.h Normal file
View 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"}
};

View 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;
}

View 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;
}

View 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);
}

View File

@ -0,0 +1,2 @@
void flipper_test_app(void* p);

View File

@ -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};
app_gpio_init(red_led, GpioModeOutput);
while(1) {
delay(100);
app_gpio_write(red_led, true);
delay(100);
app_gpio_write(red_led, false);
} }
bool is_alive = false;
do {
is_alive = false;
for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
if(handlers[i]->handler != NULL) {
is_alive = true;
}
}
delay(500);
// TODO add deferred event queue here
} while(is_alive);
} }

View File

@ -18,7 +18,7 @@ ssize_t uart_write(void* cookie, const char * buffer, size_t size) {
} }
FILE* get_debug() { FILE* get_debug() {
FILE* fp = fopencookie(NULL,"w+", (cookie_io_functions_t){ FILE* fp = fopencookie(NULL, "w+", (cookie_io_functions_t){
.read = NULL, .read = NULL,
.write = uart_write, .write = uart_write,
.seek = NULL, .seek = NULL,

1
core/debug.h Normal file
View File

@ -0,0 +1 @@
FILE* get_debug();

View File

@ -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

View File

@ -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;
}

View File

@ -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
View 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);
}
}

View File

@ -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 \

View File

@ -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);

View File

@ -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)

View File

@ -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;
} }

View File

@ -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