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:
		
							parent
							
								
									5094623d04
								
							
						
					
					
						commit
						4dc82b68d1
					
				
							
								
								
									
										33
									
								
								applications/examples/uart_write.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								applications/examples/uart_write.c
									
									
									
									
									
										Normal 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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 | ||||||
| }; | }; | ||||||
| @ -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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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])]; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								core/debug.c
									
									
									
									
									
								
							
							
						
						
									
										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; |  | ||||||
| } |  | ||||||
| @ -1 +0,0 @@ | |||||||
| FILE* get_debug(); |  | ||||||
							
								
								
									
										81
									
								
								core/furi.c
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								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 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
 | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								core/furi.h
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								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 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
									
								
							
							
						
						
									
										25
									
								
								core/log.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								core/log.h
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										20
									
								
								core/tty_uart.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										5
									
								
								core/tty_uart.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdbool.h> | ||||||
|  | 
 | ||||||
|  | bool register_tty_uart(); | ||||||
| @ -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 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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
									
								
								wiki/examples/UART-write.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								wiki/examples/UART-write.md
									
									
									
									
									
										Normal 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`_ | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | _Code for target F1 can be compiled by `docker-compose exec dev make -C target_f1 example_uart_write`_ | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
| @ -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
									
								
								wiki_static/application_examples/example_uart_write.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								wiki_static/application_examples/example_uart_write.gif
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | version https://git-lfs.github.com/spec/v1 | ||||||
|  | oid sha256:818b3ef2a3b10fdade1be9459904f8295f75efd16fb532927d5ef6ff187e60e6 | ||||||
|  | size 265769 | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | version https://git-lfs.github.com/spec/v1 | ||||||
|  | oid sha256:76e5e8205a6cec14f5cc34f9b48a12133e0a8d79347f3286eb4bb28aacde4337 | ||||||
|  | size 772608 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 coreglitch
						coreglitch