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

View File

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

View File

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

View File

@ -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();
FuriRecordSubscriber* log = get_default_log();
if(test_furi_ac_create_kill(debug_uart)) {
fprintf(debug_uart, "[TEST] test_furi_ac_create_kill PASSED\n");
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);

View File

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

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) {
// 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());
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

View File

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

View File

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

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
* **[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