Core api concept (#144)
* add input debounce code from old fw * exampl of input api * change input API to get/release * revert input API to read * pointer instead of instance * add input API description * add display API * rewrite display names * migrate to valuemanager * add LED API * add closing brakets * add sound api * fix led api * basic api * rename API pages * change pubsub implementation * move FURI AC -> flapp, add valuemutex example, add valuemanager implementation * pubsub usage example * user led example * update example * simplify input * add composed display * add SPI/GPIO and CC1101 bus * change cc1101 api * spi api and devices * spi api and devices * move SPI to page, add GPIO * not block pin open * backlight API and more * add minunit tests * fix logging * ignore unexisting time service on embedded targets * fix warning, issue with printf * Deprecate furi_open and furi_close (#167) Rename existing furi_open and furi_close to deprecated version * add exitcode * migrate to printf * indicate test by leds * add testing description * rename furi.h * wip basic api * add valuemutex, pubsub, split files * add value expanders * value mutex realization and tests * valuemutex test added to makefile * do not build unimplemented files * fix build furmware target f2 * redesigned minunit tests to allow testing in separate files * test file for valuemutex minunit testing * minunit partial test valuemutex * local cmsis_os2 mutex bindings * implement furi open/create, tests * migrate concurrent_access to ValueMutex * add spi header * Lib: add mlib submodule. Co-authored-by: rusdacent <rusdacentx0x08@gmail.com> Co-authored-by: DrZlo13 <who.just.the.doctor@gmail.com>
This commit is contained in:
		
							parent
							
								
									b7c30154f4
								
							
						
					
					
						commit
						942bbfaefe
					
				
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,6 @@ | |||||||
| [submodule "lib/STM32CubeL4"] | [submodule "lib/STM32CubeL4"] | ||||||
| 	path = lib/STM32CubeL4 | 	path = lib/STM32CubeL4 | ||||||
| 	url = https://github.com/STMicroelectronics/STM32CubeL4.git | 	url = https://github.com/STMicroelectronics/STM32CubeL4.git | ||||||
|  | [submodule "lib/mlib"] | ||||||
|  | 	path = lib/mlib | ||||||
|  | 	url = https://github.com/P-p-H-d/mlib.git | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ C_SOURCES	+= $(APP_DIR)/tests/furiac_test.c | |||||||
| C_SOURCES	+= $(APP_DIR)/tests/furi_record_test.c | C_SOURCES	+= $(APP_DIR)/tests/furi_record_test.c | ||||||
| C_SOURCES	+= $(APP_DIR)/tests/test_index.c | C_SOURCES	+= $(APP_DIR)/tests/test_index.c | ||||||
| C_SOURCES	+= $(APP_DIR)/tests/minunit_test.c | C_SOURCES	+= $(APP_DIR)/tests/minunit_test.c | ||||||
|  | C_SOURCES	+= $(APP_DIR)/tests/furi_valuemutex_test.c | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| APP_EXAMPLE_BLINK ?= 0 | APP_EXAMPLE_BLINK ?= 0 | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ void coreglitch_demo_0(void* p) { | |||||||
|     fuprintf(log, "coreglitch demo!\n"); |     fuprintf(log, "coreglitch demo!\n"); | ||||||
| 
 | 
 | ||||||
|     // open record
 |     // open record
 | ||||||
|     FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* fb_record = | ||||||
|  |         furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(log, "[widget] cannot create fb record\n"); |         fuprintf(log, "[widget] cannot create fb record\n"); | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ void display_u8g2(void* p) { | |||||||
|         &_u8g2); // send init sequence to the display, display is in sleep mode after this
 |         &_u8g2); // send init sequence to the display, display is in sleep mode after this
 | ||||||
|     u8g2_SetContrast(&_u8g2, 36); |     u8g2_SetContrast(&_u8g2, 36); | ||||||
| 
 | 
 | ||||||
|     if(!furi_create("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) { |     if(!furi_create_deprecated("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) { | ||||||
|         fuprintf(log, "[display_u8g2] cannot create fb record\n"); |         fuprintf(log, "[display_u8g2] cannot create fb record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| @ -162,7 +162,7 @@ void display_u8g2(void* p) { | |||||||
| 
 | 
 | ||||||
|     // subscribe to record. ctx will be passed to handle_fb_change
 |     // subscribe to record. ctx will be passed to handle_fb_change
 | ||||||
|     FuriRecordSubscriber* fb_record = |     FuriRecordSubscriber* fb_record = | ||||||
|         furi_open("u8g2_fb", false, false, handle_fb_change, NULL, &ctx); |         furi_open_deprecated("u8g2_fb", false, false, handle_fb_change, NULL, &ctx); | ||||||
| 
 | 
 | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(log, "[display] cannot open fb record\n"); |         fuprintf(log, "[display] cannot open fb record\n"); | ||||||
|  | |||||||
| @ -49,14 +49,14 @@ void fatfs_list(void* p) { | |||||||
| 
 | 
 | ||||||
|     furi_log = get_default_log(); |     furi_log = get_default_log(); | ||||||
| 
 | 
 | ||||||
|     FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n"); |         fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FuriRecordSubscriber* event_record = |     FuriRecordSubscriber* event_record = | ||||||
|         furi_open("input_events", false, false, event_cb, NULL, event_queue); |         furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue); | ||||||
|     if(event_record == NULL) { |     if(event_record == NULL) { | ||||||
|         fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n"); |         fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|  | |||||||
| @ -16,9 +16,9 @@ static void event_cb(const void* value, size_t size, void* ctx) { | |||||||
| void application_input_dump(void* p) { | void application_input_dump(void* p) { | ||||||
|     // open record
 |     // open record
 | ||||||
|     FuriRecordSubscriber* state_record = |     FuriRecordSubscriber* state_record = | ||||||
|         furi_open("input_state", false, false, state_cb, NULL, NULL); |         furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL); | ||||||
|     FuriRecordSubscriber* event_record = |     FuriRecordSubscriber* event_record = | ||||||
|         furi_open("input_events", false, false, event_cb, NULL, NULL); |         furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     for(;;) { |     for(;;) { | ||||||
|         delay(100); |         delay(100); | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ void application_ipc_display(void* p) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // create record
 |     // create record
 | ||||||
|     if(!furi_create("test_fb", (void*)_framebuffer, FB_SIZE)) { |     if(!furi_create_deprecated("test_fb", (void*)_framebuffer, FB_SIZE)) { | ||||||
|         fuprintf(log, "[display] cannot create fb record\n"); |         fuprintf(log, "[display] cannot create fb record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| @ -79,7 +79,7 @@ void application_ipc_display(void* p) { | |||||||
| 
 | 
 | ||||||
|     // subscribe to record. ctx will be passed to handle_fb_change
 |     // subscribe to record. ctx will be passed to handle_fb_change
 | ||||||
|     FuriRecordSubscriber* fb_record = |     FuriRecordSubscriber* fb_record = | ||||||
|         furi_open("test_fb", false, false, handle_fb_change, NULL, &ctx); |         furi_open_deprecated("test_fb", false, false, handle_fb_change, NULL, &ctx); | ||||||
| 
 | 
 | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(log, "[display] cannot open fb record\n"); |         fuprintf(log, "[display] cannot open fb record\n"); | ||||||
| @ -124,7 +124,8 @@ void application_ipc_widget(void* p) { | |||||||
|     FuriRecordSubscriber* log = get_default_log(); |     FuriRecordSubscriber* log = get_default_log(); | ||||||
| 
 | 
 | ||||||
|     // open record
 |     // open record
 | ||||||
|     FuriRecordSubscriber* fb_record = furi_open("test_fb", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* fb_record = | ||||||
|  |         furi_open_deprecated("test_fb", false, false, NULL, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(log, "[widget] cannot create fb record\n"); |         fuprintf(log, "[widget] cannot create fb record\n"); | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ void u8g2_example(void* p) { | |||||||
|     FuriRecordSubscriber* log = get_default_log(); |     FuriRecordSubscriber* log = get_default_log(); | ||||||
| 
 | 
 | ||||||
|     // open record
 |     // open record
 | ||||||
|     FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     if(fb_record == NULL) { |     if(fb_record == NULL) { | ||||||
|         fuprintf(log, "[widget] cannot create fb record\n"); |         fuprintf(log, "[widget] cannot create fb record\n"); | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ void u8g2_qrcode(void* p) { | |||||||
|     FuriRecordSubscriber* log = get_default_log(); |     FuriRecordSubscriber* log = get_default_log(); | ||||||
| 
 | 
 | ||||||
|     // open record
 |     // open record
 | ||||||
|     FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     // Allocate a chunk of memory to store the QR code
 |     // Allocate a chunk of memory to store the QR code
 | ||||||
|     // https://github.com/ricmoo/QRCode
 |     // https://github.com/ricmoo/QRCode
 | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
| #include <input_priv.h> | #include <input_priv.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <furi.h> | #include <flipper.h> | ||||||
| 
 | 
 | ||||||
| static volatile bool initialized = false; | static volatile bool initialized = false; | ||||||
| static SemaphoreHandle_t event; | static SemaphoreHandle_t event; | ||||||
| @ -16,25 +16,25 @@ void input_task(void* p) { | |||||||
| 
 | 
 | ||||||
|     event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); |     event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); | ||||||
| 
 | 
 | ||||||
|     if(!furi_create("input_state", (void*)&input_state, sizeof(input_state))) { |     if(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) { | ||||||
|         printf("[input_task] cannot create the input_state record\n"); |         printf("[input_task] cannot create the input_state record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FuriRecordSubscriber* input_state_record = |     FuriRecordSubscriber* input_state_record = | ||||||
|         furi_open("input_state", false, false, NULL, NULL, NULL); |         furi_open_deprecated("input_state", false, false, NULL, NULL, NULL); | ||||||
|     if(input_state_record == NULL) { |     if(input_state_record == NULL) { | ||||||
|         printf("[input_task] cannot open the input_state record\n"); |         printf("[input_task] cannot open the input_state record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!furi_create("input_events", NULL, 0)) { |     if(!furi_create_deprecated("input_events", NULL, 0)) { | ||||||
|         printf("[input_task] cannot create the input_events record\n"); |         printf("[input_task] cannot create the input_events record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FuriRecordSubscriber* input_events_record = |     FuriRecordSubscriber* input_events_record = | ||||||
|         furi_open("input_events", false, false, NULL, NULL, NULL); |         furi_open_deprecated("input_events", false, false, NULL, NULL, NULL); | ||||||
|     if(input_events_record == NULL) { |     if(input_events_record == NULL) { | ||||||
|         printf("[input_task] cannot open the input_events record\n"); |         printf("[input_task] cannot open the input_events record\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "furi.h" | #include "flipper.h" | ||||||
| 
 | 
 | ||||||
| #define FURI_LIB (const char*[]) | #define FURI_LIB (const char*[]) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,245 +1,18 @@ | |||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include "flipper.h" | #include "flipper.h" | ||||||
|  | #include "flipper_v2.h" | ||||||
| #include "log.h" | #include "log.h" | ||||||
|  | #include "minunit.h" | ||||||
| 
 | 
 | ||||||
| /*
 | void test_furi_create_open() { | ||||||
| TEST: pipe record |     // 1. Create record
 | ||||||
| 
 |     uint8_t test_data = 0; | ||||||
| 1. create pipe record |     mu_check(furi_create("test/holding", (void*)&test_data)); | ||||||
| 2. Open/subscribe to it  |  | ||||||
| 3. write data |  | ||||||
| 4. check that subscriber get data |  | ||||||
| 5. try to read, get error |  | ||||||
| 6. close record |  | ||||||
| 7. try to write, get error |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| static uint8_t pipe_record_value = 0; |  | ||||||
| 
 |  | ||||||
| void pipe_record_cb(const void* value, size_t size, void* ctx) { |  | ||||||
|     // hold value to static var
 |  | ||||||
|     pipe_record_value = *((uint8_t*)value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool test_furi_pipe_record() { |  | ||||||
|     // 1. create pipe record
 |  | ||||||
|     if(!furi_create("test/pipe", NULL, 0)) { |  | ||||||
|         printf("cannot create record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 2. Open/subscribe to it
 |  | ||||||
|     FuriRecordSubscriber* pipe_record = |  | ||||||
|         furi_open("test/pipe", false, false, pipe_record_cb, NULL, NULL); |  | ||||||
|     if(pipe_record == NULL) { |  | ||||||
|         printf("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))) { |  | ||||||
|         printf("cannot write to record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 4. check that subscriber get data
 |  | ||||||
|     if(pipe_record_value != WRITE_VALUE) { |  | ||||||
|         printf("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))) { |  | ||||||
|         printf("reading from pipe record not allowed\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 6. close record
 |  | ||||||
|     furi_close(pipe_record); |  | ||||||
| 
 |  | ||||||
|     // 7. try to write, get error
 |  | ||||||
|     if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { |  | ||||||
|         printf("writing to closed record not allowed\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
| TEST: holding data |  | ||||||
| 
 |  | ||||||
| 1. Create holding record |  | ||||||
| 2. Open/Subscribe on it |  | ||||||
| 3. Write data |  | ||||||
| 4. Check that subscriber get data |  | ||||||
| 5. Read and check data |  | ||||||
| 6. Try to write/read wrong size of data |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| static uint8_t holding_record_value = 0; |  | ||||||
| 
 |  | ||||||
| void holding_record_cb(const void* value, size_t size, void* ctx) { |  | ||||||
|     // hold value to static var
 |  | ||||||
|     holding_record_value = *((uint8_t*)value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool test_furi_holding_data() { |  | ||||||
|     // 1. Create holding record
 |  | ||||||
|     uint8_t holder = 0; |  | ||||||
|     if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) { |  | ||||||
|         printf("cannot create record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 2. Open/Subscribe on it
 |  | ||||||
|     FuriRecordSubscriber* holding_record = |  | ||||||
|         furi_open("test/holding", false, false, holding_record_cb, NULL, NULL); |  | ||||||
|     if(holding_record == NULL) { |  | ||||||
|         printf("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))) { |  | ||||||
|         printf("cannot write to record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 4. check that subscriber get data
 |  | ||||||
|     if(holding_record_value != WRITE_VALUE) { |  | ||||||
|         printf("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))) { |  | ||||||
|         printf("cannot read from record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(read_value != WRITE_VALUE) { |  | ||||||
|         printf("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)) { |  | ||||||
|         printf("overflowed write not allowed\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(furi_read(holding_record, &read_value, 100)) { |  | ||||||
|         printf("overflowed read not allowed\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
| TEST: concurrent access |  | ||||||
| 
 |  | ||||||
| 1. Create holding record |  | ||||||
| 2. Open it twice |  | ||||||
| 3. Change value simultaneously in two app and check integrity |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| // TODO this test broke because mutex in furi is not implemented
 |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     // a and b must be equal
 |  | ||||||
|     uint8_t a; |  | ||||||
|     uint8_t b; |  | ||||||
| } ConcurrentValue; |  | ||||||
| 
 |  | ||||||
| void furi_concurent_app(void* p) { |  | ||||||
|     FuriRecordSubscriber* holding_record = |  | ||||||
|         furi_open("test/concurrent", false, false, NULL, NULL, NULL); |  | ||||||
|     if(holding_record == NULL) { |  | ||||||
|         printf("cannot open record\n"); |  | ||||||
|         furiac_exit(NULL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < 10; i++) { |  | ||||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); |  | ||||||
| 
 |  | ||||||
|         if(value == NULL) { |  | ||||||
|             printf("cannot take record\n"); |  | ||||||
|             furi_give(holding_record); |  | ||||||
|             furiac_exit(NULL); |  | ||||||
|         } |  | ||||||
|         // emulate read-modify-write broken by context switching
 |  | ||||||
|         uint8_t a = value->a; |  | ||||||
|         uint8_t b = value->b; |  | ||||||
|         a++; |  | ||||||
|         b++; |  | ||||||
|         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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furiac_exit(NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool test_furi_concurrent_access() { |  | ||||||
|     // 1. Create holding record
 |  | ||||||
|     ConcurrentValue holder = {.a = 0, .b = 0}; |  | ||||||
|     if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) { |  | ||||||
|         printf("cannot create record\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // 2. Open it
 |     // 2. Open it
 | ||||||
|     FuriRecordSubscriber* holding_record = |     void* record = furi_open("test/holding"); | ||||||
|         furi_open("test/concurrent", false, false, NULL, NULL, NULL); |     mu_assert_pointers_eq(record, &test_data); | ||||||
|     if(holding_record == NULL) { |  | ||||||
|         printf("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", NULL); |  | ||||||
| 
 |  | ||||||
|     // 4. multiply ConcurrentValue::a
 |  | ||||||
|     for(size_t i = 0; i < 4; i++) { |  | ||||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); |  | ||||||
| 
 |  | ||||||
|         if(value == NULL) { |  | ||||||
|             printf("cannot take record\n"); |  | ||||||
|             furi_give(holding_record); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         // emulate read-modify-write broken by context switching
 |  | ||||||
|         uint8_t a = value->a; |  | ||||||
|         uint8_t b = value->b; |  | ||||||
|         a++; |  | ||||||
|         b++; |  | ||||||
|         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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     delay(50); |  | ||||||
| 
 |  | ||||||
|     if(second_app->handler != NULL) { |  | ||||||
|         printf("second app still alive\n"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(holder.a != holder.b) { |  | ||||||
|         printf("broken integrity: a=%d, b=%d\n", holder.a, holder.b); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
| @ -309,14 +82,14 @@ void mute_record_state_cb(FlipperRecordState state, void* ctx) { | |||||||
| 
 | 
 | ||||||
| void furi_mute_parent_app(void* p) { | void furi_mute_parent_app(void* p) { | ||||||
|     // 1. Create pipe record
 |     // 1. Create pipe record
 | ||||||
|     if(!furi_create("test/mute", NULL, 0)) { |     if(!furi_create_deprecated("test/mute", NULL, 0)) { | ||||||
|         printf("cannot create record\n"); |         printf("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
 | ||||||
|     FuriRecordSubscriber* watch_handler = |     FuriRecordSubscriber* watch_handler = | ||||||
|         furi_open("test/mute", false, false, mute_record_cb, NULL, NULL); |         furi_open_deprecated("test/mute", false, false, mute_record_cb, NULL, NULL); | ||||||
|     if(watch_handler == NULL) { |     if(watch_handler == NULL) { | ||||||
|         printf("cannot open watch handler\n"); |         printf("cannot open watch handler\n"); | ||||||
|         furiac_exit(NULL); |         furiac_exit(NULL); | ||||||
| @ -336,7 +109,7 @@ bool test_furi_mute_algorithm() { | |||||||
| 
 | 
 | ||||||
|     // 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.
 | ||||||
|     FuriRecordSubscriber* handler_a = |     FuriRecordSubscriber* handler_a = | ||||||
|         furi_open("test/mute", false, false, NULL, mute_record_state_cb, NULL); |         furi_open_deprecated("test/mute", false, false, NULL, mute_record_state_cb, NULL); | ||||||
|     if(handler_a == NULL) { |     if(handler_a == NULL) { | ||||||
|         printf("cannot open handler A\n"); |         printf("cannot open handler A\n"); | ||||||
|         return false; |         return false; | ||||||
| @ -356,7 +129,8 @@ bool test_furi_mute_algorithm() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 |     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 | ||||||
|     FuriRecordSubscriber* handler_b = furi_open("test/mute", true, true, NULL, NULL, NULL); |     FuriRecordSubscriber* handler_b = | ||||||
|  |         furi_open_deprecated("test/mute", true, true, NULL, NULL, NULL); | ||||||
|     if(handler_b == NULL) { |     if(handler_b == NULL) { | ||||||
|         printf("cannot open handler B\n"); |         printf("cannot open handler B\n"); | ||||||
|         return false; |         return false; | ||||||
| @ -395,7 +169,8 @@ bool test_furi_mute_algorithm() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 |     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 | ||||||
|     FuriRecordSubscriber* handler_c = furi_open("test/mute", true, false, NULL, NULL, NULL); |     FuriRecordSubscriber* handler_c = | ||||||
|  |         furi_open_deprecated("test/mute", true, false, NULL, NULL, NULL); | ||||||
|     if(handler_c == NULL) { |     if(handler_c == NULL) { | ||||||
|         printf("cannot open handler C\n"); |         printf("cannot open handler C\n"); | ||||||
|         return false; |         return false; | ||||||
| @ -406,7 +181,8 @@ bool test_furi_mute_algorithm() { | |||||||
|     // 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.
 | ||||||
|     FuriRecordSubscriber* handler_d = furi_open("test/mute", false, false, NULL, NULL, NULL); |     FuriRecordSubscriber* handler_d = | ||||||
|  |         furi_open_deprecated("test/mute", false, false, NULL, NULL, NULL); | ||||||
|     if(handler_d == NULL) { |     if(handler_d == NULL) { | ||||||
|         printf("cannot open handler D\n"); |         printf("cannot open handler D\n"); | ||||||
|         return false; |         return false; | ||||||
|  | |||||||
							
								
								
									
										123
									
								
								applications/tests/furi_valuemutex_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								applications/tests/furi_valuemutex_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | |||||||
|  | #include <stdio.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include "flipper_v2.h" | ||||||
|  | #include "log.h" | ||||||
|  | 
 | ||||||
|  | #include "minunit.h" | ||||||
|  | 
 | ||||||
|  | void test_furi_valuemutex() { | ||||||
|  |     const int init_value = 0xdeadbeef; | ||||||
|  |     const int changed_value = 0x12345678; | ||||||
|  | 
 | ||||||
|  |     int value = init_value; | ||||||
|  |     bool result; | ||||||
|  |     ValueMutex valuemutex; | ||||||
|  | 
 | ||||||
|  |     // init mutex case
 | ||||||
|  |     result = init_mutex(&valuemutex, &value, sizeof(value)); | ||||||
|  |     mu_assert(result, "init mutex failed"); | ||||||
|  | 
 | ||||||
|  |     // acquire mutex case
 | ||||||
|  |     int* value_pointer = acquire_mutex(&valuemutex, 100); | ||||||
|  |     mu_assert_pointers_eq(value_pointer, &value); | ||||||
|  | 
 | ||||||
|  |     // second acquire mutex case
 | ||||||
|  |     int* value_pointer_second = acquire_mutex(&valuemutex, 100); | ||||||
|  |     mu_assert_pointers_eq(value_pointer_second, NULL); | ||||||
|  | 
 | ||||||
|  |     // change value case
 | ||||||
|  |     *value_pointer = changed_value; | ||||||
|  |     mu_assert_int_eq(value, changed_value); | ||||||
|  | 
 | ||||||
|  |     // release mutex case
 | ||||||
|  |     result = release_mutex(&valuemutex, &value); | ||||||
|  |     mu_assert(result, "release mutex failed"); | ||||||
|  | 
 | ||||||
|  |     // TODO
 | ||||||
|  |     //acquire mutex blocking case
 | ||||||
|  |     //write mutex blocking case
 | ||||||
|  |     //read mutex blocking case
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | TEST: concurrent access | ||||||
|  | 
 | ||||||
|  | 1. Create holding record | ||||||
|  | 2. Open it twice | ||||||
|  | 3. Change value simultaneously in two app and check integrity | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | // TODO this test broke because mutex in furi is not implemented
 | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     // a and b must be equal
 | ||||||
|  |     uint8_t a; | ||||||
|  |     uint8_t b; | ||||||
|  | } ConcurrentValue; | ||||||
|  | 
 | ||||||
|  | void furi_concurent_app(void* p) { | ||||||
|  |     ValueMutex* mutex = (ValueMutex*)p; | ||||||
|  |     if(mutex == NULL) { | ||||||
|  |         printf("cannot open mutex\n"); | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < 10; i++) { | ||||||
|  |         ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(mutex); | ||||||
|  | 
 | ||||||
|  |         if(value == NULL) { | ||||||
|  |             printf("cannot take record\n"); | ||||||
|  |             release_mutex(mutex, value); | ||||||
|  |             furiac_exit(NULL); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // emulate read-modify-write broken by context switching
 | ||||||
|  |         uint8_t a = value->a; | ||||||
|  |         uint8_t b = value->b; | ||||||
|  |         a++; | ||||||
|  |         b++; | ||||||
|  |         delay(2); | ||||||
|  |         value->a = a; | ||||||
|  |         value->b = b; | ||||||
|  |         release_mutex(mutex, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furiac_exit(NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void test_furi_concurrent_access() { | ||||||
|  |     // 1. Create holding record
 | ||||||
|  |     ConcurrentValue value = {.a = 0, .b = 0}; | ||||||
|  |     ValueMutex mutex; | ||||||
|  |     mu_check(init_mutex(&mutex, &value, sizeof(value))); | ||||||
|  | 
 | ||||||
|  |     // 3. Create second app for interact with it
 | ||||||
|  |     FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", (void*)&mutex); | ||||||
|  | 
 | ||||||
|  |     // 4. multiply ConcurrentValue::a
 | ||||||
|  |     for(size_t i = 0; i < 4; i++) { | ||||||
|  |         ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(&mutex); | ||||||
|  | 
 | ||||||
|  |         if(value == NULL) { | ||||||
|  |             release_mutex(&mutex, value); | ||||||
|  |             mu_fail("cannot take record\n"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // emulate read-modify-write broken by context switching
 | ||||||
|  |         uint8_t a = value->a; | ||||||
|  |         uint8_t b = value->b; | ||||||
|  |         a++; | ||||||
|  |         b++; | ||||||
|  |         value->a = a; | ||||||
|  |         delay(10); // this is only for test, do not add delay between take/give in prod!
 | ||||||
|  |         value->b = b; | ||||||
|  |         release_mutex(&mutex, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     delay(50); | ||||||
|  | 
 | ||||||
|  |     mu_assert_pointers_eq(second_app->handler, NULL); | ||||||
|  | 
 | ||||||
|  |     mu_assert_int_eq(value.a, value.b); | ||||||
|  | } | ||||||
| @ -6,12 +6,15 @@ | |||||||
| 
 | 
 | ||||||
| bool test_furi_ac_create_kill(); | bool test_furi_ac_create_kill(); | ||||||
| bool test_furi_ac_switch_exit(); | bool test_furi_ac_switch_exit(); | ||||||
| bool test_furi_pipe_record(); | 
 | ||||||
| bool test_furi_holding_data(); |  | ||||||
| bool test_furi_concurrent_access(); |  | ||||||
| bool test_furi_nonexistent_data(); | bool test_furi_nonexistent_data(); | ||||||
| bool test_furi_mute_algorithm(); | bool test_furi_mute_algorithm(); | ||||||
| 
 | 
 | ||||||
|  | // v2 tests
 | ||||||
|  | void test_furi_create_open(); | ||||||
|  | void test_furi_valuemutex(); | ||||||
|  | void test_furi_concurrent_access(); | ||||||
|  | 
 | ||||||
| static int foo = 0; | static int foo = 0; | ||||||
| 
 | 
 | ||||||
| void test_setup(void) { | void test_setup(void) { | ||||||
| @ -34,27 +37,22 @@ MU_TEST(mu_test_furi_ac_switch_exit) { | |||||||
|     mu_assert_int_eq(test_furi_ac_switch_exit(), true); |     mu_assert_int_eq(test_furi_ac_switch_exit(), true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(mu_test_furi_pipe_record) { |  | ||||||
|     mu_assert_int_eq(test_furi_pipe_record(), true); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MU_TEST(mu_test_furi_holding_data) { |  | ||||||
|     mu_assert_int_eq(test_furi_holding_data(), true); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MU_TEST(mu_test_furi_concurrent_access) { |  | ||||||
|     mu_assert_int_eq(test_furi_concurrent_access(), true); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MU_TEST(mu_test_furi_nonexistent_data) { | MU_TEST(mu_test_furi_nonexistent_data) { | ||||||
|     mu_assert_int_eq(test_furi_nonexistent_data(), true); |     mu_assert_int_eq(test_furi_nonexistent_data(), true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | // v2 tests
 | ||||||
| MU_TEST(mu_test_furi_mute_algorithm) { | MU_TEST(mu_test_furi_create_open) { | ||||||
|     mu_assert_int_eq(test_furi_mute_algorithm(test_log), true); |     test_furi_create_open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mu_test_furi_valuemutex) { | ||||||
|  |     test_furi_valuemutex(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mu_test_furi_concurrent_access) { | ||||||
|  |     test_furi_concurrent_access(); | ||||||
| } | } | ||||||
| */ |  | ||||||
| 
 | 
 | ||||||
| MU_TEST_SUITE(test_suite) { | MU_TEST_SUITE(test_suite) { | ||||||
|     MU_SUITE_CONFIGURE(&test_setup, &test_teardown); |     MU_SUITE_CONFIGURE(&test_setup, &test_teardown); | ||||||
| @ -62,11 +60,14 @@ MU_TEST_SUITE(test_suite) { | |||||||
|     MU_RUN_TEST(test_check); |     MU_RUN_TEST(test_check); | ||||||
|     MU_RUN_TEST(mu_test_furi_ac_create_kill); |     MU_RUN_TEST(mu_test_furi_ac_create_kill); | ||||||
|     MU_RUN_TEST(mu_test_furi_ac_switch_exit); |     MU_RUN_TEST(mu_test_furi_ac_switch_exit); | ||||||
|     MU_RUN_TEST(mu_test_furi_pipe_record); | 
 | ||||||
|     MU_RUN_TEST(mu_test_furi_holding_data); |  | ||||||
|     MU_RUN_TEST(mu_test_furi_concurrent_access); |  | ||||||
|     MU_RUN_TEST(mu_test_furi_nonexistent_data); |     MU_RUN_TEST(mu_test_furi_nonexistent_data); | ||||||
|     // MU_RUN_TEST(mu_test_furi_mute_algorithm);
 | 
 | ||||||
|  |     // v2 tests
 | ||||||
|  |     MU_RUN_TEST(mu_test_furi_create_open); | ||||||
|  |     MU_RUN_TEST(mu_test_furi_valuemutex); | ||||||
|  |     MU_RUN_TEST(mu_test_furi_concurrent_access); | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int run_minunit() { | int run_minunit() { | ||||||
|  | |||||||
							
								
								
									
										47
									
								
								core/api-basic/flapp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								core/api-basic/flapp.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "flipper.h" | ||||||
|  | 
 | ||||||
|  | // == Flipper Application control (flapp) ==
 | ||||||
|  | 
 | ||||||
|  | typedef FlappHandler uint32_t; // TODO
 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | simply starts application. It call `app` entrypoint with `param` passed as argument | ||||||
|  | Useful for daemon applications and pop-up. | ||||||
|  | */ | ||||||
|  | FlappHandler* flapp_start(void(app*)(void*), char* name, void* param); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | swtich to other application. | ||||||
|  | System **stop current app**, call `app` entrypoint with `param` passed | ||||||
|  | as argument and save current application entrypoint to `prev` field in | ||||||
|  | current application registry. Useful for UI or "active" application. | ||||||
|  | */ | ||||||
|  | FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Exit application | ||||||
|  | stop current application (stop thread and clear application's stack), | ||||||
|  | start application from `prev` entry in current application registry, | ||||||
|  | cleanup current application registry. | ||||||
|  | */ | ||||||
|  | void flapp_exit(void* param); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | stop specified `app` without returning to `prev` application. | ||||||
|  | */ | ||||||
|  | bool flapp_kill(FlappHandler* app); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | If case one app depend on other, notify that app is ready. | ||||||
|  | */ | ||||||
|  | void flapp_ready(); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Register on-exit callback. | ||||||
|  | It called before app will be killed. | ||||||
|  | Not recommended to use in user scenario, only for system purpose | ||||||
|  | (unregister callbacks, release mutexes, etc.) | ||||||
|  | */ | ||||||
|  | bool flapp_on_exit(void(cb*)(void*), void* ctx); | ||||||
							
								
								
									
										14
									
								
								core/api-basic/furi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								core/api-basic/furi.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | #include "furi.h" | ||||||
|  | #include "furi-deprecated.h" | ||||||
|  | 
 | ||||||
|  | bool furi_create(const char* name, void* ptr) { | ||||||
|  |     return furi_create_deprecated(name, ptr, sizeof(size_t)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void* furi_open(const char* name) { | ||||||
|  |     FuriRecordSubscriber* record = furi_open_deprecated(name, false, false, NULL, NULL, NULL); | ||||||
|  |     void* res = furi_take(record); | ||||||
|  |     furi_give(record); | ||||||
|  | 
 | ||||||
|  |     return res; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								core/api-basic/furi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								core/api-basic/furi.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "flipper.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | == Flipper universal registry implementation (FURI) == | ||||||
|  | 
 | ||||||
|  | ## Requirements | ||||||
|  | 
 | ||||||
|  | * start daemon app | ||||||
|  | * kill app | ||||||
|  | * start child thread (kill when parent app was killed) | ||||||
|  | * switch between UI apps | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Create record. | ||||||
|  | creates new record in registry and store pointer into it | ||||||
|  | */ | ||||||
|  | bool furi_create(const char* name, void* ptr); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Open record. | ||||||
|  | get stored pointer by its name | ||||||
|  | */ | ||||||
|  | void* furi_open(const char* name); | ||||||
							
								
								
									
										48
									
								
								core/api-basic/pubsub.c.unimplemented
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								core/api-basic/pubsub.c.unimplemented
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | #include "pubsub.h" | ||||||
|  | 
 | ||||||
|  | void init_pubsub(PubSub* pubsub) { | ||||||
|  |     pubsub->count = 0; | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) { | ||||||
|  |         pubsub->items[i]. | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO add mutex to reconfigurate PubSub | ||||||
|  | PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) { | ||||||
|  |     if(pubsub->count >= NUM_OF_CALLBACKS) return NULL; | ||||||
|  | 
 | ||||||
|  |     pubsub->count++; | ||||||
|  |     PubSubItem* current = pubsub->items[pubsub->count]; | ||||||
|  |      | ||||||
|  |     current->cb = cb; | ||||||
|  |     currrnt->ctx = ctx; | ||||||
|  | 
 | ||||||
|  |     pubsub->ids[pubsub->count].self = pubsub; | ||||||
|  |     pubsub->ids[pubsub->count].item = current; | ||||||
|  | 
 | ||||||
|  |     flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count])); | ||||||
|  |      | ||||||
|  |     return current; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void unsubscribe_pubsub(PubSubId* pubsub_id) { | ||||||
|  |     // TODO: add, and rearrange all items to keep subscribers item continuous | ||||||
|  |     // TODO: keep ids link actual | ||||||
|  |     // TODO: also add mutex on every pubsub changes | ||||||
|  | 
 | ||||||
|  |     // trivial implementation for NUM_OF_CALLBACKS = 1 | ||||||
|  |     if(NUM_OF_CALLBACKS != 1) return; | ||||||
|  | 
 | ||||||
|  |     if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return; | ||||||
|  | 
 | ||||||
|  |     pubsub_id->self->count = 0; | ||||||
|  |     pubsub_id->item = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void notify_pubsub(PubSub* pubsub, void* arg) { | ||||||
|  |     // iterate over subscribers | ||||||
|  |     for(size_t i = 0; i < pubsub->count; i++) { | ||||||
|  |         pubsub->items[i]->cb(arg, pubsub->items[i]->ctx); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								core/api-basic/pubsub.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								core/api-basic/pubsub.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "flipper.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | == PubSub == | ||||||
|  | 
 | ||||||
|  | PubSub allows users to subscribe on notifies and notify subscribers. | ||||||
|  | Notifier side can pass `void*` arg to subscriber callback, | ||||||
|  | and also subscriber can set `void*` context pointer that pass into | ||||||
|  | callback (you can see callback signature below). | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | typedef void(PubSubCallback*)(void*, void*); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSubCallback cb; | ||||||
|  |     void* ctx; | ||||||
|  | } PubSubItem; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSub* self; | ||||||
|  |     PubSubItem* item; | ||||||
|  | } PubSubId; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSubItem items[NUM_OF_CALLBACKS]; | ||||||
|  |     PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
 | ||||||
|  |     size_t count; ///< count of callbacks
 | ||||||
|  | } PubSub; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | To create PubSub you should create PubSub instance and call `init_pubsub`. | ||||||
|  | */ | ||||||
|  | void init_pubsub(PubSub* pubsub); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Use `subscribe_pubsub` to register your callback. | ||||||
|  | */ | ||||||
|  | PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Use `unsubscribe_pubsub` to unregister callback. | ||||||
|  | */ | ||||||
|  | void unsubscribe_pubsub(PubSubId* pubsub_id); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Use `notify_pubsub` to notify subscribers. | ||||||
|  | */ | ||||||
|  | void notify_pubsub(PubSub* pubsub, void* arg); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // MANIFEST
 | ||||||
|  | // name="test"
 | ||||||
|  | // stack=128
 | ||||||
|  | 
 | ||||||
|  | void example_pubsub_handler(void* arg, void* ctx) { | ||||||
|  |     printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pubsub_test() { | ||||||
|  |     const char* app_name = "test app"; | ||||||
|  | 
 | ||||||
|  |     PubSub example_pubsub; | ||||||
|  |     init_pubsub(&example_pubsub); | ||||||
|  | 
 | ||||||
|  |     if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint32_t counter = 0; | ||||||
|  |     while(1) { | ||||||
|  |         notify_pubsub(&example_pubsub, (void*)&counter); | ||||||
|  |         counter++; | ||||||
|  | 
 | ||||||
|  |         osDelay(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | */ | ||||||
							
								
								
									
										24
									
								
								core/api-basic/value-expanders.c.unimplemented
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								core/api-basic/value-expanders.c.unimplemented
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | #include "value-expanders.h" | ||||||
|  | 
 | ||||||
|  | bool commit_managed(ValueManager* managed, void* value) { | ||||||
|  |     if(value != managed->mutex->value) return false; | ||||||
|  | 
 | ||||||
|  |     notify_pubsub(&managed->pubsub, value); | ||||||
|  |      | ||||||
|  |     if(!osMutexGive(managed->mutex)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(managed->mutex, timeout); | ||||||
|  |     if(value == NULL) return false; | ||||||
|  | 
 | ||||||
|  |     memcpy(value, data, len): | ||||||
|  | 
 | ||||||
|  |     notify_pubsub(&managed->pubsub, value); | ||||||
|  | 
 | ||||||
|  |     if(!release_mutex(managed->mutex, value)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								core/api-basic/value-expanders.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								core/api-basic/value-expanders.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "flipper.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | == Value composer == | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | typedef void(ValueComposerCallback)(void* ctx, void* state); | ||||||
|  | 
 | ||||||
|  | void COPY_COMPOSE(void* ctx, void* state) { | ||||||
|  |     read_mutex((ValueMutex*)ctx, state, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     UiLayerBelowNotify | ||||||
|  |     UiLayerNotify, | ||||||
|  |     UiLayerAboveNotify | ||||||
|  | } UiLayer; | ||||||
|  | 
 | ||||||
|  | ValueComposerHandle* add_compose_layer( | ||||||
|  |     ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | bool remove_compose_layer(ValueComposerHandle* handle); | ||||||
|  | 
 | ||||||
|  | void request_compose(ValueComposerHandle* handle); | ||||||
|  | 
 | ||||||
|  | // See [LED](LED-API) or [Display](Display-API) API for examples.
 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | == ValueManager == | ||||||
|  | 
 | ||||||
|  | More complicated concept is ValueManager. | ||||||
|  | It is like ValueMutex, but user can subscribe to value updates. | ||||||
|  | 
 | ||||||
|  | First of all you can use value and pubsub part as showing above: | ||||||
|  | aquire/release mutex, read value, subscribe/unsubscribe pubsub. | ||||||
|  | There are two specific methods for ValueManager: write_managed, commit_managed | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex value; | ||||||
|  |     PubSub pubsub; | ||||||
|  | } ValueManager; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | acquire value, changes it and send notify with current value. | ||||||
|  | */ | ||||||
|  | bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | commit_managed works as `release_mutex` but send notify with current value. | ||||||
|  | */ | ||||||
|  | bool commit_managed(ValueManager* managed, void* value); | ||||||
							
								
								
									
										52
									
								
								core/api-basic/valuemutex.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								core/api-basic/valuemutex.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | #include "valuemutex.h" | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { | ||||||
|  |     // mutex without name,
 | ||||||
|  |     // no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
 | ||||||
|  |     // with dynamic memory allocation
 | ||||||
|  |     const osMutexAttr_t value_mutext_attr = { | ||||||
|  |         .name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U}; | ||||||
|  | 
 | ||||||
|  |     valuemutex->mutex = osMutexNew(&value_mutext_attr); | ||||||
|  |     if(valuemutex->mutex == NULL) return false; | ||||||
|  | 
 | ||||||
|  |     valuemutex->value = value; | ||||||
|  |     valuemutex->size = size; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { | ||||||
|  |     if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) { | ||||||
|  |         return valuemutex->value; | ||||||
|  |     } else { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool release_mutex(ValueMutex* valuemutex, void* value) { | ||||||
|  |     if(value != valuemutex->value) return false; | ||||||
|  | 
 | ||||||
|  |     if(osMutexRelease(valuemutex->mutex) != osOK) return false; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(valuemutex, timeout); | ||||||
|  |     if(value == NULL || len > valuemutex->size) return false; | ||||||
|  |     memcpy(data, value, len > 0 ? len : valuemutex->size); | ||||||
|  |     if(!release_mutex(valuemutex, value)) return false; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(valuemutex, timeout); | ||||||
|  |     if(value == NULL || len > valuemutex->size) return false; | ||||||
|  |     memcpy(value, data, len > 0 ? len : valuemutex->size); | ||||||
|  |     if(!release_mutex(valuemutex, value)) return false; | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								core/api-basic/valuemutex.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								core/api-basic/valuemutex.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "flipper.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | == ValueMutex == | ||||||
|  | 
 | ||||||
|  | The most simple concept is ValueMutex. | ||||||
|  | It is wrapper around mutex and value pointer. | ||||||
|  | You can take and give mutex to work with value and read and write value. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     void* value; | ||||||
|  |     size_t size; | ||||||
|  |     osMutexId_t mutex; | ||||||
|  | } ValueMutex; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Creates ValueMutex. | ||||||
|  | */ | ||||||
|  | bool init_mutex(ValueMutex* valuemutex, void* value, size_t size); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Call for work with data stored in mutex. | ||||||
|  | Returns pointer to data if success, NULL otherwise. | ||||||
|  | */ | ||||||
|  | void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Helper: infinitly wait for mutex | ||||||
|  | */ | ||||||
|  | static inline void* acquire_mutex_block(ValueMutex* valuemutex) { | ||||||
|  |     return acquire_mutex(valuemutex, osWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Release mutex after end of work with data. | ||||||
|  | Call `release_mutex` and pass ValueData instance and pointer to data. | ||||||
|  | */ | ||||||
|  | bool release_mutex(ValueMutex* valuemutex, void* value); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. | ||||||
|  | Both functions return true in case of success, false otherwise. | ||||||
|  | */ | ||||||
|  | bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); | ||||||
|  | 
 | ||||||
|  | bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); | ||||||
|  | 
 | ||||||
|  | inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { | ||||||
|  |     return write_mutex(valuemutex, data, len, osWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { | ||||||
|  |     return read_mutex(valuemutex, data, len, osWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | 
 | ||||||
|  | Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // MANIFEST
 | ||||||
|  | // name="example-provider-app"
 | ||||||
|  | // stack=128
 | ||||||
|  | 
 | ||||||
|  | void provider_app(void* _p) { | ||||||
|  |     // create record with mutex
 | ||||||
|  |     uint32_t example_value = 0; | ||||||
|  |     ValueMutex example_mutex; | ||||||
|  |     // call `init_mutex`.
 | ||||||
|  |     if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(furi_create("provider/example", (void*)&example_mutex)) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // we are ready to provide record to other apps
 | ||||||
|  |     flapp_ready(); | ||||||
|  | 
 | ||||||
|  |     // get value and increment it
 | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever); | ||||||
|  |         if(value != NULL) { | ||||||
|  |             value++; | ||||||
|  |         } | ||||||
|  |         release_mutex(&example_mutex, value); | ||||||
|  | 
 | ||||||
|  |         osDelay(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MANIFEST
 | ||||||
|  | // name="example-consumer-app"
 | ||||||
|  | // stack=128
 | ||||||
|  | // require="example-provider-app"
 | ||||||
|  | void consumer_app(void* _p) { | ||||||
|  |     // this app run after flapp_ready call in all requirements app
 | ||||||
|  | 
 | ||||||
|  |     // open mutex value
 | ||||||
|  |     ValueMutex* counter_mutex = furi_open("provider/example"); | ||||||
|  |     if(counter_mutex == NULL) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // continously read value every 1s
 | ||||||
|  |     uint32_t counter; | ||||||
|  |     while(1) { | ||||||
|  |         if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { | ||||||
|  |             printf("counter value: %d\n", counter); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         osDelay(1000); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | */ | ||||||
							
								
								
									
										133
									
								
								core/api-hal/api-spi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								core/api-hal/api-spi.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | |||||||
|  | #include "flipper_v2.h" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | struct used for handling SPI info. | ||||||
|  | */ | ||||||
|  | typedef struct { | ||||||
|  |     SPI_HandleTypeDef* spi; | ||||||
|  |     PubSubCallback cb; | ||||||
|  |     void* ctx; | ||||||
|  | } SpiHandle; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | For transmit/receive data use `spi_xfer` function. | ||||||
|  | 
 | ||||||
|  | * `tx_data` and `rx_data` size must be equal (and equal `len`) | ||||||
|  | * `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback. | ||||||
|  | */ | ||||||
|  | bool spi_xfer( | ||||||
|  |     SPI_HandleTypeDef* spi, | ||||||
|  |     uint8_t* tx_data, uint8_t* rx_data, size_t len, | ||||||
|  |     PubSubCallback cb, void* ctx); | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Blocking verison: | ||||||
|  | */ | ||||||
|  | static inline bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) { | ||||||
|  |     semaphoreInfo s; | ||||||
|  |     osSemaphore block = createSemaphoreStatic(s); | ||||||
|  |     if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) { | ||||||
|  |         osReleaseSemaphore(block); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     osWaitSemaphore(block); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Common implementation of SPI bus: serial interface + CS pin | ||||||
|  | */ | ||||||
|  | typedef struct { | ||||||
|  |     GpioPin* cs; ///< CS pin
 | ||||||
|  |     ValueMutex* spi; ///< <SpiHandle*>
 | ||||||
|  | } SpiBus; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | For dedicated work with one device there is `SpiDevice` entity. | ||||||
|  | It contains ValueMutex around SpiBus: after you acquire device | ||||||
|  | you can acquire spi to work with it (don't forget SPI bus is shared | ||||||
|  | around many device, release it after every transaction as quick as possible). | ||||||
|  | */ | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <SpiBus*>
 | ||||||
|  | } SpiDevice; | ||||||
|  | 
 | ||||||
|  | ## SPI IRQ device | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. | ||||||
|  | For work with it there is special entity `SpiIrqDevice`. | ||||||
|  | Use `subscribe_pubsub` for subscribinq to irq events. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <SpiBus*>
 | ||||||
|  |     PubSub* irq; | ||||||
|  | } SpiIrqDevice; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | Special implementation of SPI bus: serial interface + CS, Res, D/I lines. | ||||||
|  | */ | ||||||
|  | typedef struct { | ||||||
|  |     GpioPin* cs; ///< CS pin
 | ||||||
|  |     GpioPin* res; ///< reset pin
 | ||||||
|  |     GpioPin* di; ///< D/I pin
 | ||||||
|  |     ValueMutex* spi; ///< <SPI_HandleTypeDef*>
 | ||||||
|  | } DisplayBus; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <DisplayBus*>
 | ||||||
|  | } DisplayDevice; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  | # SPI devices (F2) | ||||||
|  | 
 | ||||||
|  | * `/dev/sdcard` - SD card SPI, `SpiDevice` | ||||||
|  | * `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice` | ||||||
|  | * `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice` | ||||||
|  | * `/dev/display` - `DisplayDevice` | ||||||
|  | * `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7) | ||||||
|  | 
 | ||||||
|  | ### Application example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // Be careful, this function called from IRQ context
 | ||||||
|  | void handle_irq(void* _arg, void* _ctx) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void cc1101_example() { | ||||||
|  |     SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus"); | ||||||
|  |     if(cc1101_device == NULL) return; // bus not available, critical error
 | ||||||
|  | 
 | ||||||
|  |     subscribe_pubsub(cc1101_device->irq, handle_irq, NULL); | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         // acquire device as device bus
 | ||||||
|  |         SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0); | ||||||
|  |         if(spi_bus == NULL) { | ||||||
|  |             printf("Device busy\n"); | ||||||
|  |             // wait for device
 | ||||||
|  |             spi_bus = acquire_mutex_block(cc1101_device->bus); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // make transaction
 | ||||||
|  |         uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF}; | ||||||
|  |         uint8_t response[4]; | ||||||
|  | 
 | ||||||
|  |         { | ||||||
|  |             SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi); | ||||||
|  | 
 | ||||||
|  |             gpio_write(spi_bus->cs, false); | ||||||
|  |             spi_xfer_block(spi, request, response, 4); | ||||||
|  |             gpio_write(spi_bus->cs, true); | ||||||
|  | 
 | ||||||
|  |             release_mutex(cc1101_device->spi, spi); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // release device (device bus)
 | ||||||
|  |         release_mutex(cc1101_device->bus, spi_bus); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | */ | ||||||
| @ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| extern "C" { | extern "C" { | ||||||
| #include "flipper.h" | #include "flipper.h" | ||||||
| #include "furi.h" |  | ||||||
| #include "log.h" | #include "log.h" | ||||||
| #include "startup.h" | #include "startup.h" | ||||||
| #include "tty_uart.h" | #include "tty_uart.h" | ||||||
|  | |||||||
| @ -3,4 +3,5 @@ CORE_DIR		= $(PROJECT_ROOT)/core | |||||||
| CFLAGS			+= -I$(CORE_DIR) | CFLAGS			+= -I$(CORE_DIR) | ||||||
| ASM_SOURCES		+= $(wildcard $(CORE_DIR)/*.s) | ASM_SOURCES		+= $(wildcard $(CORE_DIR)/*.s) | ||||||
| C_SOURCES		+= $(wildcard $(CORE_DIR)/*.c) | C_SOURCES		+= $(wildcard $(CORE_DIR)/*.c) | ||||||
|  | C_SOURCES		+= $(wildcard $(CORE_DIR)/api-basic/*.c) | ||||||
| CPP_SOURCES		+= $(wildcard $(CORE_DIR)/*.cpp) | CPP_SOURCES		+= $(wildcard $(CORE_DIR)/*.cpp) | ||||||
|  | |||||||
| @ -7,7 +7,8 @@ extern "C" { | |||||||
| #include "main.h" | #include "main.h" | ||||||
| #include "flipper_hal.h" | #include "flipper_hal.h" | ||||||
| #include "cmsis_os.h" | #include "cmsis_os.h" | ||||||
| #include "furi.h" | #include "furi-deprecated.h" | ||||||
|  | 
 | ||||||
| #include "log.h" | #include "log.h" | ||||||
| #include "input/input.h" | #include "input/input.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								core/flipper_v2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								core/flipper_v2.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "api-basic/furi.h" | ||||||
|  | //#include "api-basic/flapp.h"
 | ||||||
|  | #include "cmsis_os2.h" | ||||||
|  | #include "api-basic/valuemutex.h" | ||||||
|  | //#include "api-basic/pubsub.h"
 | ||||||
| @ -1,5 +1,4 @@ | |||||||
| #include "furi.h" | #include "furi-deprecated.h" | ||||||
| #include "cmsis_os.h" |  | ||||||
| #include <string.h> | #include <string.h> | ||||||
| 
 | 
 | ||||||
| // TODO: this file contains printf, that not implemented on uC target
 | // TODO: this file contains printf, that not implemented on uC target
 | ||||||
| @ -28,7 +27,7 @@ static FuriRecord* find_record(const char* name) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: change open-create to only open
 | // TODO: change open-create to only open
 | ||||||
| bool furi_create(const char* name, void* value, size_t size) { | bool furi_create_deprecated(const char* name, void* value, size_t size) { | ||||||
| #ifdef FURI_DEBUG | #ifdef FURI_DEBUG | ||||||
|     printf("[FURI] creating %s record\n", name); |     printf("[FURI] creating %s record\n", name); | ||||||
| #endif | #endif | ||||||
| @ -73,7 +72,7 @@ bool furi_create(const char* name, void* value, size_t size) { | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FuriRecordSubscriber* furi_open( | FuriRecordSubscriber* furi_open_deprecated( | ||||||
|     const char* name, |     const char* name, | ||||||
|     bool solo, |     bool solo, | ||||||
|     bool no_mute, |     bool no_mute, | ||||||
| @ -94,7 +93,7 @@ FuriRecordSubscriber* furi_open( | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|         // create record if not exist
 |         // create record if not exist
 | ||||||
|         if(!furi_create(name, NULL, 0)) { |         if(!furi_create_deprecated(name, NULL, 0)) { | ||||||
|             return NULL; |             return NULL; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -127,7 +127,7 @@ If NULL, create FURI Pipe (only callbacks management, no data/mutex) | |||||||
| 
 | 
 | ||||||
| Returns false if registry have not enough memory for creating. | Returns false if registry have not enough memory for creating. | ||||||
| */ | */ | ||||||
| bool furi_create(const char* name, void* value, size_t size); | bool furi_create_deprecated(const char* name, void* value, size_t size); | ||||||
| 
 | 
 | ||||||
| /*!
 | /*!
 | ||||||
| Opens existing FURI record by name. | Opens existing FURI record by name. | ||||||
| @ -137,7 +137,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. | ||||||
| */ | */ | ||||||
| FuriRecordSubscriber* furi_open( | FuriRecordSubscriber* furi_open_deprecated( | ||||||
|     const char* name, |     const char* name, | ||||||
|     bool solo, |     bool solo, | ||||||
|     bool no_mute, |     bool no_mute, | ||||||
| @ -1,5 +1,4 @@ | |||||||
| #include "furi.h" | #include "flipper.h" | ||||||
| #include "cmsis_os.h" |  | ||||||
| 
 | 
 | ||||||
| // TODO: this file contains printf, that not implemented on uC target
 | // TODO: this file contains printf, that not implemented on uC target
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| #include <string.h> | #include <string.h> | ||||||
| 
 | 
 | ||||||
| #include "log.h" | #include "log.h" | ||||||
| #include "furi.h" | #include "flipper.h" | ||||||
| 
 | 
 | ||||||
| #define PRINT_STR_SIZE 64 | #define PRINT_STR_SIZE 64 | ||||||
| 
 | 
 | ||||||
| @ -21,5 +21,5 @@ void fuprintf(FuriRecordSubscriber* f, const char* format, ...) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FuriRecordSubscriber* get_default_log() { | FuriRecordSubscriber* get_default_log() { | ||||||
|     return furi_open("tty", false, false, NULL, NULL, NULL); |     return furi_open_deprecated("tty", false, false, NULL, NULL, NULL); | ||||||
| } | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "furi.h" | #include "flipper.h" | ||||||
| 
 | 
 | ||||||
| FuriRecordSubscriber* get_default_log(); | FuriRecordSubscriber* get_default_log(); | ||||||
| void fuprintf(FuriRecordSubscriber* f, const char* format, ...); | void fuprintf(FuriRecordSubscriber* f, const char* format, ...); | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #define _GNU_SOURCE | #define _GNU_SOURCE | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include "furi.h" | #include "flipper.h" | ||||||
| #include "main.h" | #include "main.h" | ||||||
| 
 | 
 | ||||||
| extern UART_HandleTypeDef DEBUG_UART; | extern UART_HandleTypeDef DEBUG_UART; | ||||||
| @ -12,7 +12,7 @@ void handle_uart_write(const void* data, size_t size, void* ctx) { | |||||||
| static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { | static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { | ||||||
|     FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0); |     FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0); | ||||||
|     if(log == NULL) { |     if(log == NULL) { | ||||||
|         log = furi_open("tty", false, false, NULL, NULL, NULL); |         log = furi_open_deprecated("tty", false, false, NULL, NULL, NULL); | ||||||
|         if(log == NULL) { |         if(log == NULL) { | ||||||
|             return -1; |             return -1; | ||||||
|         } |         } | ||||||
| @ -33,11 +33,11 @@ static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool register_tty_uart() { | bool register_tty_uart() { | ||||||
|     if(!furi_create("tty", NULL, 0)) { |     if(!furi_create_deprecated("tty", NULL, 0)) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(furi_open("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { |     if(furi_open_deprecated("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -68,3 +68,31 @@ void* pvTaskGetThreadLocalStoragePointer(TaskHandle_t xTaskToQuery, BaseType_t x | |||||||
| void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue); | void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue); | ||||||
| 
 | 
 | ||||||
| QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize); | QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |   const char                   *name;   ///< name of the mutex
 | ||||||
|  |   uint32_t                 attr_bits;   ///< attribute bits
 | ||||||
|  |   void                      *cb_mem;    ///< memory for control block
 | ||||||
|  |   uint32_t                   cb_size;   ///< size of provided memory for control block
 | ||||||
|  | } osMutexAttr_t; | ||||||
|  | 
 | ||||||
|  | typedef SemaphoreHandle_t osMutexId_t; | ||||||
|  | 
 | ||||||
|  | osMutexId_t osMutexNew(const osMutexAttr_t *attr); | ||||||
|  | 
 | ||||||
|  | /// Status code values returned by CMSIS-RTOS functions.
 | ||||||
|  | typedef enum { | ||||||
|  |   osOK                      =  0,         ///< Operation completed successfully.
 | ||||||
|  |   osError                   = -1,         ///< Unspecified RTOS error: run-time error but no other error message fits.
 | ||||||
|  |   osErrorTimeout            = -2,         ///< Operation not completed within the timeout period.
 | ||||||
|  |   osErrorResource           = -3,         ///< Resource not available.
 | ||||||
|  |   osErrorParameter          = -4,         ///< Parameter error.
 | ||||||
|  |   osErrorNoMemory           = -5,         ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
 | ||||||
|  |   osErrorISR                = -6,         ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
 | ||||||
|  |   osStatusReserved          = 0x7FFFFFFF  ///< Prevents enum down-size compiler optimization.
 | ||||||
|  | } osStatus_t; | ||||||
|  | 
 | ||||||
|  | osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout); | ||||||
|  | osStatus_t osMutexRelease (osMutexId_t mutex_id); | ||||||
|  | 
 | ||||||
|  | #define osWaitForever portMAX_DELAY | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								firmware/targets/local/Inc/cmsis_os2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								firmware/targets/local/Inc/cmsis_os2.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | #include "cmsis_os.h" | ||||||
| @ -230,3 +230,26 @@ void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xInde | |||||||
|     pthread_setspecific(tls_keys[xIndex], pvValue); |     pthread_setspecific(tls_keys[xIndex], pvValue); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | osMutexId_t osMutexNew(const osMutexAttr_t *attr) { | ||||||
|  |     StaticSemaphore_t* pxMutexBuffer = malloc(sizeof(StaticSemaphore_t)); | ||||||
|  |     xSemaphoreCreateMutexStatic(pxMutexBuffer); | ||||||
|  | 
 | ||||||
|  |     return (osMutexId_t)pxMutexBuffer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) { | ||||||
|  |     if(xSemaphoreTake((SemaphoreHandle_t)mutex_id, (TickType_t)timeout) == pdTRUE) { | ||||||
|  |         return osOK; | ||||||
|  |     } else { | ||||||
|  |         return osErrorTimeout; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | osStatus_t osMutexRelease (osMutexId_t mutex_id) { | ||||||
|  |     if(xSemaphoreGive((SemaphoreHandle_t)mutex_id) == pdTRUE) { | ||||||
|  |         return osOK; | ||||||
|  |     } else { | ||||||
|  |         return osError; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,6 +2,10 @@ LIB_DIR			= $(PROJECT_ROOT)/lib | |||||||
| 
 | 
 | ||||||
| CFLAGS			+= -I$(LIB_DIR) | CFLAGS			+= -I$(LIB_DIR) | ||||||
| 
 | 
 | ||||||
|  | # Mlib containers
 | ||||||
|  | CFLAGS			+= -I$(LIB_DIR)/mlib | ||||||
|  | 
 | ||||||
|  | # U8G2 display library
 | ||||||
| U8G2_DIR		= $(LIB_DIR)/u8g2 | U8G2_DIR		= $(LIB_DIR)/u8g2 | ||||||
| CFLAGS			+= -I$(U8G2_DIR) | CFLAGS			+= -I$(U8G2_DIR) | ||||||
| C_SOURCES		+= $(U8G2_DIR)/u8x8_d_st7565.c | C_SOURCES		+= $(U8G2_DIR)/u8x8_d_st7565.c | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								lib/mlib
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								lib/mlib
									
									
									
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Subproject commit eb7556f88faf0bbfd6a4ae99a3d53dcbe2064b88 | ||||||
| @ -1,13 +1,27 @@ | |||||||
| # Basic concepts: | # [Basic concepts](Basic-API) | ||||||
| 
 | 
 | ||||||
| * ValueMutex | * ValueMutex | ||||||
| * PubSub, Publisher, Subscriber | * PubSub | ||||||
| * ValueManager | * ValueManager | ||||||
| * LayeredReducer | * ValueComposer | ||||||
| 
 | 
 | ||||||
| # HAL | # [HAL and devices](HAL-API) | ||||||
| 
 | 
 | ||||||
| We use [Zephyr HAL](https://docs.zephyrproject.org/latest/reference/peripherals/index.html). | * GPIO | ||||||
|  | * PWM | ||||||
|  | * ADC | ||||||
|  | * I2C | ||||||
|  | 
 | ||||||
|  | * IR RX (unimplemented) | ||||||
|  | * Comparator RX (touch key and RFID 125 kHz RX) (unimplemented) | ||||||
|  | 
 | ||||||
|  | # [SPI Devices](SPI-Devices-API.md) | ||||||
|  | 
 | ||||||
|  | * Sub-GHz chip | ||||||
|  | * NFC | ||||||
|  | * SD card | ||||||
|  | * display | ||||||
|  | * external SPI | ||||||
| 
 | 
 | ||||||
| # OS | # OS | ||||||
| 
 | 
 | ||||||
| @ -15,68 +29,20 @@ We use [CMSIS OS v2](https://www.keil.com/pack/doc/CMSIS_Dev/RTOS2/html/group__C | |||||||
| 
 | 
 | ||||||
| # UI | # UI | ||||||
| 
 | 
 | ||||||
|  | * **[Input](Input-API)** | ||||||
| 
 | 
 | ||||||
| * **[Input](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Input)** | * **[Display](Display-API)** | ||||||
| 
 | 
 | ||||||
| * **[Display](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Display)** | * **[LED](LED-API)** | ||||||
| 
 | 
 | ||||||
| * **[LED](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:LED)** | * **[Backlight](Backlight-API)** (unimplemented) | ||||||
| 
 | 
 | ||||||
| * **vibro** | # [Power](Power-API) | ||||||
| 
 | 
 | ||||||
| * **[Sound](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Sound)** | * batt voltage | ||||||
|  | * batt charge | ||||||
| 
 | 
 | ||||||
| * **backlight** | # [UART](Serial-API) | ||||||
| 
 |  | ||||||
| # System |  | ||||||
| 
 |  | ||||||
| ## batt voltage |  | ||||||
| 
 |  | ||||||
| ## batt charge |  | ||||||
| 
 |  | ||||||
| # CC1101 |  | ||||||
| 
 |  | ||||||
| ## SPI |  | ||||||
| 
 |  | ||||||
| ## IRQ |  | ||||||
| 
 |  | ||||||
| # SD Card |  | ||||||
| 
 |  | ||||||
| ## SPI |  | ||||||
| 
 |  | ||||||
| # NFC |  | ||||||
| 
 |  | ||||||
| ## SPI |  | ||||||
| 
 |  | ||||||
| ## IRQ |  | ||||||
| 
 |  | ||||||
| # IR |  | ||||||
| 
 |  | ||||||
| ## TX LED |  | ||||||
| 
 |  | ||||||
| ## RX ADC |  | ||||||
| 
 |  | ||||||
| # RFID 125 kHz |  | ||||||
| 
 |  | ||||||
| ## Carrier |  | ||||||
| 
 |  | ||||||
| ## Pull |  | ||||||
| 
 |  | ||||||
| ## Comparator RX (shared with touch key) |  | ||||||
| 
 |  | ||||||
| # Touch key |  | ||||||
| 
 |  | ||||||
| ## Pull |  | ||||||
| 
 |  | ||||||
| ## Comparator RX (shared with RFID 125 kHz) |  | ||||||
| 
 |  | ||||||
| # External GPIO |  | ||||||
| 
 |  | ||||||
| # External SPI |  | ||||||
| 
 |  | ||||||
| # External I2C |  | ||||||
| 
 |  | ||||||
| # UART |  | ||||||
| 
 | 
 | ||||||
| # USB | # USB | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,27 +6,7 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f | |||||||
| 
 | 
 | ||||||
| # Application registry and control (FURIAC) | # Application registry and control (FURIAC) | ||||||
| 
 | 
 | ||||||
| ### Start and change application wrokflow |  | ||||||
| 
 | 
 | ||||||
| **`FuriApp* furiac_start(void(app*)(void*), char* name, void* param)`** |  | ||||||
| 
 |  | ||||||
| simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **`FuriApp furiac_switch(void(app*)(void*), char* name, void* param)`** |  | ||||||
| 
 |  | ||||||
| swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application. |  | ||||||
| 
 |  | ||||||
| ### Exit application |  | ||||||
| 
 |  | ||||||
| **`void furiac_exit(void* param)`** |  | ||||||
| 
 |  | ||||||
| stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry. |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| **`bool furiac_kill(FuriApp app)`** |  | ||||||
| 
 |  | ||||||
| stop specified `app` without returning to `prev` application. |  | ||||||
| 
 | 
 | ||||||
| # Data exchange | # Data exchange | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,61 +0,0 @@ | |||||||
| All display operations based on [u8g2](https://github.com/olikraus/u8g2) library. |  | ||||||
| 
 |  | ||||||
| API available as struct, contains u8g2 functions, instance and fonts: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| typedef struct { |  | ||||||
|     ValueManager* display; /// ValueManager<u8g2_t*> |  | ||||||
|     void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t  *font); |  | ||||||
|     void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color); |  | ||||||
|     void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent); |  | ||||||
|     u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str); |  | ||||||
| 
 |  | ||||||
|     Fonts fonts; |  | ||||||
| } Display; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     const uint8_t* u8g2_font_6x10_mf; |  | ||||||
| } Fonts; |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| First of all you can open display API instance by calling `open_display` |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// Get display instance and API |  | ||||||
| inline Display* open_display(const char* name) { |  | ||||||
|     return (Display*)furi_open(name); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Default display name is `/dev/display`. |  | ||||||
| 
 |  | ||||||
| For draw something to display you can get display instance pointer by calling `take_display`, do something and commit your changes by calling `commit_display`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return pointer in case off success, NULL otherwise |  | ||||||
| inline u8g2_t* take_display(Display* api, uint32_t timeout) { |  | ||||||
|     return (u8g2_t*)take_mutex(api->display->value, timeout); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| inline void commit_display(Display* api, u8g2_t* display) { |  | ||||||
|     commit_valuemanager(api->display, display); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Usage example |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| void u8g2_example(void* p) { |  | ||||||
|     Display* display_api = open_display("/dev/display"); |  | ||||||
|     if(display_api == NULL) return; // display not available, critical error |  | ||||||
| 
 |  | ||||||
|     u8g2_t* display = take_display(display_api); |  | ||||||
|     if(display != NULL) { |  | ||||||
|         display_api->u8g2_SetFont(display, display_api->fonts.u8g2_font_6x10_mf); |  | ||||||
|         display_api->u8g2_SetDrawColor(display, 1); |  | ||||||
|         display_api->u8g2_SetFontMode(display, 1); |  | ||||||
|         display_api->u8g2_DrawStr(display, 2, 12, "hello world!"); |  | ||||||
|     } |  | ||||||
|     commit_display(display_api, display); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| @ -1,124 +0,0 @@ | |||||||
| LED state describes by struct: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| typedef struct { |  | ||||||
|     uint8_t red; |  | ||||||
|     uint8_t green; |  | ||||||
|     uint8_t blue;  |  | ||||||
| } Rgb; |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| LED API provided by struct: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| typedef struct { |  | ||||||
|     LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Rgb*> |  | ||||||
|     Subscriber* updates; /// LED value changes Supscriber<Rgb*> |  | ||||||
|     ValueMutex* state; /// LED state, ValueMutex<Rgb*> |  | ||||||
| } LedApi; |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| You can get API instance by calling `open_led`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// Add new layer to LED: |  | ||||||
| inline LedApi* open_led(const char* name) { |  | ||||||
|     return (LedApi*)furi_open(name); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Default system led is `/dev/led`. |  | ||||||
| 
 |  | ||||||
| Then add new layer to control LED by calling `add_led_layer`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| inline ValueManager* add_led_layer(Rgb* layer, uint8_t priority) { |  | ||||||
|     ValueManager* manager = register_valuemanager((void*)layer); |  | ||||||
|     if(manager == NULL) return NULL; |  | ||||||
| 
 |  | ||||||
|     if(!add_layered_reducer(manager, priority, layer_compose_default)) { |  | ||||||
|         unregister_valuemanager(manager); |  | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return manager; |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| For change led you can get display instance pointer by calling `take_led`, do something and commit your changes by calling `commit_led`. Or you can call `write_led`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return pointer in case off success, NULL otherwise |  | ||||||
| inline Rgb* take_led(ValueManager* led, uint32_t timeout) { |  | ||||||
|     return (Rgb*)take_mutex(led->value, timeout); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| inline void commit_led(ValueManager* led, Rgb* value) { |  | ||||||
|     commit_valuemanager(led, value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool write_led(ValueManager* led, Rgb* value, uint32_t timeout) { |  | ||||||
|     return write_valuemanager(state, (void*)value, sizeof(Rgb), timeout); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| To read current led state you should use `read_led` function: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool read_led(ValueManager* led, Rgb* value, uint32_t timeout) { |  | ||||||
|     return read_mutex(led->value, (void*)value, sizeof(Rgb), timeout); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Also you can subscribe to led state changes: |  | ||||||
| 
 |  | ||||||
| Use `subscribe_led_changes` to register your callback: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool subscribe_led_changes(Subscriber* updates, void(*cb)(Rgb*, void*), void* ctx) { |  | ||||||
|     return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Usage example |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| 
 |  | ||||||
| void handle_led_state(Rgb* rgb, void* _ctx) { |  | ||||||
|     printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void led_example(void* p) { |  | ||||||
|     LedApi* led_api = open_display("/dev/led"); |  | ||||||
|     if(led_api == NULL) return; // led not available, critical error |  | ||||||
| 
 |  | ||||||
|     // subscribe to led state updates |  | ||||||
|     subscribe_led_changes(led_api->updates, handle_led_state, NULL); |  | ||||||
| 
 |  | ||||||
|     Rgb current_state; |  | ||||||
|     if(read_led(led_api->state, ¤t_state, OsWaitForever)) { |  | ||||||
|         printf( |  | ||||||
|             "initial led: #%02X%02X%02X\n", |  | ||||||
|             current_state->red, |  | ||||||
|             current_state->green, |  | ||||||
|             current_state->blue |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // add layer to control led |  | ||||||
|     ValueManager* led_manager = add_led_layer(¤t_state, UI_LAYER_APP); |  | ||||||
| 
 |  | ||||||
|     // write only blue by getting pointer |  | ||||||
|     Rgb* rgb = take_led(led_manager, OsWaitForever); |  | ||||||
|     if(rgb != NULL) { |  | ||||||
|         rgb->blue = 0; |  | ||||||
|     } |  | ||||||
|     commit_led(led_manager, rgb); |  | ||||||
| 
 |  | ||||||
|     // write RGB value |  | ||||||
|     write_led(led_manager, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}), OsWaitForever); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| @ -1,122 +0,0 @@ | |||||||
| sound state describes by struct: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| typedef struct { |  | ||||||
|     float freq; /// frequency in Hz |  | ||||||
|     float width; /// pulse witdh 0...1 |  | ||||||
| } Tone; |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| sound API provided by struct: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| typedef struct { |  | ||||||
|     LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Tone*> |  | ||||||
|     Subscriber* updates; /// sound value changes Supscriber<Tone*> |  | ||||||
|     ValueMutex* state; /// sound state, ValueMutex<Tone*> |  | ||||||
| } SoundApi; |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| You can get API instance by calling `open_sound`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// Add new layer to sound: |  | ||||||
| inline SoundApi* open_sound(const char* name) { |  | ||||||
|     return (SoundApi*)furi_open(name); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Default system sound is `/dev/sound`. |  | ||||||
| 
 |  | ||||||
| Then add new layer to control sound by calling `add_sound_layer`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| inline ValueManager* add_sound_layer(Tone* layer, uint8_t priority) { |  | ||||||
|     ValueManager* manager = register_valuemanager((void*)layer); |  | ||||||
|     if(manager == NULL) return NULL; |  | ||||||
| 
 |  | ||||||
|     if(!add_layered_reducer(manager, priority, layer_compose_default)) { |  | ||||||
|         unregister_valuemanager(manager); |  | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return manager; |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| For change sound you can get display instance pointer by calling `take_sound`, do something and commit your changes by calling `commit_sound`. Or you can call `write_sound`: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return pointer in case off success, NULL otherwise |  | ||||||
| inline Tone* take_sound(ValueManager* sound, uint32_t timeout) { |  | ||||||
|     return (Tone*)take_mutex(sound->value, timeout); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| inline void commit_sound(ValueManager* sound, Tone* value) { |  | ||||||
|     commit_valuemanager(sound, value); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool write_sound(ValueManager* sound, Tone* value, uint32_t timeout) { |  | ||||||
|     return write_valuemanager(state, (void*)value, sizeof(Tone), timeout); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| To read current sound state you should use `read_sound` function: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool read_sound(ValueManager* sound, Tone* value, uint32_t timeout) { |  | ||||||
|     return read_mutex(sound->value, (void*)value, sizeof(Tone), timeout); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Also you can subscribe to sound state changes: |  | ||||||
| 
 |  | ||||||
| Use `subscribe_sound_changes` to register your callback: |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| /// return true if success, false otherwise |  | ||||||
| inline bool subscribe_sound_changes(Subscriber* updates, void(*cb)(Tone*, void*), void* ctx) { |  | ||||||
|     return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Usage example |  | ||||||
| 
 |  | ||||||
| ```C |  | ||||||
| 
 |  | ||||||
| void handle_sound_state(Tone* tone, void* _ctx) { |  | ||||||
|     printf("sound: %d Hz, %d %%\n", (uint16_t)tone->freq, (uint8_t)(tone->witdh * 100)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void sound_example(void* p) { |  | ||||||
|     soundApi* sound_api = open_display("/dev/sound"); |  | ||||||
|     if(sound_api == NULL) return; // sound not available, critical error |  | ||||||
| 
 |  | ||||||
|     // subscribe to sound state updates |  | ||||||
|     subscribe_sound_changes(sound_api->updates, handle_sound_state, NULL); |  | ||||||
| 
 |  | ||||||
|     Tone current_state; |  | ||||||
|     if(read_sound(sound_api->state, ¤t_state, OsWaitForever)) { |  | ||||||
|         printf( |  | ||||||
|             "sound: %d Hz, %d %%\n", |  | ||||||
|             (uint16_t)current_state->freq, |  | ||||||
|             (uint8_t)(current_state->witdh * 100) |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // add layer to control sound |  | ||||||
|     ValueManager* sound_manager = add_sound_layer(¤t_state, UI_LAYER_APP); |  | ||||||
| 
 |  | ||||||
|     // write only freq by getting pointer |  | ||||||
|     Tone* tone = take_sound(sound_manager, OsWaitForever); |  | ||||||
|     if(tone != NULL) { |  | ||||||
|         tone->freq = 440; |  | ||||||
|     } |  | ||||||
|     commit_sound(sound_manager, tone); |  | ||||||
| 
 |  | ||||||
|     // write tone value |  | ||||||
|     write_sound(sound_manager, &(Tone{.freq = 110., witdh = 0.5}), OsWaitForever); |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
							
								
								
									
										100
									
								
								wiki/fw/api/Backlight-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								wiki/fw/api/Backlight-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | |||||||
|  | Backlight state describes by `uint8_t level;` brightness level. | ||||||
|  | 
 | ||||||
|  | LED API provided by struct: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueComposer* composer; /// every app add its value to compose, <uint8_t*> | ||||||
|  |     ValueManager* state; /// value state and changes <uint8_t*> | ||||||
|  | } BacklightApi; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You can get API instance by calling `open_backlight`: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// Add new layer to LED: | ||||||
|  | inline BacklightApi* open_backlight(const char* name) { | ||||||
|  |     return (BacklightApi*)furi_open(name); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Default system led is `/dev/backlight`. | ||||||
|  | 
 | ||||||
|  | To read current backlight state you should use `read_backlight` function: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// return true if success, false otherwise | ||||||
|  | inline bool read_backlight(BacklightApi* api, uint8_t* value, uint32_t timeout) { | ||||||
|  |     return read_mutex(api->state->value, (void*)value, sizeof(uint8_t), timeout); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Also you can subscribe to backlight state changes: | ||||||
|  | 
 | ||||||
|  | Use `subscribe_backlight_changes` to register your callback: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// return true if success, false otherwise | ||||||
|  | inline bool subscribe_backlight_changes(LedApi* led, void(*cb)(uint8_t*, void*), void* ctx) { | ||||||
|  |     return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Userspace helpers | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t value; | ||||||
|  |     ValueMutex value_mutex; | ||||||
|  |     ValueComposerHandle* composer_handle; | ||||||
|  | } Backlight; | ||||||
|  | 
 | ||||||
|  | inline bool init_backlight_composer(Backlight* backlight, BacklightApi* api, uint32_t layer) { | ||||||
|  |     if(!init_mutex(&backlight->value_mutex, (void*)&backlight->value, sizeof(uint8_t))) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     backlight->composer_handle = add_compose_layer( | ||||||
|  |         api->composer, COPY_COMPOSE, &backlight->value_mutex, layer | ||||||
|  |     ); // just copy backlight state on update | ||||||
|  | 
 | ||||||
|  |     return backlight->composer_handle != NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline void write_backlight(Backlight* backlight, uint8_t value) { | ||||||
|  |     write_mutex(&backlight->value_mutex, (void*)&value, sizeof(uint8_t), OsWaitForever); | ||||||
|  |     request_compose(backlight->composer_handle); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | 
 | ||||||
|  | void handle_backlight_state(uint8_t* value, void* _ctx) { | ||||||
|  |     printf("backlight: %d %%\n", (*value * 100) / 256); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void backlight_example(void* p) { | ||||||
|  |     BacklightApi* backlight_api = open_backlight("/dev/backlight"); | ||||||
|  |     if(backlight_api == NULL) return; // backlight not available, critical error | ||||||
|  | 
 | ||||||
|  |     // subscribe to led state updates | ||||||
|  |     subscribe_backlight_changes(backlight_api, handle_backlight_state, NULL); | ||||||
|  |     // get current backlight value | ||||||
|  |     uint8_t backlight_value; | ||||||
|  |     if(read_backlight(backlight_api, &backlight_value, OsWaitForever)) { | ||||||
|  |         printf( | ||||||
|  |             "initial backlight: %d %%\n", | ||||||
|  |             backlight_value * 100 / 256 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // create compose to control led | ||||||
|  |     Backlight backlight; | ||||||
|  |     if(!init_led_composer(&backlight, backlight_api, UiLayerBelowNotify)) return; | ||||||
|  | 
 | ||||||
|  |     // write RGB value | ||||||
|  |     write_backlight(&backlight, 127); | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										402
									
								
								wiki/fw/api/Basic-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								wiki/fw/api/Basic-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,402 @@ | |||||||
|  | # Flipper universal registry implementation (FURI) | ||||||
|  | 
 | ||||||
|  | Create record. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // creates new record in registry and store pointer into it | ||||||
|  | bool furi_create(const char* name, void* ptr); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Open record. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // get stored pointer by its name | ||||||
|  | void* furi_open(const char* name); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Flipper Application control (flapp) | ||||||
|  | 
 | ||||||
|  | ## (in progress. Old verison) | ||||||
|  | 
 | ||||||
|  | **`FlappHandler* flapp_start(void(app*)(void*), char* name, void* param)`** | ||||||
|  | 
 | ||||||
|  | simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | **`FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param)`** | ||||||
|  | 
 | ||||||
|  | swtich to other application. System **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application. | ||||||
|  | 
 | ||||||
|  | ### Exit application | ||||||
|  | 
 | ||||||
|  | **`void flapp_exit(void* param)`** | ||||||
|  | 
 | ||||||
|  | stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | **`bool flapp_kill(FlappHandler* app)`** | ||||||
|  | 
 | ||||||
|  | stop specified `app` without returning to `prev` application. | ||||||
|  | 
 | ||||||
|  | **`void flapp_ready()`** | ||||||
|  | 
 | ||||||
|  | If case one app depend on other, notify that app is ready. | ||||||
|  | 
 | ||||||
|  | ## Requirements | ||||||
|  | 
 | ||||||
|  | * start daemon app | ||||||
|  | * kill app | ||||||
|  | * start child thread (kill when parent app was killed) | ||||||
|  | * switch between UI apps | ||||||
|  | 
 | ||||||
|  | **`bool flapp_on_exit(void(cb*)(void*), void* ctx);`** | ||||||
|  | 
 | ||||||
|  | Register on-exit callback. It called before app will be killed. Not recommended to use in user scenario, only for system purpose (unregister callbacks, release mutexes, etc.) | ||||||
|  | 
 | ||||||
|  | # ValueMutex | ||||||
|  | 
 | ||||||
|  | The most simple concept is ValueMutex. It is wrapper around mutex and value pointer. You can take and give mutex to work with value and read and write value. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     void* value; | ||||||
|  |     size_t size; | ||||||
|  |     osMutex mutex; | ||||||
|  |      | ||||||
|  |     osMutexDescriptor __static // some internals; | ||||||
|  | } ValueMutex; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Create ValueMutex. Create instance of ValueMutex and call `init_mutex`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { | ||||||
|  |     valuemutex->mutex = osMutexCreateStatic(valuemutex->__static); | ||||||
|  |     if(valuemutex->mutex == NULL) return false; | ||||||
|  |      | ||||||
|  |     valuemutex->value = value; | ||||||
|  |     valuemutex->size = size; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | For work with data stored in mutex you should call `acquire_mutex`. It return pointer to data if success, NULL otherwise. | ||||||
|  | 
 | ||||||
|  | You must release mutex after end of work with data. Call `release_mutex` and pass ValueData instance and pointer to data. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { | ||||||
|  |     if(osMutexTake(valuemutex->mutex, timeout) == osOk) { | ||||||
|  |         return valuemutex->value; | ||||||
|  |     } else { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // infinitly wait for mutex | ||||||
|  | inline static void* acquire_mutex_block(ValueMutex* valuemutex) { | ||||||
|  |     return acquire_mutex(valuemutex, OsWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool release_mutex(ValueMutex* valuemutex, void* value) { | ||||||
|  |     if(value != valuemutex->value) return false; | ||||||
|  |      | ||||||
|  |     if(!osMutexGive(valuemutex->mutex)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. Both functions return true in case of success, false otherwise. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(valuemutex, timeout); | ||||||
|  |     if(value == NULL || len > valuemutex->size) return false; | ||||||
|  |     memcpy(data, value, len > 0 ? len : valuemutex->size): | ||||||
|  |     if(!release_mutex(valuemutex, value)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { | ||||||
|  |     return read_mutex(valuemutex, data, len, OsWaitForever); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(valuemutex, timeout); | ||||||
|  |     if(value == NULL || len > valuemutex->size) return false; | ||||||
|  |     memcpy(value, data, len > 0 ? len : valuemutex->size): | ||||||
|  |     if(!release_mutex(valuemutex, value)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { | ||||||
|  |     return write_mutex(valuemutex, data, len, OsWaitForever); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /* | ||||||
|  | MANIFEST | ||||||
|  | name="example-provider-app" | ||||||
|  | stack=128 | ||||||
|  | */ | ||||||
|  | void provider_app(void* _p) { | ||||||
|  |     // create record with mutex | ||||||
|  |     uint32_t example_value = 0; | ||||||
|  |     ValueMutex example_mutex; | ||||||
|  |     if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(furi_create("provider/example", (void*)&example_mutex)) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // we are ready to provide record to other apps | ||||||
|  |     flapp_ready(); | ||||||
|  | 
 | ||||||
|  |     // get value and increment it | ||||||
|  |     while(1) { | ||||||
|  |         uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever); | ||||||
|  |         if(value != NULL) { | ||||||
|  |             value++; | ||||||
|  |         } | ||||||
|  |         release_mutex(&example_mutex, value); | ||||||
|  | 
 | ||||||
|  |         osDelay(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  | MANIFEST | ||||||
|  | name="example-consumer-app" | ||||||
|  | stack=128 | ||||||
|  | require="example-provider-app" | ||||||
|  | */ | ||||||
|  | void consumer_app(void* _p) { | ||||||
|  |     // this app run after flapp_ready call in all requirements app | ||||||
|  | 
 | ||||||
|  |     // open mutex value | ||||||
|  |     ValueMutex* counter_mutex = furi_open("provider/example"); | ||||||
|  |     if(counter_mutex == NULL) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // continously read value every 1s | ||||||
|  |     uint32_t counter; | ||||||
|  |     while(1) { | ||||||
|  |         if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { | ||||||
|  |             printf("counter value: %d\n", counter); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         osDelay(1000); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # PubSub | ||||||
|  | 
 | ||||||
|  | PubSub allows users to subscribe on notifies and notify subscribers. Notifier side can pass `void*` arg to subscriber callback, and also subscriber can set `void*` context pointer that pass into callback (you can see callback signature below). | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef void(PubSubCallback*)(void*, void*); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSubCallback cb; | ||||||
|  |     void* ctx; | ||||||
|  | } PubSubItem; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSub* self; | ||||||
|  |     PubSubItem* item; | ||||||
|  | } PubSubId; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     PubSubItem items[NUM_OF_CALLBACKS]; | ||||||
|  |     PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item | ||||||
|  |     size_t count; ///< count of callbacks | ||||||
|  | } PubSub; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To create PubSub you should create PubSub instance and call `init_pubsub`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void init_pubsub(PubSub* pubsub) { | ||||||
|  |     pubsub->count = 0; | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) { | ||||||
|  |         pubsub->items[i]. | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Use `subscribe_pubsub` to register your callback. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // TODO add mutex to reconfigurate PubSub | ||||||
|  | PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) { | ||||||
|  |     if(pubsub->count >= NUM_OF_CALLBACKS) return NULL; | ||||||
|  | 
 | ||||||
|  |     pubsub->count++; | ||||||
|  |     PubSubItem* current = pubsub->items[pubsub->count]; | ||||||
|  |      | ||||||
|  |     current->cb = cb; | ||||||
|  |     currrnt->ctx = ctx; | ||||||
|  | 
 | ||||||
|  |     pubsub->ids[pubsub->count].self = pubsub; | ||||||
|  |     pubsub->ids[pubsub->count].item = current; | ||||||
|  | 
 | ||||||
|  |     flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count])); | ||||||
|  |      | ||||||
|  |     return current; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Use `unsubscribe_pubsub` to unregister callback. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void unsubscribe_pubsub(PubSubId* pubsub_id) { | ||||||
|  |     // TODO: add, and rearrange all items to keep subscribers item continuous | ||||||
|  |     // TODO: keep ids link actual | ||||||
|  |     // TODO: also add mutex on every pubsub changes | ||||||
|  | 
 | ||||||
|  |     // trivial implementation for NUM_OF_CALLBACKS = 1 | ||||||
|  |     if(NUM_OF_CALLBACKS != 1) return; | ||||||
|  | 
 | ||||||
|  |     if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return; | ||||||
|  | 
 | ||||||
|  |     pubsub_id->self->count = 0; | ||||||
|  |     pubsub_id->item = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Use `notify_pubsub` to notify subscribers. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void notify_pubsub(PubSub* pubsub, void* arg) { | ||||||
|  |     // iterate over subscribers | ||||||
|  |     for(size_t i = 0; i < pubsub->count; i++) { | ||||||
|  |         pubsub->items[i]->cb(arg, pubsub->items[i]->ctx); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /* | ||||||
|  | MANIFEST | ||||||
|  | name="test" | ||||||
|  | stack=128 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | void example_pubsub_handler(void* arg, void* ctx) { | ||||||
|  |     printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pubsub_test() { | ||||||
|  |     const char* app_name = "test app"; | ||||||
|  | 
 | ||||||
|  |     PubSub example_pubsub; | ||||||
|  |     init_pubsub(&example_pubsub); | ||||||
|  | 
 | ||||||
|  |     if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) { | ||||||
|  |         printf("critical error\n"); | ||||||
|  |         flapp_exit(NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint32_t counter = 0; | ||||||
|  |     while(1) { | ||||||
|  |         notify_pubsub(&example_pubsub, (void*)&counter); | ||||||
|  |         counter++; | ||||||
|  | 
 | ||||||
|  |         osDelay(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # ValueComposer | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef void(ValueComposerCallback)(void* ctx, void* state); | ||||||
|  | 
 | ||||||
|  | void COPY_COMPOSE(void* ctx, void* state) { | ||||||
|  |     read_mutex((ValueMutex*)ctx, state, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     UiLayerBelowNotify | ||||||
|  |     UiLayerNotify, | ||||||
|  |     UiLayerAboveNotify | ||||||
|  | } UiLayer; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | ValueComposerHandle* add_compose_layer( | ||||||
|  |     ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool remove_compose_layer(ValueComposerHandle* handle); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void request_compose(ValueComposerHandle* handle); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | See [LED](LED-API) or [Display](Display-API) API for examples. | ||||||
|  | 
 | ||||||
|  | # ValueManager | ||||||
|  | 
 | ||||||
|  | More complicated concept is ValueManager. It is like ValueMutex, but user can subscribe to value updates. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex value; | ||||||
|  |     PubSub pubsub; | ||||||
|  | } ValueManager; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | First of all you can use value and pubsub part as showing above: aquire/release mutex, read value, subscribe/unsubscribe pubsub. There are two specific methods for ValueManager: | ||||||
|  | 
 | ||||||
|  | `write_managed` acquire value, changes it and send notify with current value. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) { | ||||||
|  |     void* value = acquire_mutex(managed->mutex, timeout); | ||||||
|  |     if(value == NULL) return false; | ||||||
|  | 
 | ||||||
|  |     memcpy(value, data, len): | ||||||
|  | 
 | ||||||
|  |     notify_pubsub(&managed->pubsub, value); | ||||||
|  | 
 | ||||||
|  |     if(!release_mutex(managed->mutex, value)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `commit_managed` works as `release_mutex` but send notify with current value. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool commit_managed(ValueManager* managed, void* value) { | ||||||
|  |     if(value != managed->mutex->value) return false; | ||||||
|  | 
 | ||||||
|  |     notify_pubsub(&managed->pubsub, value); | ||||||
|  |      | ||||||
|  |     if(!osMutexGive(managed->mutex)) return false; | ||||||
|  |      | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										68
									
								
								wiki/fw/api/Display-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								wiki/fw/api/Display-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | All display operations based on [u8g2](https://github.com/olikraus/u8g2) library. | ||||||
|  | 
 | ||||||
|  | API available as `ValueComposer`. | ||||||
|  | 
 | ||||||
|  | Driver call render callback and pass API contains u8g2 functions, instance and fonts: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     u8g2_t* display; | ||||||
|  | 
 | ||||||
|  |     void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t  *font); | ||||||
|  |     void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color); | ||||||
|  |     void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent); | ||||||
|  |     u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str); | ||||||
|  | 
 | ||||||
|  |     Fonts fonts; | ||||||
|  | } DisplayApi; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const uint8_t* u8g2_font_6x10_mf; | ||||||
|  | } Fonts; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | First of all you can open display API instance by calling `open_display` | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// Get display instance and API | ||||||
|  | inline Display* open_display(const char* name) { | ||||||
|  |     return (Display*)furi_open(name); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Default display name is `/dev/display`. | ||||||
|  | 
 | ||||||
|  | For draw something to display you need to register new layer in display composer: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef void (RenderCallback*)(void* ctx, DisplayApi* api); | ||||||
|  | 
 | ||||||
|  | inline ValueComposerHandle* init_display_composer( | ||||||
|  |     Display* api, RenderCallback render, void* ctx, uint32_t layer) { | ||||||
|  |     return add_compose_layer(api->composer, (ValueComposerCallback)render, ctx, layer); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And then call `request_compose` every time you need to redraw your image. | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | 
 | ||||||
|  | void example_render(void* ctx, DisplayApi* api) { | ||||||
|  |     api->u8g2_SetFont(api->display, display_api->fonts.u8g2_font_6x10_mf); | ||||||
|  |     api->u8g2_SetDrawColor(api->display, 1); | ||||||
|  |     api->u8g2_SetFontMode(api->display, 1); | ||||||
|  |     api->u8g2_DrawStr(api->display, 2, 12, (char*)ctx); // ctx contains some static text | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void u8g2_example(void* p) { | ||||||
|  |     Display* display_api = open_display("/dev/display"); | ||||||
|  |     if(display_api == NULL) return; // display not available, critical error | ||||||
|  | 
 | ||||||
|  |     ValueComposerHandle display_handler = init_display_composer( | ||||||
|  |         display_api, example_render, (void*)"Hello world", UiLayerBelowNotify); | ||||||
|  | 
 | ||||||
|  |     request_compose(display_handler); | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										158
									
								
								wiki/fw/api/HAL-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								wiki/fw/api/HAL-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | |||||||
|  | # GPIO | ||||||
|  | 
 | ||||||
|  | GPIO defined as struct `GpioPin`. | ||||||
|  | 
 | ||||||
|  | GPIO functions: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // Init GPIO | ||||||
|  | void gpio_init(GpioPin* gpio, GpioMode mode); | ||||||
|  | 
 | ||||||
|  | typedef enum { GpioModeInput, GpioModeOutput, GpioModeOpenDrain } GpioMode; | ||||||
|  | 
 | ||||||
|  | // write value to GPIO | ||||||
|  | void gpio_write(GpioPin* gpio, bool state); | ||||||
|  | 
 | ||||||
|  | // read value from GPIO, f = LOW, t = HIGH | ||||||
|  | bool gpio_read(GpioPin* gpio); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | When application is exited, system place pin to Z-state by calling `gpio_disable`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // put GPIO to Z-state (used for restore pin state on app exit) | ||||||
|  | void gpio_disable(ValueMutex* gpio_mutex) { | ||||||
|  |     GpioPin* gpio = acquire_mutex(gpio_mutex, 0); | ||||||
|  |     gpio_init(gpio, GpioModeInput); | ||||||
|  |     release_mutex(gpio_mutex, gpio); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Available GPIO stored in FURI as `ValueMutex<GpioPin*>`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | inline static ValueMutex* open_gpio_mutex(const char* name) { | ||||||
|  |     ValueMutex* gpio_mutex = (ValueMutex*)furi_open(name); | ||||||
|  |     if(gpio_mutex != NULL) flapp_on_exit(gpio_disable, gpio_mutex); | ||||||
|  | 
 | ||||||
|  |     return gpio_mutex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // helper | ||||||
|  | inline static GpioPin* open_gpio(const char* name) { | ||||||
|  |     ValueMutex* gpio_mutex = open_gpio(name); | ||||||
|  |     return (GpioPin*)acquire_mutex(gpio_mutex, 0); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Available GPIO (target F2) | ||||||
|  | 
 | ||||||
|  | * PA4 | ||||||
|  | * PA5 | ||||||
|  | * PA6 | ||||||
|  | * PA7 | ||||||
|  | * PB2 | ||||||
|  | * PC3 | ||||||
|  | * PC0 | ||||||
|  | * PC1 | ||||||
|  | * PB6 | ||||||
|  | * PB7 | ||||||
|  | * PA13 | ||||||
|  | * PA14 | ||||||
|  | * RFID_PULL | ||||||
|  | * IR_TX | ||||||
|  | * IBUTTON | ||||||
|  | * VIBRO | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void gpio_example() { | ||||||
|  |     GpioPin* pin = open_gpio("PB6"); | ||||||
|  | 
 | ||||||
|  |     if(pin == NULL) { | ||||||
|  |         printf("pin not available\n"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     gpio_init(pin, GpioModeOutput); | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         gpio_write(pin, true); | ||||||
|  |         delay(100); | ||||||
|  |         gpio_write(pin, false); | ||||||
|  |         delay(100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # PWM | ||||||
|  | 
 | ||||||
|  | PWM defined as `PwmPin`. To set PWM channel: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void pwm_set(PwmPin* pwm, float value, float freq); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | When application is exited, system disable pwm by calling `pwm_disable`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // put GPIO to Z-state (used for restore pin state on app exit) | ||||||
|  | void pwm_disable(ValueMutex* pwm_mutex) { | ||||||
|  |     PwmPin* pwm = acquire_mutex(pwm_mutex, 0); | ||||||
|  |     pwm_set(pwm, 0., 0.); | ||||||
|  |     release_mutex(pwm_mutex, pwm); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Available PWM stored in FURI as `ValueMutex<PwmPin*>`. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | inline static ValueMutex* open_pwm_mutex(const char* name) { | ||||||
|  |     ValueMutex* pwm_mutex = (ValueMutex*)furi_open(name); | ||||||
|  |     if(pwm_mutex != NULL) flapp_on_exit(pwm_disable, pwm_mutex); | ||||||
|  | 
 | ||||||
|  |     return pwm_mutex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // helper | ||||||
|  | inline static PwmPin* open_pwm(const char* name) { | ||||||
|  |     ValueMutex* pwm_mutex = open_gpio(name); | ||||||
|  |     return (PwmPin*)acquire_mutex(pwm_mutex, 0); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Available PWM (target F2) | ||||||
|  | 
 | ||||||
|  | * SPEAKER | ||||||
|  | * RFID_OUT | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | void sound_example() { | ||||||
|  |     PwmPin* speaker = open_pwm("SPEAKER"); | ||||||
|  | 
 | ||||||
|  |     if(speaker == NULL) { | ||||||
|  |         printf("speaker not available\n"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         pwm_set(speaker, 1000., 0.1); | ||||||
|  |         delay(2); | ||||||
|  |         pwm_set(speaker, 110., 0.5); | ||||||
|  |         delay(198); | ||||||
|  |         pwm_set(speaker, 330., 0.5); | ||||||
|  |         delay(200); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # ADC | ||||||
|  | 
 | ||||||
|  | Coming soon... | ||||||
|  | 
 | ||||||
|  | # I2C | ||||||
|  | 
 | ||||||
|  | Coming soon... | ||||||
| @ -13,7 +13,7 @@ You can get API instance by calling `open_input`: | |||||||
| ```C | ```C | ||||||
| /// Get input struct | /// Get input struct | ||||||
| inline Input* open_input(const char* name) { | inline Input* open_input(const char* name) { | ||||||
|     return furi_open(name); |     return (Input*)furi_open(name); | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -37,8 +37,8 @@ To read buttons state you should use `read_state` function: | |||||||
| 
 | 
 | ||||||
| ```C | ```C | ||||||
| /// read current state of all buttons. Return true if success, false otherwise | /// read current state of all buttons. Return true if success, false otherwise | ||||||
| inline bool read_state(ValueMutex* state, InputState* value, uint32_t timeout) { | inline bool read_state(Input* api, InputState* value, uint32_t timeout) { | ||||||
|     return read_mutex(state, (void*)value, sizeof(InputState), timeout); |     return read_mutex(api->state, (void*)value, sizeof(InputState), timeout); | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -94,7 +94,7 @@ void input_example(void* p) { | |||||||
|     // blocking way |     // blocking way | ||||||
|     InputState state; |     InputState state; | ||||||
|     while(1) { |     while(1) { | ||||||
|         if(read_state(input->state, &state, OsWaitForever)) { |         if(read_state(input, &state, OsWaitForever)) { | ||||||
|             if(state.up) { |             if(state.up) { | ||||||
|                 printf("up is pressed"); |                 printf("up is pressed"); | ||||||
|                 delay(1000); |                 delay(1000); | ||||||
							
								
								
									
										110
									
								
								wiki/fw/api/LED-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								wiki/fw/api/LED-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | |||||||
|  | LED state describes by struct: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t red; | ||||||
|  |     uint8_t green; | ||||||
|  |     uint8_t blue;  | ||||||
|  | } Rgb; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | LED API provided by struct: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueComposer* composer; /// every app add its value to compose, <Rgb*> | ||||||
|  |     ValueManager* state; /// LED value state and changes <Rgb*> | ||||||
|  | } LedApi; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | You can get API instance by calling `open_led`: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// Add new layer to LED: | ||||||
|  | inline LedApi* open_led(const char* name) { | ||||||
|  |     return (LedApi*)furi_open(name); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Default system led is `/dev/led`. | ||||||
|  | 
 | ||||||
|  | To read current led state you should use `read_led` function: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// return true if success, false otherwise | ||||||
|  | inline bool read_led(LedApi* led, Rgb* value, uint32_t timeout) { | ||||||
|  |     return read_mutex(led->state->value, (void*)value, sizeof(Rgb), timeout); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Also you can subscribe to led state changes: | ||||||
|  | 
 | ||||||
|  | Use `subscribe_led_changes` to register your callback: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | /// return true if success, false otherwise | ||||||
|  | inline bool subscribe_led_changes(LedApi* led, void(*cb)(Rgb*, void*), void* ctx) { | ||||||
|  |     return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Userspace helpers | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     Rgb value; | ||||||
|  |     ValueMutex value_mutex; | ||||||
|  |     ValueComposerHandle* composer_handle; | ||||||
|  | } SystemLed; | ||||||
|  | 
 | ||||||
|  | inline bool init_led_composer(SystemLed* led, LedApi* api, uint32_t layer) { | ||||||
|  |     if(!init_mutex(&led->value_mutex, (void*)&led->value, sizeof(Rgb))) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     led->composer_handle = add_compose_layer( | ||||||
|  |         api->composer, COPY_COMPOSE, &led->value_mutex, layer | ||||||
|  |     ); // just copy led state on update | ||||||
|  | 
 | ||||||
|  |     return led->composer_handle != NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline void write_led(SystemLed* led, Rgb* value) { | ||||||
|  |     write_mutex(&led->value_mutex, (void*)value, sizeof(Rgb), OsWaitForever); | ||||||
|  |     request_compose(led->composer_handle); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## Usage example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | 
 | ||||||
|  | void handle_led_state(Rgb* rgb, void* _ctx) { | ||||||
|  |     printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void led_example(void* p) { | ||||||
|  |     LedApi* led_api = open_led("/dev/led"); | ||||||
|  |     if(led_api == NULL) return; // led not available, critical error | ||||||
|  | 
 | ||||||
|  |     // subscribe to led state updates | ||||||
|  |     subscribe_led_changes(led_api, handle_led_state, NULL); | ||||||
|  |     // get current led value | ||||||
|  |     Rgb led_value; | ||||||
|  |     if(read_led(led_api, &led_value, OsWaitForever)) { | ||||||
|  |         printf( | ||||||
|  |             "initial led: #%02X%02X%02X\n", | ||||||
|  |             led_value->red, | ||||||
|  |             led_value->green, | ||||||
|  |             led_value->blue | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // create compose to control led | ||||||
|  |     SystemLed system_led; | ||||||
|  |     if(!init_led_composer(&system_led, led_api, UiLayerBelowNotify)) return; | ||||||
|  | 
 | ||||||
|  |     // write RGB value | ||||||
|  |     write_led(&system_led, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D})); | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										130
									
								
								wiki/fw/api/SPI-Devices-API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								wiki/fw/api/SPI-Devices-API.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | # SPI | ||||||
|  | 
 | ||||||
|  | HAL struct `SPI_HandleTypeDef*` used for handling SPI info. | ||||||
|  | 
 | ||||||
|  | For transmit/receive data use `spi_xfer` function: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | bool spi_xfer( | ||||||
|  |     SPI_HandleTypeDef* spi, | ||||||
|  |     uint8_t* tx_data, uint8_t* rx_data, size_t len, | ||||||
|  |     PubSubCallback cb, void* ctx); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | * `tx_data` and `rx_data` size must be equal (and equal `len`) | ||||||
|  | * `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback. | ||||||
|  | 
 | ||||||
|  | Blocking verison: | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | inline static bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) { | ||||||
|  |     semaphoreInfo s; | ||||||
|  |     osSemaphore block = createSemaphoreStatic(s); | ||||||
|  |     if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) { | ||||||
|  |         osReleaseSemaphore(block); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     osWaitSemaphore(block); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## SPI Bus | ||||||
|  | 
 | ||||||
|  | Common implementation of SPI bus: serial interface + CS pin | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     GpioPin* cs; ///< CS pin | ||||||
|  |     ValueMutex* spi; ///< <SPI_HandleTypeDef*> | ||||||
|  | } SpiBus; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## SPI device | ||||||
|  | 
 | ||||||
|  | For dedicated work with one device there is `SpiDevice` entity. It contains ValueMutex around SpiBus: after you acquire device you can acquire spi to work with it (don't forget SPI bus is shared around many device, release it after every transaction as quick as possible). | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <SpiBus*> | ||||||
|  | } SpiDevice; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## SPI IRQ device | ||||||
|  | 
 | ||||||
|  | Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. For work with it there is special entity `SpiIrqDevice`. Use `subscribe_pubsub` for subscribinq to irq events. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <SpiBus*> | ||||||
|  |     PubSub* irq; | ||||||
|  | } SpiIrqDevice; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Display device | ||||||
|  | 
 | ||||||
|  | Special implementation of SPI bus: serial interface + CS, Res, D/I lines. | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     GpioPin* cs; ///< CS pin | ||||||
|  |     GpioPin* res; ///< reset pin | ||||||
|  |     GpioPin* di; ///< D/I pin | ||||||
|  |     ValueMutex* spi; ///< <SPI_HandleTypeDef*> | ||||||
|  | } DisplayBus; | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | typedef struct { | ||||||
|  |     ValueMutex* bus; ///< <DisplayBus*> | ||||||
|  | } DisplayDevice; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # SPI devices (F2) | ||||||
|  | 
 | ||||||
|  | * `/dev/sdcard` - SD card SPI, `SpiDevice` | ||||||
|  | * `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice` | ||||||
|  | * `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice` | ||||||
|  | * `/dev/display` - `DisplayDevice` | ||||||
|  | * `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7) | ||||||
|  | 
 | ||||||
|  | ### Application example | ||||||
|  | 
 | ||||||
|  | ```C | ||||||
|  | // Be careful, this function called from IRQ context | ||||||
|  | void handle_irq(void* _arg, void* _ctx) { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void cc1101_example() { | ||||||
|  |     SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus"); | ||||||
|  |     if(cc1101_device == NULL) return; // bus not available, critical error | ||||||
|  | 
 | ||||||
|  |     subscribe_pubsub(cc1101_device->irq, handle_irq, NULL); | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |         // acquire device as device bus | ||||||
|  |         SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0); | ||||||
|  |         if(spi_bus == NULL) { | ||||||
|  |             printf("Device busy\n"); | ||||||
|  |             // wait for device | ||||||
|  |             spi_bus = acquire_mutex_block(cc1101_device->bus); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // make transaction | ||||||
|  |         uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF}; | ||||||
|  |         uint8_t response[4]; | ||||||
|  | 
 | ||||||
|  |         { | ||||||
|  |             SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi); | ||||||
|  | 
 | ||||||
|  |             gpio_write(spi_bus->cs, false); | ||||||
|  |             spi_xfer_block(spi, request, response, 4); | ||||||
|  |             gpio_write(spi_bus->cs, true); | ||||||
|  | 
 | ||||||
|  |             release_mutex(cc1101_device->spi, spi); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // release device (device bus) | ||||||
|  |         release_mutex(cc1101_device->bus, spi_bus); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 coreglitch
						coreglitch