Explorar el Código

UART write example (#53)

* Rename test functions

* rewrite furi API, segfault

* make fixes in FURI, log through FURI

* add uart write example blank

* implement fuprintf instead of fopencookie

* add gif, blank page

* UART write example description

Co-authored-by: Vadim Kaushan <admin@disasm.info>
coreglitch hace 5 años
padre
commit
4dc82b68d1

+ 33 - 0
applications/examples/uart_write.c

@@ -0,0 +1,33 @@
+#include "flipper.h"
+#include <string.h>
+#include "log.h"
+
+void application_uart_write(void* p) {
+    // Red led for showing progress
+    GpioPin led = {.pin = GPIO_PIN_8, .port = GPIOA};
+    pinMode(led, GpioModeOpenDrain);
+
+    // get_default_log open "tty" record
+    FuriRecordSubscriber* log = get_default_log();
+
+    // create buffer
+    const char test_string[] = "test\n";
+    furi_write(log, test_string, strlen(test_string));
+
+    // for example, create counter and show its value
+    uint8_t counter = 0;
+
+    while(1) {
+        // continously write it to UART
+        fuprintf(log, "counter: %d\n", counter);
+        counter++;
+
+        // flash at every send
+        digitalWrite(led, LOW);
+        delay(50);
+        digitalWrite(led, HIGH);
+
+        // delay with overall perion of 1s
+        delay(950);
+    }
+}

+ 4 - 0
applications/startup.h

@@ -12,6 +12,7 @@ void flipper_test_app(void* p);
 #endif
 
 void application_blink(void* p);
+void application_uart_write(void* p);
 
 const FlipperStartupApp FLIPPER_STARTUP[] = {
     #ifdef TEST
@@ -21,4 +22,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     #ifdef EXAMPLE_BLINK
     {.app = application_blink, .name = "blink"},
     #endif
+    #ifdef EXAMPLE_UART_WRITE
+    {.app = application_uart_write, .name = "uart write"},
+    #endif
 };

+ 80 - 78
applications/tests/furi_record_test.c

@@ -1,7 +1,7 @@
 #include <stdio.h>
 #include <string.h>
 #include "flipper.h"
-#include "debug.h"
+#include "log.h"
 
 /*
 TEST: pipe record
@@ -22,48 +22,48 @@ void pipe_record_cb(const void* value, size_t size) {
     pipe_record_value = *((uint8_t*)value);
 }
 
-bool test_furi_pipe_record(FILE* debug_uart) {
+bool test_furi_pipe_record(FuriRecordSubscriber* log) {
     // 1. create pipe record
     if(!furi_create("test/pipe", NULL, 0)) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         return false;
     }
 
     // 2. Open/subscribe to it 
-    FuriRecordHandler pipe_record = furi_open(
+    FuriRecordSubscriber* pipe_record = furi_open(
         "test/pipe", false, false, pipe_record_cb, NULL
     );
-    if(pipe_record.record == NULL) {
-        fprintf(debug_uart, "cannot open record\n");
+    if(pipe_record == NULL) {
+        fuprintf(log, "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");
+    if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
+        fuprintf(log, "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);
+        fuprintf(log, "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");
+    if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) {
+        fuprintf(log, "reading from pipe record not allowed\n");
         return false;
     }
 
     // 6. close record
-    furi_close(&pipe_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");
+    if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
+        fuprintf(log, "writing to closed record not allowed\n");
         return false;
     }
 
@@ -88,56 +88,56 @@ void holding_record_cb(const void* value, size_t size) {
     holding_record_value = *((uint8_t*)value);
 }
 
-bool test_furi_holding_data(FILE* debug_uart) {
+bool test_furi_holding_data(FuriRecordSubscriber* log) {
     // 1. Create holding record
     uint8_t holder = 0;
     if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         return false;
     }
 
     // 2. Open/Subscribe on it
-    FuriRecordHandler holding_record = furi_open(
+    FuriRecordSubscriber* holding_record = furi_open(
         "test/holding", false, false, holding_record_cb, NULL
     );
-    if(holding_record.record == NULL) {
-        fprintf(debug_uart, "cannot open record\n");
+    if(holding_record == NULL) {
+        fuprintf(log, "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");
+    if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
+        fuprintf(log, "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);
+        fuprintf(log, "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");
+    if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) {
+        fuprintf(log, "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);
+        fuprintf(log, "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");
+    if(furi_write(holding_record, &WRITE_VALUE, 100)) {
+        fuprintf(log, "overflowed write not allowed\n");
         return false;
     }
 
-    if(furi_read(&holding_record, &read_value, 100)) {
-        fprintf(debug_uart, "overflowed read not allowed\n");
+    if(furi_read(holding_record, &read_value, 100)) {
+        fuprintf(log, "overflowed read not allowed\n");
         return false;
     }
 
@@ -161,21 +161,22 @@ typedef struct {
 } ConcurrentValue;
 
 void furi_concurent_app(void* p) {
-    FILE* debug_uart = (FILE*)p;
+    FuriRecordSubscriber* log = (FuriRecordSubscriber*)p;
 
-    FuriRecordHandler holding_record = furi_open(
+    FuriRecordSubscriber* holding_record = furi_open(
         "test/concurrent", false, false, NULL, NULL
     );
-    if(holding_record.record == NULL) {
-        fprintf(debug_uart, "cannot open record\n");
+    if(holding_record == NULL) {
+        fuprintf(log, "cannot open record\n");
         furiac_exit(NULL);
     }
 
     for(size_t i = 0; i < 10; i++) {
-        ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record);
+        ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
 
         if(value == NULL) {
-            fprintf(debug_uart, "cannot take record\n");
+            fuprintf(log, "cannot take record\n");
+            furi_give(holding_record);
             furiac_exit(NULL);
         }
         // emulate read-modify-write broken by context switching
@@ -186,40 +187,41 @@ void furi_concurent_app(void* p) {
         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);
+        furi_give(holding_record);
     }
 
     furiac_exit(NULL);
 }
 
-bool test_furi_concurrent_access(FILE* debug_uart) {
+bool test_furi_concurrent_access(FuriRecordSubscriber* log) {
     // 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");
+        fuprintf(log, "cannot create record\n");
         return false;
     }
 
     // 2. Open it
-    FuriRecordHandler holding_record = furi_open(
+    FuriRecordSubscriber* holding_record = furi_open(
         "test/concurrent", false, false, NULL, NULL
     );
-    if(holding_record.record == NULL) {
-        fprintf(debug_uart, "cannot open record\n");
+    if(holding_record == NULL) {
+        fuprintf(log, "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
+        furi_concurent_app, "furi concurent app", (void*)log
     );
 
     // 4. multiply ConcurrentValue::a
     for(size_t i = 0; i < 4; i++) {
-        ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record);
+        ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
 
         if(value == NULL) {
-            fprintf(debug_uart, "cannot take record\n");
+            fuprintf(log, "cannot take record\n");
+            furi_give(holding_record);
             return false;
         }
         // emulate read-modify-write broken by context switching
@@ -230,18 +232,18 @@ bool test_furi_concurrent_access(FILE* debug_uart) {
         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);
+        furi_give(holding_record);
     }
 
     delay(20);
 
     if(second_app->handler != NULL) {
-        fprintf(debug_uart, "second app still alive\n");
+        fuprintf(log, "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);
+        fuprintf(log, "broken integrity: a=%d, b=%d\n", holder.a, holder.b);
         return false;
     }
 
@@ -256,7 +258,7 @@ TEST: non-existent data
 
 TODO: implement this test
 */
-bool test_furi_nonexistent_data(FILE* debug_uart) {
+bool test_furi_nonexistent_data(FuriRecordSubscriber* log) {
 
     return true;
 }
@@ -315,20 +317,20 @@ void mute_record_state_cb(FlipperRecordState state) {
 }
 
 void furi_mute_parent_app(void* p) {
-    FILE* debug_uart = (FILE*)p;
+    FuriRecordSubscriber* log = (FuriRecordSubscriber*)p;
 
     // 1. Create pipe record
     if(!furi_create("test/mute", NULL, 0)) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         furiac_exit(NULL);
     }
 
     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
-    FuriRecordHandler watch_handler = furi_open(
+    FuriRecordSubscriber* 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");
+    if(watch_handler == NULL) {
+        fuprintf(log, "cannot open watch handler\n");
         furiac_exit(NULL);
     }
 
@@ -338,61 +340,61 @@ void furi_mute_parent_app(void* p) {
     }
 }
 
-bool test_furi_mute_algorithm(FILE* debug_uart) {
+bool test_furi_mute_algorithm(FuriRecordSubscriber* log) {
     // 1. Create "parent" application:
     FuriApp* parent_app = furiac_start(
-        furi_mute_parent_app, "parent app", (void*)debug_uart
+        furi_mute_parent_app, "parent app", (void*)log
     );
 
     delay(2); // wait creating record
 
     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
-    FuriRecordHandler handler_a = furi_open(
+    FuriRecordSubscriber* 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");
+    if(handler_a == NULL) {
+        fuprintf(log, "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");
+    if(!furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
+        fuprintf(log, "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);
+        fuprintf(log, "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(
+    FuriRecordSubscriber* handler_b = furi_open(
         "test/mute", true, true, NULL, NULL
     );
-    if(handler_b.record == NULL) {
-        fprintf(debug_uart, "cannot open handler B\n");
+    if(handler_b == NULL) {
+        fuprintf(log, "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);
+        fuprintf(log, "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");
+    if(furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
+        fuprintf(log, "A not muted\n");
         return false;
     }
 
     if(mute_last_value == test_counter) {
-        fprintf(debug_uart, "value A must be muted\n");
+        fuprintf(log, "value A must be muted\n");
         return false;
     }
 
@@ -400,23 +402,23 @@ bool test_furi_mute_algorithm(FILE* debug_uart) {
 
 
     // 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");
+    if(!furi_write(handler_b, &test_counter, sizeof(uint8_t))) {
+        fuprintf(log, "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);
+        fuprintf(log, "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(
+    FuriRecordSubscriber* handler_c = furi_open(
         "test/mute", true, false, NULL, NULL
     );
-    if(handler_c.record == NULL) {
-        fprintf(debug_uart, "cannot open handler C\n");
+    if(handler_c == NULL) {
+        fuprintf(log, "cannot open handler C\n");
         return false;
     }
 
@@ -425,11 +427,11 @@ bool test_furi_mute_algorithm(FILE* debug_uart) {
     // 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(
+    FuriRecordSubscriber* handler_d = furi_open(
         "test/mute", false, false, NULL, NULL
     );
-    if(handler_d.record == NULL) {
-        fprintf(debug_uart, "cannot open handler D\n");
+    if(handler_d == NULL) {
+        fuprintf(log, "cannot open handler D\n");
         return false;
     }
 
@@ -445,7 +447,7 @@ bool test_furi_mute_algorithm(FILE* debug_uart) {
 
     // 7. Exit "parent application"
     if(!furiac_kill(parent_app)) {
-        fprintf(debug_uart, "kill parent_app fail\n");
+        fuprintf(log, "kill parent_app fail\n");
         return false;
     }
 

+ 8 - 8
applications/tests/furiac_test.c

@@ -1,7 +1,7 @@
 #include <stdio.h>
 #include <string.h>
 #include "flipper.h"
-#include "debug.h"
+#include "log.h"
 
 /*
 Test: creating and killing task
@@ -23,26 +23,26 @@ void create_kill_app(void* p) {
     }
 }
 
-bool test_furi_ac_create_kill(FILE* debug_uart) {
+bool test_furi_ac_create_kill(FuriRecordSubscriber* log) {
     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");
+        fuprintf(log, "create widget fail\n");
         return false;
     }
 
     delay(10);
 
     if(!furiac_kill(widget)) {
-        fprintf(debug_uart, "kill widget fail\n");
+        fuprintf(log, "kill widget fail\n");
         return false;
     }
 
     if(value_a == counter) {
-        fprintf(debug_uart, "counter unchanged\n");
+        fuprintf(log, "counter unchanged\n");
         return false;
     }
 
@@ -51,7 +51,7 @@ bool test_furi_ac_create_kill(FILE* debug_uart) {
     delay(10);
 
     if(value_a != counter) {
-        fprintf(debug_uart, "counter changes after kill (counter = %d vs %d)\n", value_a, counter);
+        fuprintf(log, "counter changes after kill (counter = %d vs %d)\n", value_a, counter);
         return false;
     }
 
@@ -111,7 +111,7 @@ void task_b(void* p) {
     furiac_exit(p);
 }
 
-bool test_furi_ac_switch_exit(FILE* debug_uart) {
+bool test_furi_ac_switch_exit(FuriRecordSubscriber* log) {
     // init sequence
     TestSwitchSequence seq;
     seq.count = 0;
@@ -124,7 +124,7 @@ bool test_furi_ac_switch_exit(FILE* debug_uart) {
     seq.sequence[seq.count] = '\0';
 
     if(strcmp(seq.sequence, "ABA/") != 0) {
-        fprintf(debug_uart, "wrong sequence: %s\n", seq.sequence);
+        fuprintf(log, "wrong sequence: %s\n", seq.sequence);
         return false;
     }
 

+ 33 - 33
applications/tests/test_index.c

@@ -1,67 +1,67 @@
 #include <stdio.h>
 #include "flipper.h"
-#include "debug.h"
+#include "log.h"
 
 #include "flipper-core.h"
 
-bool test_furi_ac_create_kill(FILE* debug_uart);
-bool test_furi_ac_switch_exit(FILE* debug_uart);
+bool test_furi_ac_create_kill(FuriRecordSubscriber* log);
+bool test_furi_ac_switch_exit(FuriRecordSubscriber* log);
 
-bool test_furi_pipe_record(FILE* debug_uart);
-bool test_furi_holding_data(FILE* debug_uart);
-bool test_furi_concurrent_access(FILE* debug_uart);
-bool test_furi_nonexistent_data(FILE* debug_uart);
-bool test_furi_mute_algorithm(FILE* debug_uart);
+bool test_furi_pipe_record(FuriRecordSubscriber* log);
+bool test_furi_holding_data(FuriRecordSubscriber* log);
+bool test_furi_concurrent_access(FuriRecordSubscriber* log);
+bool test_furi_nonexistent_data(FuriRecordSubscriber* log);
+bool test_furi_mute_algorithm(FuriRecordSubscriber* log);
 
 void flipper_test_app(void* p) {
-    FILE* debug_uart = get_debug();
-
-    if(test_furi_ac_create_kill(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_ac_create_kill PASSED\n");
+    FuriRecordSubscriber* log = get_default_log();
+    
+    if(test_furi_ac_create_kill(log)) {
+        fuprintf(log, "[TEST] test_furi_ac_create_kill PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_ac_create_kill FAILED\n");
+        fuprintf(log, "[TEST] test_furi_ac_create_kill FAILED\n");
     }
 
-    if(test_furi_ac_switch_exit(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_ac_switch_exit PASSED\n");
+    if(test_furi_ac_switch_exit(log)) {
+        fuprintf(log, "[TEST] test_furi_ac_switch_exit PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_ac_switch_exit FAILED\n");
+        fuprintf(log, "[TEST] test_furi_ac_switch_exit FAILED\n");
     }
 
-    if(test_furi_pipe_record(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_pipe_record PASSED\n");
+    if(test_furi_pipe_record(log)) {
+        fuprintf(log, "[TEST] test_furi_pipe_record PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_pipe_record FAILED\n");
+        fuprintf(log, "[TEST] test_furi_pipe_record FAILED\n");
     }
 
-    if(test_furi_holding_data(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_holding_data PASSED\n");
+    if(test_furi_holding_data(log)) {
+        fuprintf(log, "[TEST] test_furi_holding_data PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_holding_data FAILED\n");
+        fuprintf(log, "[TEST] test_furi_holding_data FAILED\n");
     }
 
-    if(test_furi_concurrent_access(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_concurrent_access PASSED\n");
+    if(test_furi_concurrent_access(log)) {
+        fuprintf(log, "[TEST] test_furi_concurrent_access PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_concurrent_access FAILED\n");
+        fuprintf(log, "[TEST] test_furi_concurrent_access FAILED\n");
     }
 
-    if(test_furi_nonexistent_data(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_nonexistent_data PASSED\n");
+    if(test_furi_nonexistent_data(log)) {
+        fuprintf(log, "[TEST] test_furi_nonexistent_data PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_nonexistent_data FAILED\n");
+        fuprintf(log, "[TEST] test_furi_nonexistent_data FAILED\n");
     }
 
-    if(test_furi_mute_algorithm(debug_uart)) {
-        fprintf(debug_uart, "[TEST] test_furi_mute_algorithm PASSED\n");
+    if(test_furi_mute_algorithm(log)) {
+        fuprintf(log, "[TEST] test_furi_mute_algorithm PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] test_furi_mute_algorithm FAILED\n");
+        fuprintf(log, "[TEST] test_furi_mute_algorithm FAILED\n");
     }
 
     if(add(1, 2) == 3) {
-        fprintf(debug_uart, "[TEST] Rust add PASSED\n");
+        fuprintf(log, "[TEST] Rust add PASSED\n");
     } else {
-        fprintf(debug_uart, "[TEST] Rust add FAILED\n");
+        fuprintf(log, "[TEST] Rust add FAILED\n");
     }
 
     furiac_exit(NULL);

+ 7 - 1
core/app.cpp

@@ -4,10 +4,16 @@
 extern "C" {
     #include "startup.h"
     #include "furi.h"
-    #include "debug.h"
+    #include "log.h"
+    #include "tty_uart.h"
 }
 
 extern "C" void app() {
+    register_tty_uart();
+
+    FuriRecordSubscriber* log = get_default_log();
+    fuprintf(log, "\n=== Welcome to Flipper Zero! ===\n\n");
+
     // FURI startup
     FuriApp* handlers[sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0])];
 

+ 0 - 31
core/debug.c

@@ -1,31 +0,0 @@
-#define _GNU_SOURCE
-#include "main.h"
-#include <stdio.h>
-
-extern UART_HandleTypeDef DEBUG_UART;
-
-ssize_t uart_write(void* cookie, const char * buffer, size_t size) {
-    if (buffer == 0) {
-        /*
-         * This means that we should flush internal buffers.  Since we
-         * don't we just return.  (Remember, "handle" == -1 means that all
-         * handles should be flushed.)
-         */
-        return 0;
-    }
-    
-    return (ssize_t)HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)buffer, (uint16_t)size, HAL_MAX_DELAY);
-}
-
-FILE* get_debug() {
-    FILE* fp = fopencookie(NULL, "w+", (cookie_io_functions_t){
-        .read  = NULL,
-        .write = uart_write,
-        .seek  = NULL,
-        .close = NULL
-    });
-
-    setvbuf(fp, NULL, _IONBF, 0);
-
-    return fp;
-}

+ 0 - 1
core/debug.h

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

+ 54 - 27
core/furi.c

@@ -35,7 +35,7 @@ bool furi_create(const char* name, void* value, size_t size) {
     if(current_buffer_idx >= MAX_RECORD_COUNT) {
         // max record count exceed
         #ifdef FURI_DEBUG
-            printf("[FURI] max record count exceed\n");
+            printf("[FURI] create: max record count exceed\n");
         #endif
         return NULL;
     }
@@ -52,10 +52,12 @@ bool furi_create(const char* name, void* value, size_t size) {
         records[current_buffer_idx].subscribers[i].allocated = false;
     }
 
+    current_buffer_idx++;
+
     return true;
 }
 
-FuriRecordHandler furi_open(
+FuriRecordSubscriber* furi_open(
     const char* name,
     bool solo,
     bool no_mute,
@@ -75,16 +77,15 @@ FuriRecordHandler furi_open(
             printf("[FURI] cannot find record %s\n", name);
         #endif
 
-        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
-        return res;
+        return NULL;
     }
 
     // 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];
+        if(!record->subscribers[i].allocated) {
+            subscriber = &record->subscribers[i];
             break;
         }
     }
@@ -92,11 +93,10 @@ FuriRecordHandler furi_open(
     if(subscriber == NULL) {
         // cannot add subscriber (full)
         #ifdef FURI_DEBUG
-            printf("[FURI] cannot add subscriber (full)\n");
+            printf("[FURI] open: cannot add subscriber (full)\n");
         #endif
-
-        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
-        return res;
+        
+        return NULL;
     }
 
     // increase mute_counter
@@ -110,25 +110,31 @@ FuriRecordHandler furi_open(
     subscriber->no_mute = no_mute;
     subscriber->cb = value_callback;
     subscriber->state_cb = state_callback;
+    subscriber->record = record;
 
     // register record in application
     FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
 
-    current_task->records[current_task->records_count] = record;
-    current_task->records_count++;
+    if(current_task != NULL) {
+        current_task->records[current_task->records_count] = record;
+        current_task->records_count++;
+    } else {
+        #ifdef FURI_DEBUG
+            printf("[FURI] open: no current task\n");
+        #endif
+    }
 
-    FuriRecordHandler res = {.record = record, .subscriber = subscriber};
-    return res;
+    return subscriber;
 }
 
 
-void furi_close(FuriRecordHandler* handler) {
+void furi_close(FuriRecordSubscriber* handler) {
     #ifdef FURI_DEBUG
         printf("[FURI] closing %s record\n", handler->record->name);
     #endif
 
     // deallocate subscriber
-    handler->subscriber->allocated = false;
+    handler->allocated = false;
 
     // set mute counter to next max value
     uint8_t max_mute_counter = 0;
@@ -142,7 +148,7 @@ void furi_close(FuriRecordHandler* handler) {
     handler->record->mute_counter = max_mute_counter;
 }
 
-static void furi_notify(FuriRecordHandler* handler, const void* value, size_t size) {
+static void furi_notify(FuriRecordSubscriber* 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) {
@@ -152,17 +158,17 @@ static void furi_notify(FuriRecordHandler* handler, const void* value, size_t si
     }
 }
 
-void* furi_take(FuriRecordHandler* handler) {
+void* furi_take(FuriRecordSubscriber* handler) {
     // take mutex
 
     return handler->record->value;
 }
 
-void furi_give(FuriRecordHandler* handler) {
+void furi_give(FuriRecordSubscriber* handler) {
     // release mutex
 }
 
-bool furi_read(FuriRecordHandler* handler, void* value, size_t size) {
+bool furi_read(FuriRecordSubscriber* handler, void* value, size_t size) {
     #ifdef FURI_DEBUG
         printf("[FURI] read from %s\n", handler->record->name);
     #endif
@@ -182,23 +188,44 @@ bool furi_read(FuriRecordHandler* handler, void* value, size_t size) {
     return true;
 }
 
-bool furi_write(FuriRecordHandler* handler, const void* value, size_t size) {
+bool furi_write(FuriRecordSubscriber* handler, const void* value, size_t size) {
     #ifdef FURI_DEBUG
         printf("[FURI] write to %s\n", handler->record->name);
     #endif
 
-    if(handler == NULL || handler->record == NULL || value == NULL) return false;
+    if(handler == NULL || handler->record == NULL || value == NULL) {
+        #ifdef FURI_DEBUG
+            printf("[FURI] write: null param %x %x\n", (uint32_t)(size_t)handler, (uint32_t)(size_t)value);
+        #endif
+
+        return false;
+    }
 
     // check if closed
-    if(!handler->subscriber->allocated) return false;
+    if(!handler->allocated) {
+        #ifdef FURI_DEBUG
+            printf("[FURI] write: handler closed\n");
+        #endif
+        return false;
+    }
 
-    if(handler->record->value != NULL && size > handler->record->size) return false;
+    if(handler->record->value != NULL && size > handler->record->size) {
+        #ifdef FURI_DEBUG
+            printf("[FURI] write: wrong size %d\n", (uint32_t)size);
+        #endif
+        return false;
+    }
 
     // check mute
     if(
-        handler->record->mute_counter != handler->subscriber->mute_counter
-        && !handler->subscriber->no_mute
-    ) return false;
+        handler->record->mute_counter != handler->mute_counter
+        && !handler->no_mute
+    ) {
+        #ifdef FURI_DEBUG
+            printf("[FURI] write: muted\n");
+        #endif
+        return false;
+    }
 
     if(handler->record->value != NULL) {
         // real write to value

+ 12 - 13
core/furi.h

@@ -22,16 +22,19 @@ typedef enum {
 /// pointer to state callback function
 typedef void(*FlipperRecordStateCallback)(FlipperRecordState);
 
+struct _FuriRecord;
+
 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;
+    struct _FuriRecord* record; ///< parent record
 } FuriRecordSubscriber;
 
 /// FURI record handler
-typedef struct {
+struct _FuriRecord {
     const char* name;
     void* value;
     size_t size;
@@ -39,13 +42,9 @@ typedef struct {
     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;
+typedef struct _FuriRecord FuriRecord;
 
 /// store info about active task
 typedef struct {
@@ -110,7 +109,7 @@ 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(
+FuriRecordSubscriber* furi_open(
     const char* name,
     bool solo,
     bool no_mute,
@@ -121,7 +120,7 @@ FuriRecordHandler furi_open(
 /*!
 
 */
-void furi_close(FuriRecordHandler* handler);
+void furi_close(FuriRecordSubscriber* handler);
 
 /*!
 read message from record.
@@ -130,7 +129,7 @@ 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);
+bool furi_read(FuriRecordSubscriber* record, void* data, size_t size);
 
 /*!
 write message to record.
@@ -138,7 +137,7 @@ 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);
+bool furi_write(FuriRecordSubscriber* record, const void* data, size_t size);
 
 /*!
 lock value mutex.
@@ -150,9 +149,9 @@ Returns pointer to data, NULL if closed/non-existent record or muted
 
 TODO: enum return value with execution status
 */
-void* furi_take(FuriRecordHandler* record);
+void* furi_take(FuriRecordSubscriber* record);
 
 /*!
 unlock value mutex.
 */
-void furi_give(FuriRecordHandler* record);
+void furi_give(FuriRecordSubscriber* record);

+ 25 - 0
core/log.c

@@ -0,0 +1,25 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "log.h"
+#include "furi.h"
+
+#define PRINT_STR_SIZE 64
+
+void fuprintf(FuriRecordSubscriber* f, const char * format, ...) {
+    char buffer[PRINT_STR_SIZE];
+
+    va_list args;
+    va_start(args, format);
+    vsprintf(buffer, format, args);
+    va_end(args);
+
+    furi_write(f, buffer, strlen(buffer));
+}
+
+FuriRecordSubscriber* get_default_log() {
+    return furi_open("tty", false, false, NULL, NULL);
+}

+ 6 - 0
core/log.h

@@ -0,0 +1,6 @@
+#pragma once
+
+#include "furi.h"
+
+FuriRecordSubscriber* get_default_log();
+void fuprintf(FuriRecordSubscriber* f, const char * format, ...);

+ 20 - 0
core/tty_uart.c

@@ -0,0 +1,20 @@
+#include "furi.h"
+#include "main.h"
+
+extern UART_HandleTypeDef DEBUG_UART;
+
+void handle_uart_write(const void* data, size_t size) {
+	HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)data, (uint16_t)size, HAL_MAX_DELAY);
+}
+
+bool register_tty_uart() {
+	if(!furi_create("tty", NULL, 0)) {
+		return false;
+	}
+	
+	if(furi_open("tty", false, false, handle_uart_write, NULL) == NULL) {
+		return false;
+	}
+
+	return true;
+}

+ 5 - 0
core/tty_uart.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include <stdbool.h>
+
+bool register_tty_uart();

+ 10 - 1
target_f1/Makefile

@@ -111,7 +111,8 @@ startup_stm32l476xx.s
 
 CPP_SOURCES += ../core/app.cpp
 
-C_SOURCES += ../core/debug.c
+C_SOURCES += ../core/log.c
+C_SOURCES += ../core/tty_uart.c
 C_SOURCES += ../core/furi.c
 C_SOURCES += ../core/furi_ac.c
 
@@ -131,6 +132,11 @@ C_SOURCES += ../applications/examples/blink.c
 C_DEFS += -DEXAMPLE_BLINK
 endif
 
+ifeq ($(EXAMPLE_UART_WRITE), 1)
+C_SOURCES += ../applications/examples/uart_write.c
+C_DEFS += -DEXAMPLE_UART_WRITE
+endif
+
 # User application
 
 # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
@@ -253,6 +259,9 @@ all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET
 example_blink:
 	EXAMPLE_BLINK=1 make
 
+example_uart_write:
+	EXAMPLE_UART_WRITE=1 make
+
 test:
 	TEST=1 make
 

+ 16 - 1
target_lo/Makefile

@@ -32,11 +32,13 @@ C_SOURCES += Src/lo_os.c
 C_SOURCES += Src/lo_hal.c
 
 C_DEFS += -DFURI_DEBUG
+
 # Core
 
 CPP_SOURCES += ../core/app.cpp
 
-C_SOURCES += ../core/debug.c
+C_SOURCES += ../core/log.c
+C_SOURCES += ../core/tty_uart.c
 C_SOURCES += ../core/furi.c
 C_SOURCES += ../core/furi_ac.c
 
@@ -56,6 +58,11 @@ C_SOURCES += ../applications/examples/blink.c
 C_DEFS += -DEXAMPLE_BLINK
 endif
 
+ifeq ($(EXAMPLE_UART_WRITE), 1)
+C_SOURCES += ../applications/examples/uart_write.c
+C_DEFS += -DEXAMPLE_UART_WRITE
+endif
+
 # User application
 
 # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
@@ -130,10 +137,18 @@ all: $(BUILD_DIR)/$(TARGET)
 
 example_blink:
 	EXAMPLE_BLINK=1 make
+	rm $(BUILD_DIR)/app.o
+	$(BUILD_DIR)/$(TARGET)
+
+
+example_uart_write:
+	EXAMPLE_UART_WRITE=1 make
+	rm $(BUILD_DIR)/app.o
 	$(BUILD_DIR)/$(TARGET)
 
 test:
 	TEST=1 make
+	rm $(BUILD_DIR)/app.o
 	$(BUILD_DIR)/$(TARGET)
 
 

+ 71 - 0
wiki/examples/UART-write.md

@@ -0,0 +1,71 @@
+In this example we try to use FURI for interacting between user application and core subsystem.
+
+First of all, we open FURI record by name "tty". This record is used for send some debug/logging info and interact with user by kind-of-TTY (like UART or USB CDC). By default on Flipper target all writes to tty record handled by debug UART (configured by `DEBUG_UART` define). On local target all writes simply prints to stdout.
+
+Open record:
+
+```C
+FuriRecordSubscriber* log = get_default_log();
+```
+
+This is just wrapper on common FURI method:
+
+```C
+furi_open("tty", false, false, NULL, NULL);
+```
+
+"tty" is FURI pipe record. It means that there is no "data" hold in record, it only manage callbacks: when you call `furi_write`, all subscriber's callback is called. You can find default implementation in `core/tty_uart.c`.
+
+Let's get a look at full example code:
+
+```C
+#include "flipper.h"
+#include <string.h>
+#include "log.h"
+
+void application_uart_write(void* p) {
+    // Red led for showing progress
+    GpioPin led = {.pin = GPIO_PIN_8, .port = GPIOA};
+    pinMode(led, GpioModeOpenDrain);
+
+    // get_default_log open "tty" record
+    FuriRecordSubscriber* log = get_default_log();
+
+    // create buffer
+    const char test_string[] = "test\n";
+    furi_write(log, test_string, strlen(test_string));
+
+    // for example, create counter and show its value
+    uint8_t counter = 0;
+
+    while(1) {
+        // continously write it to UART
+        fuprintf(log, "counter: %d\n", counter);
+        counter++;
+
+        // flash at every send
+        digitalWrite(led, LOW);
+        delay(50);
+        digitalWrite(led, HIGH);
+
+        // delay with overall perion of 1s
+        delay(950);
+    }
+}
+```
+
+This code demonstrates two way to work with record:
+
+1. Directly writes some data by `furi_write`
+2. Uses `fuprintf` wrapper on `printf`.
+
+For creating application and set it to autorun, read [Blink example](Blink-app).
+
+_You can also find source of this example in `applications/examples/uart_write.c` and run it by `docker-compose exec dev make -C target_lo example_uart_write`_
+
+![](https://github.com/Flipper-Zero/flipperzero-firmware-community/raw/master/wiki_static/application_examples/example_uart_write.gif)
+
+_Code for target F1 can be compiled by `docker-compose exec dev make -C target_f1 example_uart_write`_
+
+![](https://github.com/Flipper-Zero/flipperzero-firmware-community/raw/master/wiki_static/application_examples/example_uart_write_hw.gif)
+

+ 2 - 1
wiki/fw/Application-examples.md

@@ -25,4 +25,5 @@ void application_name(void* p) {
 
 # Application examples
 
-* **[Blink](Blink-app)**
+* **[Blink](Blink-app)** show how to create app and control GPIO
+* **[UART write](UART-write)** operate with FURI pipe and print some messages

+ 3 - 0
wiki_static/application_examples/example_uart_write.gif

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:818b3ef2a3b10fdade1be9459904f8295f75efd16fb532927d5ef6ff187e60e6
+size 265769

+ 3 - 0
wiki_static/application_examples/example_uart_write_hw.gif

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:76e5e8205a6cec14f5cc34f9b48a12133e0a8d79347f3286eb4bb28aacde4337
+size 772608