From 17597873344541db261ff9ee99f4780bd3415811 Mon Sep 17 00:00:00 2001 From: coreglitch Date: Mon, 24 Aug 2020 21:31:22 +0600 Subject: [PATCH] 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 --- applications/app_example/app_example.c | 81 +++++ applications/app_example/app_example.h | 2 + applications/furi_test/furi_test.c | 0 applications/furi_test/furi_test.h | 0 applications/startup.h | 13 + applications/tests/furi_record_test.c | 455 +++++++++++++++++++++++++ applications/tests/furiac_test.c | 130 +++++++ applications/tests/test_index.c | 60 ++++ applications/tests/test_index.h | 2 + core/app.cpp | 33 +- core/{write.c => debug.c} | 2 +- core/debug.h | 1 + core/flipper.h | 7 + core/furi.c | 216 ++++++++++++ core/furi.h | 158 +++++++++ core/furi_ac.c | 139 ++++++++ target_f1/Makefile | 3 +- target_lo/Inc/cmsis_os.h | 29 +- target_lo/Makefile | 15 +- target_lo/Src/lo_os.c | 81 ++++- wiki/fw/FURI.md | 68 ++-- 21 files changed, 1448 insertions(+), 47 deletions(-) create mode 100644 applications/app_example/app_example.c create mode 100644 applications/app_example/app_example.h delete mode 100644 applications/furi_test/furi_test.c delete mode 100644 applications/furi_test/furi_test.h create mode 100644 applications/startup.h create mode 100644 applications/tests/furi_record_test.c create mode 100644 applications/tests/furiac_test.c create mode 100644 applications/tests/test_index.c create mode 100644 applications/tests/test_index.h rename core/{write.c => debug.c} (91%) create mode 100644 core/debug.h create mode 100644 core/furi_ac.c diff --git a/applications/app_example/app_example.c b/applications/app_example/app_example.c new file mode 100644 index 00000000..cbbebd5f --- /dev/null +++ b/applications/app_example/app_example.c @@ -0,0 +1,81 @@ +#include +#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); +} +*/ \ No newline at end of file diff --git a/applications/app_example/app_example.h b/applications/app_example/app_example.h new file mode 100644 index 00000000..40d5ec13 --- /dev/null +++ b/applications/app_example/app_example.h @@ -0,0 +1,2 @@ + +void furi_test_app(void*); \ No newline at end of file diff --git a/applications/furi_test/furi_test.c b/applications/furi_test/furi_test.c deleted file mode 100644 index e69de29b..00000000 diff --git a/applications/furi_test/furi_test.h b/applications/furi_test/furi_test.h deleted file mode 100644 index e69de29b..00000000 diff --git a/applications/startup.h b/applications/startup.h new file mode 100644 index 00000000..7c93013a --- /dev/null +++ b/applications/startup.h @@ -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"} +}; \ No newline at end of file diff --git a/applications/tests/furi_record_test.c b/applications/tests/furi_record_test.c new file mode 100644 index 00000000..e9418881 --- /dev/null +++ b/applications/tests/furi_record_test.c @@ -0,0 +1,455 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/applications/tests/furiac_test.c b/applications/tests/furiac_test.c new file mode 100644 index 00000000..d5e88121 --- /dev/null +++ b/applications/tests/furiac_test.c @@ -0,0 +1,130 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c new file mode 100644 index 00000000..d6758bd4 --- /dev/null +++ b/applications/tests/test_index.c @@ -0,0 +1,60 @@ +#include +#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); +} \ No newline at end of file diff --git a/applications/tests/test_index.h b/applications/tests/test_index.h new file mode 100644 index 00000000..9b5204ee --- /dev/null +++ b/applications/tests/test_index.h @@ -0,0 +1,2 @@ + +void flipper_test_app(void* p); \ No newline at end of file diff --git a/core/app.cpp b/core/app.cpp index 26a4bf94..3c666808 100644 --- a/core/app.cpp +++ b/core/app.cpp @@ -2,23 +2,28 @@ #include 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); } \ No newline at end of file diff --git a/core/write.c b/core/debug.c similarity index 91% rename from core/write.c rename to core/debug.c index 2146e4f0..ce482417 100644 --- a/core/write.c +++ b/core/debug.c @@ -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, diff --git a/core/debug.h b/core/debug.h new file mode 100644 index 00000000..c94181a2 --- /dev/null +++ b/core/debug.h @@ -0,0 +1 @@ +FILE* get_debug(); \ No newline at end of file diff --git a/core/flipper.h b/core/flipper.h index f94bc1b2..591761a2 100644 --- a/core/flipper.h +++ b/core/flipper.h @@ -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 diff --git a/core/furi.c b/core/furi.c index e69de29b..d484fbbd 100644 --- a/core/furi.c +++ b/core/furi.c @@ -0,0 +1,216 @@ +#include "furi.h" +#include "cmsis_os.h" +#include + +#define DEBUG + +#ifdef DEBUG +#include +#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; +} \ No newline at end of file diff --git a/core/furi.h b/core/furi.h index e69de29b..53d029a4 100644 --- a/core/furi.h +++ b/core/furi.h @@ -0,0 +1,158 @@ +#pragma once + +#include "cmsis_os.h" +#include +#include + +#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); diff --git a/core/furi_ac.c b/core/furi_ac.c new file mode 100644 index 00000000..31715d2b --- /dev/null +++ b/core/furi_ac.c @@ -0,0 +1,139 @@ +#include "furi.h" +#include "cmsis_os.h" + +#define DEBUG + +#ifdef DEBUG +#include +#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); + } +} \ No newline at end of file diff --git a/target_f1/Makefile b/target_f1/Makefile index f8bb8931..f80c8b3f 100644 --- a/target_f1/Makefile +++ b/target_f1/Makefile @@ -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 \ diff --git a/target_lo/Inc/cmsis_os.h b/target_lo/Inc/cmsis_os.h index 7a14c8bc..91873ab5 100644 --- a/target_lo/Inc/cmsis_os.h +++ b/target_lo/Inc/cmsis_os.h @@ -1,3 +1,30 @@ #include "main.h" +#include -void osDelay(uint32_t ms); \ No newline at end of file +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); diff --git a/target_lo/Makefile b/target_lo/Makefile index 93b7fc3e..41e0c8de 100644 --- a/target_lo/Makefile +++ b/target_lo/Makefile @@ -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) diff --git a/target_lo/Src/lo_os.c b/target_lo/Src/lo_os.c index ad748b23..dd83b4ec 100644 --- a/target_lo/Src/lo_os.c +++ b/target_lo/Src/lo_os.c @@ -1,8 +1,85 @@ #include "cmsis_os.h" #include #include +#include +#include +#include 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; } \ No newline at end of file diff --git a/wiki/fw/FURI.md b/wiki/fw/FURI.md index 24c31df5..6609a4c9 100644 --- a/wiki/fw/FURI.md +++ b/wiki/fw/FURI.md @@ -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 \ No newline at end of file