Sfoglia il codice sorgente

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
coreglitch 5 anni fa
parent
commit
1759787334

+ 81 - 0
applications/app_example/app_example.c

@@ -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 - 0
applications/app_example/app_example.h

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

+ 0 - 0
applications/furi_test/furi_test.c


+ 0 - 0
applications/furi_test/furi_test.h


+ 13 - 0
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"}
+};

+ 455 - 0
applications/tests/furi_record_test.c

@@ -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 - 0
applications/tests/furiac_test.c

@@ -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 - 0
applications/tests/test_index.c

@@ -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 - 0
applications/tests/test_index.h

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

+ 19 - 14
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);
 }

+ 1 - 1
core/write.c → 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,

+ 1 - 0
core/debug.h

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

+ 7 - 0
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
 

+ 216 - 0
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 - 0
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 - 0
core/furi_ac.c

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

+ 2 - 1
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 \

+ 28 - 1
target_lo/Inc/cmsis_os.h

@@ -1,3 +1,30 @@
 #include "main.h"
+#include <stdbool.h>
 
-void osDelay(uint32_t ms);
+void osDelay(uint32_t ms);
+
+// some FreeRTOS types
+typedef void(*TaskFunction_t)(void*);
+typedef uint32_t UBaseType_t;
+typedef uint32_t StackType_t;
+typedef uint32_t StaticTask_t;
+typedef pthread_t* TaskHandle_t;
+typedef uint32_t StaticSemaphore_t;
+typedef void* SemaphoreHandle_t;
+
+#define tskIDLE_PRIORITY 0
+
+TaskHandle_t xTaskCreateStatic(
+    TaskFunction_t pxTaskCode,
+    const char * const pcName,
+    const uint32_t ulStackDepth,
+    void * const pvParameters,
+    UBaseType_t uxPriority,
+    StackType_t * const puxStackBuffer,
+    StaticTask_t * const pxTaskBuffer
+);
+
+void vTaskDelete(TaskHandle_t xTask);
+TaskHandle_t xTaskGetCurrentTaskHandle(void);
+SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer);
+bool task_equal(TaskHandle_t a, TaskHandle_t b);

+ 11 - 4
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)

+ 79 - 2
target_lo/Src/lo_os.c

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

+ 44 - 24
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