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);
|
33
core/app.cpp
33
core/app.cpp
@ -2,23 +2,28 @@
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
FILE* get_debug();
|
||||
#include "startup.h"
|
||||
#include "furi.h"
|
||||
#include "debug.h"
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -18,7 +18,7 @@ ssize_t uart_write(void* cookie, const char * buffer, size_t size) {
|
||||
}
|
||||
|
||||
FILE* get_debug() {
|
||||
FILE* fp = fopencookie(NULL,"w+", (cookie_io_functions_t){
|
||||
FILE* fp = fopencookie(NULL, "w+", (cookie_io_functions_t){
|
||||
.read = NULL,
|
||||
.write = uart_write,
|
||||
.seek = NULL,
|
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" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "flipper_hal.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "furi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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 = \
|
||||
-IInc \
|
||||
-I../app \
|
||||
-I../applications \
|
||||
-I../core \
|
||||
-IDrivers/STM32L4xx_HAL_Driver/Inc \
|
||||
-IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \
|
||||
-IMiddlewares/Third_Party/FreeRTOS/Source/include \
|
||||
|
@ -1,3 +1,30 @@
|
||||
#include "main.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
||||
|
||||
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/lo_os.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
|
||||
#######################################
|
||||
@ -56,10 +62,11 @@ C_DEFS = \
|
||||
# C includes
|
||||
C_INCLUDES = \
|
||||
-IInc \
|
||||
-I../app
|
||||
-I../applications \
|
||||
-I../core
|
||||
|
||||
# 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)
|
||||
CFLAGS += -g -gdwarf-2
|
||||
@ -78,7 +85,7 @@ CPPFLAGS = -fno-threadsafe-statics
|
||||
# libraries
|
||||
LIBS = -lc -lm
|
||||
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
|
||||
all: $(BUILD_DIR)/$(TARGET)
|
||||
|
@ -1,8 +1,85 @@
|
||||
#include "cmsis_os.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
void osDelay(uint32_t ms) {
|
||||
usleep(ms * 1000);
|
||||
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
|
||||
|
||||
* `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_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.
|
||||
**`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_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
|
||||
|
||||
* `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.
|
||||
* `furiac_kill(FuriApp app)` stop specified `app` without returning to `prev` application.
|
||||
**`void 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.
|
||||
|
||||
|
||||
**`bool furiac_kill(FuriApp app)`**
|
||||
|
||||
stop specified `app` without returning to `prev` application.
|
||||
|
||||
# Data exchange
|
||||
|
||||
* `FuriRecord furi_create(char* name)` creates named FURI record. Returns NULL if registry have not enough memory for creating.
|
||||
* `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.
|
||||
* `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).
|
||||
* `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.
|
||||
* `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_unmute(FuriRecord record)` unmutes muted record.
|
||||
* `bool furi_mute(FuriRecord record)` mutes unmuted record.
|
||||
* `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_create(char* name, void* value, size_t size)`**
|
||||
|
||||
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).
|
||||
|
||||
**`FuriRecordHandler furi_open(char* name, bool solo, bool no_mute, void(*FlipperRecordCallback)(const void*, size_t), void(*FlipperRecordStateCallback)(FlipperRecordState))`**
|
||||
|
||||
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(FuriRecordHandler* record)`**
|
||||
|
||||
close handler and unmute anothers.
|
||||
|
||||
**`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
|
||||
_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 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.
|
||||
* "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.
|
||||
|
||||
_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