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>
This commit is contained in:
coreglitch 2020-08-27 00:32:22 +06:00 committed by GitHub
parent 5094623d04
commit 4dc82b68d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 392 additions and 195 deletions

View File

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

View File

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

View File

@ -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) { if(pipe_record == NULL) {
fprintf(debug_uart, "cannot open record\n"); 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))) { if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
fprintf(debug_uart, "cannot write to record\n"); 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))) { if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) {
fprintf(debug_uart, "reading from pipe record not allowed\n"); 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))) { if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
fprintf(debug_uart, "writing to closed record not allowed\n"); 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) { if(holding_record == NULL) {
fprintf(debug_uart, "cannot open record\n"); 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))) { if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
fprintf(debug_uart, "cannot write to record\n"); 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))) { if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) {
fprintf(debug_uart, "cannot read from record\n"); 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)) { if(furi_write(holding_record, &WRITE_VALUE, 100)) {
fprintf(debug_uart, "overflowed write not allowed\n"); fuprintf(log, "overflowed write not allowed\n");
return false; return false;
} }
if(furi_read(&holding_record, &read_value, 100)) { if(furi_read(holding_record, &read_value, 100)) {
fprintf(debug_uart, "overflowed read not allowed\n"); 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) { if(holding_record == NULL) {
fprintf(debug_uart, "cannot open record\n"); 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) { if(holding_record == NULL) {
fprintf(debug_uart, "cannot open record\n"); 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) { if(watch_handler == NULL) {
fprintf(debug_uart, "cannot open watch handler\n"); 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) { if(handler_a == NULL) {
fprintf(debug_uart, "cannot open handler A\n"); 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))) { if(!furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
fprintf(debug_uart, "write to A failed\n"); 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) { if(handler_b == NULL) {
fprintf(debug_uart, "cannot open handler B\n"); 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))) { if(furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
fprintf(debug_uart, "A not muted\n"); 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))) { if(!furi_write(handler_b, &test_counter, sizeof(uint8_t))) {
fprintf(debug_uart, "write to B failed\n"); 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) { if(handler_c == NULL) {
fprintf(debug_uart, "cannot open handler C\n"); 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) { if(handler_d == NULL) {
fprintf(debug_uart, "cannot open handler D\n"); 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;
} }

View File

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

View File

@ -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_create_kill(FuriRecordSubscriber* log);
bool test_furi_ac_switch_exit(FILE* debug_uart); bool test_furi_ac_switch_exit(FuriRecordSubscriber* log);
bool test_furi_pipe_record(FILE* debug_uart); bool test_furi_pipe_record(FuriRecordSubscriber* log);
bool test_furi_holding_data(FILE* debug_uart); bool test_furi_holding_data(FuriRecordSubscriber* log);
bool test_furi_concurrent_access(FILE* debug_uart); bool test_furi_concurrent_access(FuriRecordSubscriber* log);
bool test_furi_nonexistent_data(FILE* debug_uart); bool test_furi_nonexistent_data(FuriRecordSubscriber* log);
bool test_furi_mute_algorithm(FILE* debug_uart); bool test_furi_mute_algorithm(FuriRecordSubscriber* log);
void flipper_test_app(void* p) { void flipper_test_app(void* p) {
FILE* debug_uart = get_debug(); FuriRecordSubscriber* log = get_default_log();
if(test_furi_ac_create_kill(debug_uart)) { if(test_furi_ac_create_kill(log)) {
fprintf(debug_uart, "[TEST] test_furi_ac_create_kill PASSED\n"); 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)) { if(test_furi_ac_switch_exit(log)) {
fprintf(debug_uart, "[TEST] test_furi_ac_switch_exit PASSED\n"); 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)) { if(test_furi_pipe_record(log)) {
fprintf(debug_uart, "[TEST] test_furi_pipe_record PASSED\n"); 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)) { if(test_furi_holding_data(log)) {
fprintf(debug_uart, "[TEST] test_furi_holding_data PASSED\n"); 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)) { if(test_furi_concurrent_access(log)) {
fprintf(debug_uart, "[TEST] test_furi_concurrent_access PASSED\n"); 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)) { if(test_furi_nonexistent_data(log)) {
fprintf(debug_uart, "[TEST] test_furi_nonexistent_data PASSED\n"); 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)) { if(test_furi_mute_algorithm(log)) {
fprintf(debug_uart, "[TEST] test_furi_mute_algorithm PASSED\n"); 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);

View File

@ -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])];

View File

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

View File

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

View File

@ -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 NULL;
return res;
} }
// 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) { if(!record->subscribers[i].allocated) {
subscriber = &records[current_buffer_idx].subscribers[i]; 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 NULL;
return res;
} }
// 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; if(current_task != NULL) {
current_task->records_count++; 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 subscriber;
return res;
} }
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->record->mute_counter != handler->mute_counter
&& !handler->subscriber->no_mute && !handler->no_mute
) return false; ) {
#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

View File

@ -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 FuriRecord;
typedef struct {
FuriRecord* record; ///< full record (for read/write/take/give value)
FuriRecordSubscriber* subscriber; ///< current handler info
} FuriRecordHandler;
/// 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
core/log.c Normal file
View File

@ -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
core/log.h Normal file
View File

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

20
core/tty_uart.c Normal file
View File

@ -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
core/tty_uart.h Normal file
View File

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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

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

View File

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