Explorar o 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 %!s(int64=5) %!d(string=hai) anos
pai
achega
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
 #endif
 
 
 void application_blink(void* p);
 void application_blink(void* p);
+void application_uart_write(void* p);
 
 
 const FlipperStartupApp FLIPPER_STARTUP[] = {
 const FlipperStartupApp FLIPPER_STARTUP[] = {
     #ifdef TEST
     #ifdef TEST
@@ -21,4 +22,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     #ifdef EXAMPLE_BLINK
     #ifdef EXAMPLE_BLINK
     {.app = application_blink, .name = "blink"},
     {.app = application_blink, .name = "blink"},
     #endif
     #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 <stdio.h>
 #include <string.h>
 #include <string.h>
 #include "flipper.h"
 #include "flipper.h"
-#include "debug.h"
+#include "log.h"
 
 
 /*
 /*
 TEST: pipe record
 TEST: pipe record
@@ -22,48 +22,48 @@ void pipe_record_cb(const void* value, size_t size) {
     pipe_record_value = *((uint8_t*)value);
     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
     // 1. create pipe record
     if(!furi_create("test/pipe", NULL, 0)) {
     if(!furi_create("test/pipe", NULL, 0)) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         return false;
         return false;
     }
     }
 
 
     // 2. Open/subscribe to it 
     // 2. Open/subscribe to it 
-    FuriRecordHandler pipe_record = furi_open(
+    FuriRecordSubscriber* pipe_record = furi_open(
         "test/pipe", false, false, pipe_record_cb, NULL
         "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;
         return false;
     }
     }
 
 
     const uint8_t WRITE_VALUE = 1;
     const uint8_t WRITE_VALUE = 1;
     // 3. write data
     // 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;
         return false;
     }
     }
 
 
     // 4. check that subscriber get data
     // 4. check that subscriber get data
     if(pipe_record_value != WRITE_VALUE) {
     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;
         return false;
     }
     }
 
 
     // 5. try to read, get error
     // 5. try to read, get error
     uint8_t read_value = 0;
     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;
         return false;
     }
     }
 
 
     // 6. close record
     // 6. close record
-    furi_close(&pipe_record);
+    furi_close(pipe_record);
 
 
     // 7. try to write, get error
     // 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;
         return false;
     }
     }
 
 
@@ -88,56 +88,56 @@ void holding_record_cb(const void* value, size_t size) {
     holding_record_value = *((uint8_t*)value);
     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
     // 1. Create holding record
     uint8_t holder = 0;
     uint8_t holder = 0;
     if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
     if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         return false;
         return false;
     }
     }
 
 
     // 2. Open/Subscribe on it
     // 2. Open/Subscribe on it
-    FuriRecordHandler holding_record = furi_open(
+    FuriRecordSubscriber* holding_record = furi_open(
         "test/holding", false, false, holding_record_cb, NULL
         "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;
         return false;
     }
     }
 
 
     const uint8_t WRITE_VALUE = 1;
     const uint8_t WRITE_VALUE = 1;
     // 3. write data
     // 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;
         return false;
     }
     }
 
 
     // 4. check that subscriber get data
     // 4. check that subscriber get data
     if(holding_record_value != WRITE_VALUE) {
     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;
         return false;
     }
     }
 
 
     // 5. Read and check data
     // 5. Read and check data
     uint8_t read_value = 0;
     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;
         return false;
     }
     }
 
 
     if(read_value != WRITE_VALUE) {
     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;
         return false;
     }
     }
 
 
     // 6. Try to write/read wrong size of data
     // 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;
         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;
         return false;
     }
     }
 
 
@@ -161,21 +161,22 @@ typedef struct {
 } ConcurrentValue;
 } ConcurrentValue;
 
 
 void furi_concurent_app(void* p) {
 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
         "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);
         furiac_exit(NULL);
     }
     }
 
 
     for(size_t i = 0; i < 10; i++) {
     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) {
         if(value == NULL) {
-            fprintf(debug_uart, "cannot take record\n");
+            fuprintf(log, "cannot take record\n");
+            furi_give(holding_record);
             furiac_exit(NULL);
             furiac_exit(NULL);
         }
         }
         // emulate read-modify-write broken by context switching
         // 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!
         delay(2); // this is only for test, do not add delay between take/give in prod!
         value->a = a;
         value->a = a;
         value->b = b;
         value->b = b;
-        furi_give(&holding_record);
+        furi_give(holding_record);
     }
     }
 
 
     furiac_exit(NULL);
     furiac_exit(NULL);
 }
 }
 
 
-bool test_furi_concurrent_access(FILE* debug_uart) {
+bool test_furi_concurrent_access(FuriRecordSubscriber* log) {
     // 1. Create holding record
     // 1. Create holding record
     ConcurrentValue holder = {.a = 0, .b = 0};
     ConcurrentValue holder = {.a = 0, .b = 0};
     if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
     if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         return false;
         return false;
     }
     }
 
 
     // 2. Open it
     // 2. Open it
-    FuriRecordHandler holding_record = furi_open(
+    FuriRecordSubscriber* holding_record = furi_open(
         "test/concurrent", false, false, NULL, NULL
         "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;
         return false;
     }
     }
 
 
     // 3. Create second app for interact with it
     // 3. Create second app for interact with it
     FuriApp* second_app = furiac_start(
     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
     // 4. multiply ConcurrentValue::a
     for(size_t i = 0; i < 4; i++) {
     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) {
         if(value == NULL) {
-            fprintf(debug_uart, "cannot take record\n");
+            fuprintf(log, "cannot take record\n");
+            furi_give(holding_record);
             return false;
             return false;
         }
         }
         // emulate read-modify-write broken by context switching
         // emulate read-modify-write broken by context switching
@@ -230,18 +232,18 @@ bool test_furi_concurrent_access(FILE* debug_uart) {
         value->a = a;
         value->a = a;
         delay(10); // this is only for test, do not add delay between take/give in prod!
         delay(10); // this is only for test, do not add delay between take/give in prod!
         value->b = b;
         value->b = b;
-        furi_give(&holding_record);
+        furi_give(holding_record);
     }
     }
 
 
     delay(20);
     delay(20);
 
 
     if(second_app->handler != NULL) {
     if(second_app->handler != NULL) {
-        fprintf(debug_uart, "second app still alive\n");
+        fuprintf(log, "second app still alive\n");
         return false;
         return false;
     }
     }
 
 
     if(holder.a != holder.b) {
     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;
         return false;
     }
     }
 
 
@@ -256,7 +258,7 @@ TEST: non-existent data
 
 
 TODO: implement this test
 TODO: implement this test
 */
 */
-bool test_furi_nonexistent_data(FILE* debug_uart) {
+bool test_furi_nonexistent_data(FuriRecordSubscriber* log) {
 
 
     return true;
     return true;
 }
 }
@@ -315,20 +317,20 @@ void mute_record_state_cb(FlipperRecordState state) {
 }
 }
 
 
 void furi_mute_parent_app(void* p) {
 void furi_mute_parent_app(void* p) {
-    FILE* debug_uart = (FILE*)p;
+    FuriRecordSubscriber* log = (FuriRecordSubscriber*)p;
 
 
     // 1. Create pipe record
     // 1. Create pipe record
     if(!furi_create("test/mute", NULL, 0)) {
     if(!furi_create("test/mute", NULL, 0)) {
-        fprintf(debug_uart, "cannot create record\n");
+        fuprintf(log, "cannot create record\n");
         furiac_exit(NULL);
         furiac_exit(NULL);
     }
     }
 
 
     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
     // 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
         "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);
         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:
     // 1. Create "parent" application:
     FuriApp* parent_app = furiac_start(
     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
     delay(2); // wait creating record
 
 
     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
     // 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
         "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;
         return false;
     }
     }
 
 
     uint8_t test_counter = 1;
     uint8_t test_counter = 1;
 
 
     // Try to write data to A and check subscriber
     // 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;
         return false;
     }
     }
 
 
     if(mute_last_value != test_counter) {
     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;
         return false;
     }
     }
 
 
     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
     // 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
         "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;
         return false;
     }
     }
 
 
     // Check A state cb get FlipperRecordStateMute.
     // Check A state cb get FlipperRecordStateMute.
     if(mute_last_state != 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;
         return false;
     }
     }
 
 
     test_counter = 2;
     test_counter = 2;
 
 
     // Try to write data to A and check that subscriber get no data. (muted)
     // 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;
         return false;
     }
     }
 
 
     if(mute_last_value == test_counter) {
     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;
         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.
     // 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;
         return false;
     }
     }
 
 
     if(mute_last_value != test_counter) {
     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;
         return false;
     }
     }
 
 
 
 
     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
     // 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
         "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;
         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.
     // TODO: Try to write data to C and check that subscriber get data.
 
 
     // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
     // 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
         "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;
         return false;
     }
     }
 
 
@@ -445,7 +447,7 @@ bool test_furi_mute_algorithm(FILE* debug_uart) {
 
 
     // 7. Exit "parent application"
     // 7. Exit "parent application"
     if(!furiac_kill(parent_app)) {
     if(!furiac_kill(parent_app)) {
-        fprintf(debug_uart, "kill parent_app fail\n");
+        fuprintf(log, "kill parent_app fail\n");
         return false;
         return false;
     }
     }
 
 

+ 8 - 8
applications/tests/furiac_test.c

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

+ 33 - 33
applications/tests/test_index.c

@@ -1,67 +1,67 @@
 #include <stdio.h>
 #include <stdio.h>
 #include "flipper.h"
 #include "flipper.h"
-#include "debug.h"
+#include "log.h"
 
 
 #include "flipper-core.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) {
 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 {
     } 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 {
     } 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 {
     } 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 {
     } 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 {
     } 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 {
     } 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 {
     } 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) {
     if(add(1, 2) == 3) {
-        fprintf(debug_uart, "[TEST] Rust add PASSED\n");
+        fuprintf(log, "[TEST] Rust add PASSED\n");
     } else {
     } else {
-        fprintf(debug_uart, "[TEST] Rust add FAILED\n");
+        fuprintf(log, "[TEST] Rust add FAILED\n");
     }
     }
 
 
     furiac_exit(NULL);
     furiac_exit(NULL);

+ 7 - 1
core/app.cpp

@@ -4,10 +4,16 @@
 extern "C" {
 extern "C" {
     #include "startup.h"
     #include "startup.h"
     #include "furi.h"
     #include "furi.h"
-    #include "debug.h"
+    #include "log.h"
+    #include "tty_uart.h"
 }
 }
 
 
 extern "C" void app() {
 extern "C" void app() {
+    register_tty_uart();
+
+    FuriRecordSubscriber* log = get_default_log();
+    fuprintf(log, "\n=== Welcome to Flipper Zero! ===\n\n");
+
     // FURI startup
     // FURI startup
     FuriApp* handlers[sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0])];
     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) {
     if(current_buffer_idx >= MAX_RECORD_COUNT) {
         // max record count exceed
         // max record count exceed
         #ifdef FURI_DEBUG
         #ifdef FURI_DEBUG
-            printf("[FURI] max record count exceed\n");
+            printf("[FURI] create: max record count exceed\n");
         #endif
         #endif
         return NULL;
         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;
         records[current_buffer_idx].subscribers[i].allocated = false;
     }
     }
 
 
+    current_buffer_idx++;
+
     return true;
     return true;
 }
 }
 
 
-FuriRecordHandler furi_open(
+FuriRecordSubscriber* furi_open(
     const char* name,
     const char* name,
     bool solo,
     bool solo,
     bool no_mute,
     bool no_mute,
@@ -75,16 +77,15 @@ FuriRecordHandler furi_open(
             printf("[FURI] cannot find record %s\n", name);
             printf("[FURI] cannot find record %s\n", name);
         #endif
         #endif
 
 
-        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
-        return res;
+        return NULL;
     }
     }
 
 
     // allocate subscriber
     // allocate subscriber
     FuriRecordSubscriber* subscriber = NULL;
     FuriRecordSubscriber* subscriber = NULL;
 
 
     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
     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;
             break;
         }
         }
     }
     }
@@ -92,11 +93,10 @@ FuriRecordHandler furi_open(
     if(subscriber == NULL) {
     if(subscriber == NULL) {
         // cannot add subscriber (full)
         // cannot add subscriber (full)
         #ifdef FURI_DEBUG
         #ifdef FURI_DEBUG
-            printf("[FURI] cannot add subscriber (full)\n");
+            printf("[FURI] open: cannot add subscriber (full)\n");
         #endif
         #endif
-
-        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
-        return res;
+        
+        return NULL;
     }
     }
 
 
     // increase mute_counter
     // increase mute_counter
@@ -110,25 +110,31 @@ FuriRecordHandler furi_open(
     subscriber->no_mute = no_mute;
     subscriber->no_mute = no_mute;
     subscriber->cb = value_callback;
     subscriber->cb = value_callback;
     subscriber->state_cb = state_callback;
     subscriber->state_cb = state_callback;
+    subscriber->record = record;
 
 
     // register record in application
     // register record in application
     FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
     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
     #ifdef FURI_DEBUG
         printf("[FURI] closing %s record\n", handler->record->name);
         printf("[FURI] closing %s record\n", handler->record->name);
     #endif
     #endif
 
 
     // deallocate subscriber
     // deallocate subscriber
-    handler->subscriber->allocated = false;
+    handler->allocated = false;
 
 
     // set mute counter to next max value
     // set mute counter to next max value
     uint8_t max_mute_counter = 0;
     uint8_t max_mute_counter = 0;
@@ -142,7 +148,7 @@ void furi_close(FuriRecordHandler* handler) {
     handler->record->mute_counter = max_mute_counter;
     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++) {
     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
         if(handler->record->subscribers[i].allocated) {
         if(handler->record->subscribers[i].allocated) {
             if(handler->record->subscribers[i].cb != NULL) {
             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
     // take mutex
 
 
     return handler->record->value;
     return handler->record->value;
 }
 }
 
 
-void furi_give(FuriRecordHandler* handler) {
+void furi_give(FuriRecordSubscriber* handler) {
     // release mutex
     // 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
     #ifdef FURI_DEBUG
         printf("[FURI] read from %s\n", handler->record->name);
         printf("[FURI] read from %s\n", handler->record->name);
     #endif
     #endif
@@ -182,23 +188,44 @@ bool furi_read(FuriRecordHandler* handler, void* value, size_t size) {
     return true;
     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
     #ifdef FURI_DEBUG
         printf("[FURI] write to %s\n", handler->record->name);
         printf("[FURI] write to %s\n", handler->record->name);
     #endif
     #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
     // 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
     // check mute
     if(
     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) {
     if(handler->record->value != NULL) {
         // real write to value
         // real write to value

+ 12 - 13
core/furi.h

@@ -22,16 +22,19 @@ typedef enum {
 /// pointer to state callback function
 /// pointer to state callback function
 typedef void(*FlipperRecordStateCallback)(FlipperRecordState);
 typedef void(*FlipperRecordStateCallback)(FlipperRecordState);
 
 
+struct _FuriRecord;
+
 typedef struct {
 typedef struct {
     bool allocated;
     bool allocated;
     FlipperRecordCallback cb; ///< value cb
     FlipperRecordCallback cb; ///< value cb
     FlipperRecordStateCallback state_cb; ///< state cb
     FlipperRecordStateCallback state_cb; ///< state cb
     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
     bool no_mute;
     bool no_mute;
+    struct _FuriRecord* record; ///< parent record
 } FuriRecordSubscriber;
 } FuriRecordSubscriber;
 
 
 /// FURI record handler
 /// FURI record handler
-typedef struct {
+struct _FuriRecord {
     const char* name;
     const char* name;
     void* value;
     void* value;
     size_t size;
     size_t size;
@@ -39,13 +42,9 @@ typedef struct {
     SemaphoreHandle_t mutex;
     SemaphoreHandle_t mutex;
     uint8_t mute_counter;
     uint8_t mute_counter;
     FuriRecordSubscriber subscribers[MAX_RECORD_SUBSCRIBERS];
     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
 /// store info about active task
 typedef struct {
 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.
 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.
 \param[in] no_mute if true, another applications cannot mute this handler.
 */
 */
-FuriRecordHandler furi_open(
+FuriRecordSubscriber* furi_open(
     const char* name,
     const char* name,
     bool solo,
     bool solo,
     bool no_mute,
     bool no_mute,
@@ -121,7 +120,7 @@ FuriRecordHandler furi_open(
 /*!
 /*!
 
 
 */
 */
-void furi_close(FuriRecordHandler* handler);
+void furi_close(FuriRecordSubscriber* handler);
 
 
 /*!
 /*!
 read message from record.
 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
 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.
 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
 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.
 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
 TODO: enum return value with execution status
 */
 */
-void* furi_take(FuriRecordHandler* record);
+void* furi_take(FuriRecordSubscriber* record);
 
 
 /*!
 /*!
 unlock value mutex.
 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
 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.c
 C_SOURCES += ../core/furi_ac.c
 C_SOURCES += ../core/furi_ac.c
 
 
@@ -131,6 +132,11 @@ C_SOURCES += ../applications/examples/blink.c
 C_DEFS += -DEXAMPLE_BLINK
 C_DEFS += -DEXAMPLE_BLINK
 endif
 endif
 
 
+ifeq ($(EXAMPLE_UART_WRITE), 1)
+C_SOURCES += ../applications/examples/uart_write.c
+C_DEFS += -DEXAMPLE_UART_WRITE
+endif
+
 # User application
 # User application
 
 
 # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 # 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:
 	EXAMPLE_BLINK=1 make
 	EXAMPLE_BLINK=1 make
 
 
+example_uart_write:
+	EXAMPLE_UART_WRITE=1 make
+
 test:
 test:
 	TEST=1 make
 	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_SOURCES += Src/lo_hal.c
 
 
 C_DEFS += -DFURI_DEBUG
 C_DEFS += -DFURI_DEBUG
+
 # Core
 # Core
 
 
 CPP_SOURCES += ../core/app.cpp
 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.c
 C_SOURCES += ../core/furi_ac.c
 C_SOURCES += ../core/furi_ac.c
 
 
@@ -56,6 +58,11 @@ C_SOURCES += ../applications/examples/blink.c
 C_DEFS += -DEXAMPLE_BLINK
 C_DEFS += -DEXAMPLE_BLINK
 endif
 endif
 
 
+ifeq ($(EXAMPLE_UART_WRITE), 1)
+C_SOURCES += ../applications/examples/uart_write.c
+C_DEFS += -DEXAMPLE_UART_WRITE
+endif
+
 # User application
 # User application
 
 
 # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
@@ -130,10 +137,18 @@ all: $(BUILD_DIR)/$(TARGET)
 
 
 example_blink:
 example_blink:
 	EXAMPLE_BLINK=1 make
 	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)
 	$(BUILD_DIR)/$(TARGET)
 
 
 test:
 test:
 	TEST=1 make
 	TEST=1 make
+	rm $(BUILD_DIR)/app.o
 	$(BUILD_DIR)/$(TARGET)
 	$(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
 # 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