Furi (#24)
* furiac start and thread create implementation" * create and kill task * rename debug, add header * remove write.c * kill itself * furi exit/switch * success switch and exit * WIP furi records * add furi record interface * rename furi app control file * record implementation in progress * wip furi implementation * add automatic tests for FURI AC * differ build tests * small changes * FURI record tests description * change furi statuses * FURI record test blank * exit after all application ends * delay: print then wait * fix FURI implementatnion building * pipe record test * concurrent access * uncomplete mute-test * update FURI documentation
This commit is contained in:
		
							parent
							
								
									04035ce52d
								
							
						
					
					
						commit
						1759787334
					
				
							
								
								
									
										81
									
								
								applications/app_example/app_example.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								applications/app_example/app_example.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include "flipper.h"
 | 
				
			||||||
 | 
					#include "debug.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_widget(void* param);
 | 
				
			||||||
 | 
					void furi_test_app(void* param);
 | 
				
			||||||
 | 
					void furi_next_test_app(void* param);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					widget simply print ping message
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furi_widget(void* param) {
 | 
				
			||||||
 | 
					    FILE* debug_uart = get_debug();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fprintf(debug_uart, "start furi widget: %s\n", (char*)param);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "furi widget\n");
 | 
				
			||||||
 | 
					        delay(10);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					it simply start, then start child widget, wait about 1 sec (with ping evey 200 ms),
 | 
				
			||||||
 | 
					kill the widget, continue with 500 ms ping.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furi_test_app(void* param) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t cnt = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "furi test app %d\n", cnt);
 | 
				
			||||||
 | 
					        delay(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(cnt == 2) {
 | 
				
			||||||
 | 
					            fprintf(debug_uart, "go to next app\n");
 | 
				
			||||||
 | 
					            furiac_switch(furi_next_test_app, "next_test", NULL);
 | 
				
			||||||
 | 
					            fprintf(debug_uart, "unsuccessful switch\n");
 | 
				
			||||||
 | 
					            while(1) {
 | 
				
			||||||
 | 
					                delay(1000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cnt++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_next_test_app(void* param) {
 | 
				
			||||||
 | 
					    FILE* debug_uart = get_debug();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fprintf(debug_uart, "start next test app\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delay(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fprintf(debug_uart, "exit next app\n");
 | 
				
			||||||
 | 
					    furiac_exit(NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        // this code must not be called
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "next app: something went wrong\n");
 | 
				
			||||||
 | 
					        delay(10);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					FILE* debug_uart = get_debug();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fprintf(debug_uart, "hello Flipper!\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GpioPin red_led = {LED_RED_GPIO_Port, LED_RED_Pin};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app_gpio_init(red_led, GpioModeOutput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while(1) {
 | 
				
			||||||
 | 
					    delay(100);
 | 
				
			||||||
 | 
					    app_gpio_write(red_led, true);
 | 
				
			||||||
 | 
					    delay(100);
 | 
				
			||||||
 | 
					    app_gpio_write(red_led, false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
							
								
								
									
										2
									
								
								applications/app_example/app_example.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								applications/app_example/app_example.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_test_app(void*);
 | 
				
			||||||
							
								
								
									
										13
									
								
								applications/startup.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/startup.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "furi.h"
 | 
				
			||||||
 | 
					#include "tests/test_index.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    FlipperApplication app;
 | 
				
			||||||
 | 
					    const char* name;
 | 
				
			||||||
 | 
					} FlipperStartupApp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FlipperStartupApp FLIPPER_STARTUP[] = {
 | 
				
			||||||
 | 
					    {.app = flipper_test_app, .name = "test app"}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										455
									
								
								applications/tests/furi_record_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										455
									
								
								applications/tests/furi_record_test.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,455 @@
 | 
				
			|||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include "flipper.h"
 | 
				
			||||||
 | 
					#include "debug.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					TEST: pipe record
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. create pipe record
 | 
				
			||||||
 | 
					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) {
 | 
				
			||||||
 | 
					    // hold value to static var
 | 
				
			||||||
 | 
					    pipe_record_value = *((uint8_t*)value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_pipe_record(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    // 1. create pipe record
 | 
				
			||||||
 | 
					    if(!furi_create("test/pipe", NULL, 0)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot create record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Open/subscribe to it 
 | 
				
			||||||
 | 
					    FuriRecordHandler pipe_record = furi_open(
 | 
				
			||||||
 | 
					        "test/pipe", false, false, pipe_record_cb, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(pipe_record.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint8_t WRITE_VALUE = 1;
 | 
				
			||||||
 | 
					    // 3. write data
 | 
				
			||||||
 | 
					    if(!furi_write(&pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot write to record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. check that subscriber get data
 | 
				
			||||||
 | 
					    if(pipe_record_value != WRITE_VALUE) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 5. try to read, get error
 | 
				
			||||||
 | 
					    uint8_t read_value = 0;
 | 
				
			||||||
 | 
					    if(furi_read(&pipe_record, &read_value, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "reading from pipe record not allowed\n");
 | 
				
			||||||
 | 
					        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))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "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) {
 | 
				
			||||||
 | 
					    // hold value to static var
 | 
				
			||||||
 | 
					    holding_record_value = *((uint8_t*)value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_holding_data(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    // 1. Create holding record
 | 
				
			||||||
 | 
					    uint8_t holder = 0;
 | 
				
			||||||
 | 
					    if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot create record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Open/Subscribe on it
 | 
				
			||||||
 | 
					    FuriRecordHandler holding_record = furi_open(
 | 
				
			||||||
 | 
					        "test/holding", false, false, holding_record_cb, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(holding_record.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint8_t WRITE_VALUE = 1;
 | 
				
			||||||
 | 
					    // 3. write data
 | 
				
			||||||
 | 
					    if(!furi_write(&holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot write to record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. check that subscriber get data
 | 
				
			||||||
 | 
					    if(holding_record_value != WRITE_VALUE) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 5. Read and check data
 | 
				
			||||||
 | 
					    uint8_t read_value = 0;
 | 
				
			||||||
 | 
					    if(!furi_read(&holding_record, &read_value, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot read from record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(read_value != WRITE_VALUE) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 6. Try to write/read wrong size of data
 | 
				
			||||||
 | 
					    if(furi_write(&holding_record, &WRITE_VALUE, 100)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "overflowed write not allowed\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_read(&holding_record, &read_value, 100)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "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) {
 | 
				
			||||||
 | 
					    FILE* debug_uart = (FILE*)p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriRecordHandler holding_record = furi_open(
 | 
				
			||||||
 | 
					        "test/concurrent", false, false, NULL, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(holding_record.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "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) {
 | 
				
			||||||
 | 
					            fprintf(debug_uart, "cannot take record\n");
 | 
				
			||||||
 | 
					            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 furi_concurrent_access(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    // 1. Create holding record
 | 
				
			||||||
 | 
					    ConcurrentValue holder = {.a = 0, .b = 0};
 | 
				
			||||||
 | 
					    if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot create record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Open it
 | 
				
			||||||
 | 
					    FuriRecordHandler holding_record = furi_open(
 | 
				
			||||||
 | 
					        "test/concurrent", false, false, NULL, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(holding_record.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open record\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 3. Create second app for interact with it
 | 
				
			||||||
 | 
					    FuriApp* second_app = furiac_start(
 | 
				
			||||||
 | 
					        furi_concurent_app, "furi concurent app", (void*)debug_uart
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. multiply ConcurrentValue::a
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					        ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(value == NULL) {
 | 
				
			||||||
 | 
					            fprintf(debug_uart, "cannot take record\n");
 | 
				
			||||||
 | 
					            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(20);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(second_app->handler != NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "second app still alive\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(holder.a != holder.b) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "broken integrity: a=%d, b=%d\n", holder.a, holder.b);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					TEST: non-existent data
 | 
				
			||||||
 | 
					1. Try to open non-existent record
 | 
				
			||||||
 | 
					2. Check for NULL handler
 | 
				
			||||||
 | 
					3. Try to write/read, get error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: implement this test
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool furi_nonexistent_data(FILE* debug_uart) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					TEST: mute algorithm
 | 
				
			||||||
 | 
					1. Create "parent" application:
 | 
				
			||||||
 | 
					    1. Create pipe record
 | 
				
			||||||
 | 
					    2. Open watch handler: no_mute=false, solo=false, subscribe to data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Open handler A: no_mute=false, solo=false, NULL subscriber. Subscribe to state.
 | 
				
			||||||
 | 
					Try to write data to A and check subscriber.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Open handler B: no_mute=true, solo=true, NULL subscriber.
 | 
				
			||||||
 | 
					Check A state cb get FlipperRecordStateMute.
 | 
				
			||||||
 | 
					Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					Try to write data to B and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: test 3 not pass beacuse state callback not implemented
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. Open hadler C: no_mute=false, solo=true, NULL subscriber.
 | 
				
			||||||
 | 
					Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
 | 
				
			||||||
 | 
					Try to write data to C and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. Open handler D: no_mute=false, solo=false, NULL subscriber.
 | 
				
			||||||
 | 
					Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
 | 
				
			||||||
 | 
					Try to write data to C and check that subscriber get data. (not muted because D open without solo)
 | 
				
			||||||
 | 
					Try to write data to D and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6. Close C, close B.
 | 
				
			||||||
 | 
					Check A state cb get FlipperRecordStateUnmute
 | 
				
			||||||
 | 
					Try to write data to A and check that subscriber get data. (unmuted)
 | 
				
			||||||
 | 
					Try to write data to D and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: test 6 not pass beacuse cleanup is not implemented
 | 
				
			||||||
 | 
					TODO: test 6 not pass because mute algorithm is unfinished.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					7. Exit "parent application"
 | 
				
			||||||
 | 
					Check A state cb get FlipperRecordStateDeleted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: test 7 not pass beacuse cleanup is not implemented
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint8_t mute_last_value = 0;
 | 
				
			||||||
 | 
					static FlipperRecordState mute_last_state = 255;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mute_record_cb(const void* value, size_t size) {
 | 
				
			||||||
 | 
					    // hold value to static var
 | 
				
			||||||
 | 
					    mute_last_value = *((uint8_t*)value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mute_record_state_cb(FlipperRecordState state) {
 | 
				
			||||||
 | 
					    mute_last_state = state;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_mute_parent_app(void* p) {
 | 
				
			||||||
 | 
					    FILE* debug_uart = (FILE*)p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1. Create pipe record
 | 
				
			||||||
 | 
					    if(!furi_create("test/mute", NULL, 0)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot create record\n");
 | 
				
			||||||
 | 
					        furiac_exit(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
 | 
				
			||||||
 | 
					    FuriRecordHandler watch_handler = furi_open(
 | 
				
			||||||
 | 
					        "test/mute", false, false, mute_record_cb, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(watch_handler.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open watch handler\n");
 | 
				
			||||||
 | 
					        furiac_exit(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        // TODO we don't have thread sleep
 | 
				
			||||||
 | 
					        delay(100000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_mute_algorithm(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    // 1. Create "parent" application:
 | 
				
			||||||
 | 
					    FuriApp* parent_app = furiac_start(
 | 
				
			||||||
 | 
					        furi_mute_parent_app, "parent app", (void*)debug_uart
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delay(2); // wait creating record
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
 | 
				
			||||||
 | 
					    FuriRecordHandler handler_a = furi_open(
 | 
				
			||||||
 | 
					        "test/mute", false, false, NULL, mute_record_state_cb
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(handler_a.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open handler A\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t test_counter = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Try to write data to A and check subscriber
 | 
				
			||||||
 | 
					    if(!furi_write(&handler_a, &test_counter, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "write to A failed\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mute_last_value != test_counter) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "value A mismatch: %d vs %d\n", mute_last_value, test_counter);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 | 
				
			||||||
 | 
					    FuriRecordHandler handler_b = furi_open(
 | 
				
			||||||
 | 
					        "test/mute", true, true, NULL, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(handler_b.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open handler B\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check A state cb get FlipperRecordStateMute.
 | 
				
			||||||
 | 
					    if(mute_last_state != FlipperRecordStateMute) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "A state is not FlipperRecordStateMute: %d\n", mute_last_state);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_counter = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					    if(furi_write(&handler_a, &test_counter, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "A not muted\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mute_last_value == test_counter) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "value A must be muted\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test_counter = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Try to write data to B and check that subscriber get data.
 | 
				
			||||||
 | 
					    if(!furi_write(&handler_b, &test_counter, sizeof(uint8_t))) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "write to B failed\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mute_last_value != test_counter) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "value B mismatch: %d vs %d\n", mute_last_value, test_counter);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 | 
				
			||||||
 | 
					    FuriRecordHandler handler_c = furi_open(
 | 
				
			||||||
 | 
					        "test/mute", true, false, NULL, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(handler_c.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open handler C\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to C and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
 | 
				
			||||||
 | 
					    FuriRecordHandler handler_d = furi_open(
 | 
				
			||||||
 | 
					        "test/mute", false, false, NULL, NULL
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if(handler_d.record == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "cannot open handler D\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Try to write data to A and check that subscriber get no data. (muted)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to C and check that subscriber get data. (not muted because D open without solo)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to D and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 6. Close C, close B.
 | 
				
			||||||
 | 
					    // TODO: Check A state cb get FlipperRecordStateUnmute
 | 
				
			||||||
 | 
					    // TODO: Try to write data to A and check that subscriber get data. (unmuted)
 | 
				
			||||||
 | 
					    // TODO: Try to write data to D and check that subscriber get data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 7. Exit "parent application"
 | 
				
			||||||
 | 
					    if(!furiac_kill(parent_app)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "kill parent_app fail\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Check A state cb get FlipperRecordStateDeleted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								applications/tests/furiac_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								applications/tests/furiac_test.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include "flipper.h"
 | 
				
			||||||
 | 
					#include "debug.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Test: creating and killing task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. create task
 | 
				
			||||||
 | 
					2. delay 10 ms
 | 
				
			||||||
 | 
					3. kill task
 | 
				
			||||||
 | 
					4. check that value changes
 | 
				
			||||||
 | 
					5. delay 2 ms
 | 
				
			||||||
 | 
					6. check that value stay unchanged
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void create_kill_app(void* p) {
 | 
				
			||||||
 | 
					    // this app simply increase counter
 | 
				
			||||||
 | 
					    uint8_t* counter = (uint8_t*)p;
 | 
				
			||||||
 | 
					    while(1) {
 | 
				
			||||||
 | 
					        *counter = *counter + 1;
 | 
				
			||||||
 | 
					        delay(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_ac_create_kill(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    uint8_t counter = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t value_a = counter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriApp* widget = furiac_start(create_kill_app, "create_kill_app", (void*)&counter);
 | 
				
			||||||
 | 
					    if(widget == NULL) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "create widget fail\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delay(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!furiac_kill(widget)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "kill widget fail\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(value_a == counter) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "counter unchanged\n");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value_a = counter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    delay(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(value_a != counter) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "counter changes after kill (counter = %d vs %d)\n", value_a, counter);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Test: switch between tasks
 | 
				
			||||||
 | 
					1. init s
 | 
				
			||||||
 | 
					2. create task A, add 'A" to sequence'
 | 
				
			||||||
 | 
					3. switch to task B, add 'B' to sequence
 | 
				
			||||||
 | 
					4. exit from task B -> switch to A and add 'A' to sequence
 | 
				
			||||||
 | 
					5. cleanup: exit from task A
 | 
				
			||||||
 | 
					6. check sequence
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TEST_SWITCH_CONTEXT_SEQ_SIZE 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    char sequence[TEST_SWITCH_CONTEXT_SEQ_SIZE];
 | 
				
			||||||
 | 
					    size_t count;
 | 
				
			||||||
 | 
					} TestSwitchSequence;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void task_a(void*);
 | 
				
			||||||
 | 
					void task_b(void*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void task_a(void *p) {
 | 
				
			||||||
 | 
					    // simply starts, add 'A' letter to sequence and switch
 | 
				
			||||||
 | 
					    // if sequence counter = 0, call task B, exit otherwise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TestSwitchSequence* seq = (TestSwitchSequence*)p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    seq->sequence[seq->count] = 'A';
 | 
				
			||||||
 | 
					    seq->count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(seq->count == 1) {
 | 
				
			||||||
 | 
					        furiac_switch(task_b, "task B", p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if switch unsuccessfull, this code will executed
 | 
				
			||||||
 | 
					        seq->sequence[seq->count] = 'x';
 | 
				
			||||||
 | 
					        seq->count++;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // add '/' symbol on exit
 | 
				
			||||||
 | 
					        seq->sequence[seq->count] = '/';
 | 
				
			||||||
 | 
					        seq->count++;
 | 
				
			||||||
 | 
					        furiac_exit(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// application simply add 'B' end exit
 | 
				
			||||||
 | 
					void task_b(void* p) {
 | 
				
			||||||
 | 
					    TestSwitchSequence* seq = (TestSwitchSequence*)p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    seq->sequence[seq->count] = 'B';
 | 
				
			||||||
 | 
					    seq->count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furiac_exit(p);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_ac_switch_exit(FILE* debug_uart) {
 | 
				
			||||||
 | 
					    // init sequence
 | 
				
			||||||
 | 
					    TestSwitchSequence seq;
 | 
				
			||||||
 | 
					    seq.count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furiac_start(task_a, "task A", (void*)&seq);
 | 
				
			||||||
 | 
					    // TODO how to check that all child task ends?
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    delay(10); // wait while task do its work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(strcmp(seq.sequence, "ABA/") != 0) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "wrong sequence: %s\n", seq.sequence);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								applications/tests/test_index.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								applications/tests/test_index.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include "flipper.h"
 | 
				
			||||||
 | 
					#include "debug.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_ac_create_kill(FILE* debug_uart);
 | 
				
			||||||
 | 
					bool furi_ac_switch_exit(FILE* debug_uart);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_pipe_record(FILE* debug_uart);
 | 
				
			||||||
 | 
					bool furi_holding_data(FILE* debug_uart);
 | 
				
			||||||
 | 
					bool furi_concurrent_access(FILE* debug_uart);
 | 
				
			||||||
 | 
					bool furi_nonexistent_data(FILE* debug_uart);
 | 
				
			||||||
 | 
					bool furi_mute_algorithm(FILE* debug_uart);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void flipper_test_app(void* p) {
 | 
				
			||||||
 | 
					    FILE* debug_uart = get_debug();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_ac_create_kill(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_ac_create_kill PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_ac_create_kill FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_ac_switch_exit(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_ac_switch_exit PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_ac_switch_exit FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_pipe_record(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_pipe_record PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_pipe_record FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_holding_data(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_holding_data PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_holding_data FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_concurrent_access(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_concurrent_access PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_concurrent_access FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_nonexistent_data(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_nonexistent_data PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_nonexistent_data FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_mute_algorithm(debug_uart)) {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_mute_algorithm PASSED\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        fprintf(debug_uart, "[TEST] furi_mute_algorithm FAILED\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furiac_exit(NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								applications/tests/test_index.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								applications/tests/test_index.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					void flipper_test_app(void* p);
 | 
				
			||||||
							
								
								
									
										31
									
								
								core/app.cpp
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								core/app.cpp
									
									
									
									
									
								
							@ -2,23 +2,28 @@
 | 
				
			|||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern "C" {
 | 
					extern "C" {
 | 
				
			||||||
    FILE* get_debug();
 | 
					    #include "startup.h"
 | 
				
			||||||
 | 
					    #include "furi.h"
 | 
				
			||||||
 | 
					    #include "debug.h"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern "C" void app() {
 | 
					extern "C" void app() {
 | 
				
			||||||
    FILE* debug_uart = get_debug();
 | 
					    // FURI startup
 | 
				
			||||||
 | 
					    FuriApp* handlers[sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0])];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fprintf(debug_uart, "hello Flipper!\n");
 | 
					    for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
 | 
				
			||||||
 | 
					        handlers[i] = furiac_start(FLIPPER_STARTUP[i].app, FLIPPER_STARTUP[i].name, NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GpioPin red_led = {LED_RED_GPIO_Port, LED_RED_Pin};
 | 
					    bool is_alive = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
    app_gpio_init(red_led, GpioModeOutput);
 | 
					        is_alive = false;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
 | 
				
			||||||
    
 | 
					            if(handlers[i]->handler != NULL) {
 | 
				
			||||||
    while(1) {
 | 
					                is_alive = true;
 | 
				
			||||||
        delay(100);
 | 
					 | 
				
			||||||
        app_gpio_write(red_led, true);
 | 
					 | 
				
			||||||
        delay(100);
 | 
					 | 
				
			||||||
        app_gpio_write(red_led, false);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        delay(500);
 | 
				
			||||||
 | 
					        // TODO add deferred event queue here
 | 
				
			||||||
 | 
					    } while(is_alive);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								core/debug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								core/debug.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					FILE* get_debug();
 | 
				
			||||||
@ -1,8 +1,15 @@
 | 
				
			|||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
extern "C" {
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Arduino defines
 | 
					// Arduino defines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										216
									
								
								core/furi.c
									
									
									
									
									
								
							
							
						
						
									
										216
									
								
								core/furi.c
									
									
									
									
									
								
							@ -0,0 +1,216 @@
 | 
				
			|||||||
 | 
					#include "furi.h"
 | 
				
			||||||
 | 
					#include "cmsis_os.h"
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEBUG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef DEBUG
 | 
				
			||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_RECORD_COUNT 32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static FuriRecord records[MAX_RECORD_COUNT];
 | 
				
			||||||
 | 
					static size_t current_buffer_idx = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// find record pointer by name
 | 
				
			||||||
 | 
					static FuriRecord* find_record(const char* name) {
 | 
				
			||||||
 | 
					    if(name == NULL) return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriRecord* res = NULL;
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_RECORD_COUNT; i++) {
 | 
				
			||||||
 | 
					        if(records[i].name != NULL && strcmp(name, records[i].name) == 0) {
 | 
				
			||||||
 | 
					            res = &records[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_create(const char* name, void* value, size_t size) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURI] creating %s record\n", name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(current_buffer_idx >= MAX_RECORD_COUNT) {
 | 
				
			||||||
 | 
					        // max record count exceed
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURI] max record count exceed\n");
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					        return NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    records[current_buffer_idx].mute_counter = 0;
 | 
				
			||||||
 | 
					    records[current_buffer_idx].mutex = xSemaphoreCreateMutexStatic(
 | 
				
			||||||
 | 
					        &records[current_buffer_idx].mutex_buffer
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    records[current_buffer_idx].value = value;
 | 
				
			||||||
 | 
					    records[current_buffer_idx].size = size;
 | 
				
			||||||
 | 
					    records[current_buffer_idx].name = name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
 | 
				
			||||||
 | 
					        records[current_buffer_idx].subscribers[i].allocated = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FuriRecordHandler furi_open(
 | 
				
			||||||
 | 
					    const char* name,
 | 
				
			||||||
 | 
					    bool solo,
 | 
				
			||||||
 | 
					    bool no_mute,
 | 
				
			||||||
 | 
					    FlipperRecordCallback value_callback,
 | 
				
			||||||
 | 
					    FlipperRecordStateCallback state_callback
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURI] opening %s record\n", name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get furi record by name
 | 
				
			||||||
 | 
					    FuriRecord* record = find_record(name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(record == NULL) {
 | 
				
			||||||
 | 
					        // cannot find record
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURI] cannot find record %s\n", name);
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
 | 
				
			||||||
 | 
					        return res;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // allocate subscriber
 | 
				
			||||||
 | 
					    FuriRecordSubscriber* subscriber = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
 | 
				
			||||||
 | 
					        if(!records[current_buffer_idx].subscribers[i].allocated) {
 | 
				
			||||||
 | 
					            subscriber = &records[current_buffer_idx].subscribers[i];
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(subscriber == NULL) {
 | 
				
			||||||
 | 
					        // cannot add subscriber (full)
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURI] cannot add subscriber (full)\n");
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FuriRecordHandler res = {.record = NULL, .subscriber = NULL};
 | 
				
			||||||
 | 
					        return res;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // increase mute_counter
 | 
				
			||||||
 | 
					    if(solo) {
 | 
				
			||||||
 | 
					        record->mute_counter++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // set all parameters
 | 
				
			||||||
 | 
					    subscriber->allocated = true;
 | 
				
			||||||
 | 
					    subscriber->mute_counter = record->mute_counter;
 | 
				
			||||||
 | 
					    subscriber->no_mute = no_mute;
 | 
				
			||||||
 | 
					    subscriber->cb = value_callback;
 | 
				
			||||||
 | 
					    subscriber->state_cb = state_callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // register record in application
 | 
				
			||||||
 | 
					    FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current_task->records[current_task->records_count] = record;
 | 
				
			||||||
 | 
					    current_task->records_count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriRecordHandler res = {.record = record, .subscriber = subscriber};
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_close(FuriRecordHandler* handler) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURI] closing %s record\n", handler->record->name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // deallocate subscriber
 | 
				
			||||||
 | 
					    handler->subscriber->allocated = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // set mute counter to next max value
 | 
				
			||||||
 | 
					    uint8_t max_mute_counter = 0;
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
 | 
				
			||||||
 | 
					        if(handler->record->subscribers[i].allocated) {
 | 
				
			||||||
 | 
					            if(handler->record->subscribers[i].mute_counter > max_mute_counter) {
 | 
				
			||||||
 | 
					                max_mute_counter = handler->record->subscribers[i].mute_counter;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    handler->record->mute_counter = max_mute_counter;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void furi_notify(FuriRecordHandler* handler, const void* value, size_t size) {
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) {
 | 
				
			||||||
 | 
					        if(handler->record->subscribers[i].allocated) {
 | 
				
			||||||
 | 
					            if(handler->record->subscribers[i].cb != NULL) {
 | 
				
			||||||
 | 
					                handler->record->subscribers[i].cb(value, size);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void* furi_take(FuriRecordHandler* handler) {
 | 
				
			||||||
 | 
					    // take mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return handler->record->value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furi_give(FuriRecordHandler* handler) {
 | 
				
			||||||
 | 
					    // release mutex
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_read(FuriRecordHandler* handler, void* value, size_t size) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURI] read from %s\n", handler->record->name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(handler == NULL || handler->record == NULL || value == NULL) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(size > handler->record->size) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // return false if read from pipe
 | 
				
			||||||
 | 
					    if(handler->record->value == NULL) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_take(handler);
 | 
				
			||||||
 | 
					    memcpy(value, handler->record->value, size);
 | 
				
			||||||
 | 
					    furi_give(handler);
 | 
				
			||||||
 | 
					    furi_notify(handler, value, size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furi_write(FuriRecordHandler* handler, const void* value, size_t size) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURI] write to %s\n", handler->record->name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(handler == NULL || handler->record == NULL || value == NULL) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check if closed
 | 
				
			||||||
 | 
					    if(!handler->subscriber->allocated) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(handler->record->value != NULL && size > handler->record->size) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check mute
 | 
				
			||||||
 | 
					    if(
 | 
				
			||||||
 | 
					        handler->record->mute_counter != handler->subscriber->mute_counter
 | 
				
			||||||
 | 
					        && !handler->subscriber->no_mute
 | 
				
			||||||
 | 
					    ) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(handler->record->value != NULL) {
 | 
				
			||||||
 | 
					        // real write to value
 | 
				
			||||||
 | 
					        furi_take(handler);
 | 
				
			||||||
 | 
					        memcpy(handler->record->value, value, size);
 | 
				
			||||||
 | 
					        furi_give(handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // notify subscribers
 | 
				
			||||||
 | 
					        furi_notify(handler, handler->record->value, handler->record->size);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_notify(handler, value, size);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										158
									
								
								core/furi.h
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								core/furi.h
									
									
									
									
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "cmsis_os.h"
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MAX_TASK_RECORDS 8
 | 
				
			||||||
 | 
					#define MAX_RECORD_SUBSCRIBERS 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// application is just a function
 | 
				
			||||||
 | 
					typedef void(*FlipperApplication)(void*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// pointer to value callback function
 | 
				
			||||||
 | 
					typedef void(*FlipperRecordCallback)(const void*, size_t);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    FlipperRecordStateMute, ///< record open and mute this handler
 | 
				
			||||||
 | 
					    FlipperRecordStateUnmute, ///< record unmuted
 | 
				
			||||||
 | 
					    FlipperRecordStateDeleted ///< record owner halt
 | 
				
			||||||
 | 
					} FlipperRecordState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// pointer to state callback function
 | 
				
			||||||
 | 
					typedef void(*FlipperRecordStateCallback)(FlipperRecordState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    bool allocated;
 | 
				
			||||||
 | 
					    FlipperRecordCallback cb; ///< value cb
 | 
				
			||||||
 | 
					    FlipperRecordStateCallback state_cb; ///< state cb
 | 
				
			||||||
 | 
					    uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
 | 
				
			||||||
 | 
					    bool no_mute;
 | 
				
			||||||
 | 
					} FuriRecordSubscriber;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// FURI record handler
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    const char* name;
 | 
				
			||||||
 | 
					    void* value;
 | 
				
			||||||
 | 
					    size_t size;
 | 
				
			||||||
 | 
					    StaticSemaphore_t mutex_buffer;
 | 
				
			||||||
 | 
					    SemaphoreHandle_t mutex;
 | 
				
			||||||
 | 
					    uint8_t mute_counter;
 | 
				
			||||||
 | 
					    FuriRecordSubscriber subscribers[MAX_RECORD_SUBSCRIBERS];
 | 
				
			||||||
 | 
					} FuriRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// FURI record handler for use after open
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    FuriRecord* record; ///< full record (for read/write/take/give value)
 | 
				
			||||||
 | 
					    FuriRecordSubscriber* subscriber; ///< current handler info
 | 
				
			||||||
 | 
					} FuriRecordHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// store info about active task
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    const char* name;
 | 
				
			||||||
 | 
					    FlipperApplication application;
 | 
				
			||||||
 | 
					    const char* prev_name;
 | 
				
			||||||
 | 
					    FlipperApplication prev;
 | 
				
			||||||
 | 
					    TaskHandle_t handler;
 | 
				
			||||||
 | 
					    uint8_t records_count; ///< count of records which task open
 | 
				
			||||||
 | 
					    FuriRecord* records[MAX_TASK_RECORDS]; ///< list of records which task open
 | 
				
			||||||
 | 
					} FuriApp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					Simply starts application.
 | 
				
			||||||
 | 
					It call app entrypoint with param passed as argument.
 | 
				
			||||||
 | 
					Useful for daemon applications and pop-up.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					FuriApp* furiac_start(FlipperApplication app, const 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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furiac_switch(FlipperApplication app, char* name, 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.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furiac_exit(void* param);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					Stop specified app without returning to prev application.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool furiac_kill(FuriApp* app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// find task pointer by handle
 | 
				
			||||||
 | 
					FuriApp* find_task(TaskHandle_t handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					Creates named FURI record.
 | 
				
			||||||
 | 
					\param[in] name you can open this record anywhere
 | 
				
			||||||
 | 
					\param[in] value pointer to data.
 | 
				
			||||||
 | 
					\param[in] size size of data.
 | 
				
			||||||
 | 
					If NULL, create FURI Pipe (only callbacks management, no data/mutex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Returns false if registry have not enough memory for creating.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool furi_create(const char* name, void* value, size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					Opens existing FURI record by name.
 | 
				
			||||||
 | 
					Returns NULL if record does not exist.
 | 
				
			||||||
 | 
					\param[in] solo if true another applications handlers set into "muted" state.
 | 
				
			||||||
 | 
					When appication has exited or record has closed, all handlers is unmuted.
 | 
				
			||||||
 | 
					It may be useful for concurrently acces to resources like framebuffer or beeper.
 | 
				
			||||||
 | 
					\param[in] no_mute if true, another applications cannot mute this handler.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					FuriRecordHandler furi_open(
 | 
				
			||||||
 | 
					    const char* name,
 | 
				
			||||||
 | 
					    bool solo,
 | 
				
			||||||
 | 
					    bool no_mute,
 | 
				
			||||||
 | 
					    FlipperRecordCallback value_callback,
 | 
				
			||||||
 | 
					    FlipperRecordStateCallback state_callback
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furi_close(FuriRecordHandler* handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					read message from record.
 | 
				
			||||||
 | 
					Returns true if success, false otherwise (closed/non-existent record)
 | 
				
			||||||
 | 
					Also return false if you try to read from FURI pipe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: enum return value with execution status
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool furi_read(FuriRecordHandler* record, void* data, size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					write message to record.
 | 
				
			||||||
 | 
					Returns true if success, false otherwise (closed/non-existent record or muted).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: enum return value with execution status
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					bool furi_write(FuriRecordHandler* record, const void* data, size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					lock value mutex.
 | 
				
			||||||
 | 
					It can be useful if records contain pointer to buffer which you want to change.
 | 
				
			||||||
 | 
					You must call furi_give after operation on data and
 | 
				
			||||||
 | 
					you shouldn't block executing between take and give calls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Returns pointer to data, NULL if closed/non-existent record or muted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO: enum return value with execution status
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void* furi_take(FuriRecordHandler* record);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*!
 | 
				
			||||||
 | 
					unlock value mutex.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void furi_give(FuriRecordHandler* record);
 | 
				
			||||||
							
								
								
									
										139
									
								
								core/furi_ac.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								core/furi_ac.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					#include "furi.h"
 | 
				
			||||||
 | 
					#include "cmsis_os.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEBUG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef DEBUG
 | 
				
			||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEFAULT_STACK_SIZE 1024 // Stack size in bytes
 | 
				
			||||||
 | 
					#define MAX_TASK_COUNT 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static StaticTask_t task_info_buffer[MAX_TASK_COUNT];
 | 
				
			||||||
 | 
					static StackType_t stack_buffer[MAX_TASK_COUNT][DEFAULT_STACK_SIZE / 4];
 | 
				
			||||||
 | 
					static FuriApp task_buffer[MAX_TASK_COUNT];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static size_t current_buffer_idx = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// find task pointer by handle
 | 
				
			||||||
 | 
					FuriApp* find_task(TaskHandle_t handler) {
 | 
				
			||||||
 | 
					    FuriApp* res = NULL;
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MAX_TASK_COUNT; i++) {
 | 
				
			||||||
 | 
					        if(task_equal(task_buffer[i].handler, handler)) {
 | 
				
			||||||
 | 
					            res = &task_buffer[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FuriApp* furiac_start(FlipperApplication app, const char* name, void* param) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURIAC] start %s\n", name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO check first free item (.handler == NULL) and use it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(current_buffer_idx >= MAX_TASK_COUNT) {
 | 
				
			||||||
 | 
					        // max task count exceed
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURIAC] max task count exceed\n");
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					        return NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create task on static stack memory
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].handler = xTaskCreateStatic(
 | 
				
			||||||
 | 
					        (TaskFunction_t)app,
 | 
				
			||||||
 | 
					        (const char * const)name,
 | 
				
			||||||
 | 
					        DEFAULT_STACK_SIZE / 4, // freertos specify stack size in words
 | 
				
			||||||
 | 
					        (void * const) param,
 | 
				
			||||||
 | 
					        tskIDLE_PRIORITY + 3, // normal priority
 | 
				
			||||||
 | 
					        stack_buffer[current_buffer_idx],
 | 
				
			||||||
 | 
					        &task_info_buffer[current_buffer_idx]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // save task
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].application = app;
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].prev_name = NULL;
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].prev = NULL;
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].records_count = 0;
 | 
				
			||||||
 | 
					    task_buffer[current_buffer_idx].name = name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    current_buffer_idx++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return &task_buffer[current_buffer_idx - 1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool furiac_kill(FuriApp* app) {
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURIAC] kill %s\n", app->name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check handler
 | 
				
			||||||
 | 
					    if(app == NULL || app->handler == NULL) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // kill task
 | 
				
			||||||
 | 
					    vTaskDelete(app->handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // cleanup its registry
 | 
				
			||||||
 | 
					    // TODO realy free memory
 | 
				
			||||||
 | 
					    app->handler = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furiac_exit(void* param) {
 | 
				
			||||||
 | 
					    // get current task handler
 | 
				
			||||||
 | 
					    FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // run prev
 | 
				
			||||||
 | 
					    if(current_task != NULL) {
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURIAC] exit %s\n", current_task->name);
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(current_task->prev != NULL) {
 | 
				
			||||||
 | 
					            furiac_start(current_task->prev, current_task->prev_name, param);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            #ifdef DEBUG
 | 
				
			||||||
 | 
					                printf("[FURIAC] no prev\n");
 | 
				
			||||||
 | 
					            #endif
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // cleanup registry
 | 
				
			||||||
 | 
					        // TODO realy free memory
 | 
				
			||||||
 | 
					        current_task->handler = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // kill itself
 | 
				
			||||||
 | 
					     vTaskDelete(NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void furiac_switch(FlipperApplication app, char* name, void* param) {
 | 
				
			||||||
 | 
					    // get current task handler
 | 
				
			||||||
 | 
					    FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(current_task == NULL) {
 | 
				
			||||||
 | 
					        #ifdef DEBUG
 | 
				
			||||||
 | 
					            printf("[FURIAC] no current task found\n");
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #ifdef DEBUG
 | 
				
			||||||
 | 
					        printf("[FURIAC] switch %s to %s\n", current_task->name, name);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // run next
 | 
				
			||||||
 | 
					    FuriApp* next = furiac_start(app, name, param);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(next != NULL) {
 | 
				
			||||||
 | 
					        // save current application pointer as prev
 | 
				
			||||||
 | 
					        next->prev = current_task->application;
 | 
				
			||||||
 | 
					        next->prev_name = current_task->name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // kill itself
 | 
				
			||||||
 | 
					        vTaskDelete(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -154,7 +154,8 @@ AS_INCLUDES =  \
 | 
				
			|||||||
# C includes
 | 
					# C includes
 | 
				
			||||||
C_INCLUDES =  \
 | 
					C_INCLUDES =  \
 | 
				
			||||||
-IInc \
 | 
					-IInc \
 | 
				
			||||||
-I../app \
 | 
					-I../applications \
 | 
				
			||||||
 | 
					-I../core \
 | 
				
			||||||
-IDrivers/STM32L4xx_HAL_Driver/Inc \
 | 
					-IDrivers/STM32L4xx_HAL_Driver/Inc \
 | 
				
			||||||
-IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \
 | 
					-IDrivers/STM32L4xx_HAL_Driver/Inc/Legacy \
 | 
				
			||||||
-IMiddlewares/Third_Party/FreeRTOS/Source/include \
 | 
					-IMiddlewares/Third_Party/FreeRTOS/Source/include \
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,30 @@
 | 
				
			|||||||
#include "main.h"
 | 
					#include "main.h"
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void osDelay(uint32_t ms);
 | 
					void osDelay(uint32_t ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// some FreeRTOS types
 | 
				
			||||||
 | 
					typedef void(*TaskFunction_t)(void*);
 | 
				
			||||||
 | 
					typedef uint32_t UBaseType_t;
 | 
				
			||||||
 | 
					typedef uint32_t StackType_t;
 | 
				
			||||||
 | 
					typedef uint32_t StaticTask_t;
 | 
				
			||||||
 | 
					typedef pthread_t* TaskHandle_t;
 | 
				
			||||||
 | 
					typedef uint32_t StaticSemaphore_t;
 | 
				
			||||||
 | 
					typedef void* SemaphoreHandle_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define tskIDLE_PRIORITY 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TaskHandle_t xTaskCreateStatic(
 | 
				
			||||||
 | 
					    TaskFunction_t pxTaskCode,
 | 
				
			||||||
 | 
					    const char * const pcName,
 | 
				
			||||||
 | 
					    const uint32_t ulStackDepth,
 | 
				
			||||||
 | 
					    void * const pvParameters,
 | 
				
			||||||
 | 
					    UBaseType_t uxPriority,
 | 
				
			||||||
 | 
					    StackType_t * const puxStackBuffer,
 | 
				
			||||||
 | 
					    StaticTask_t * const pxTaskBuffer
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void vTaskDelete(TaskHandle_t xTask);
 | 
				
			||||||
 | 
					TaskHandle_t xTaskGetCurrentTaskHandle(void);
 | 
				
			||||||
 | 
					SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer);
 | 
				
			||||||
 | 
					bool task_equal(TaskHandle_t a, TaskHandle_t b);
 | 
				
			||||||
 | 
				
			|||||||
@ -25,11 +25,17 @@ Src/main.c
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
CPP_SOURCES = ../core/app.cpp
 | 
					CPP_SOURCES = ../core/app.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
C_SOURCES += ../core/write.c
 | 
					C_SOURCES += ../core/debug.c
 | 
				
			||||||
 | 
					C_SOURCES += ../core/furi.c
 | 
				
			||||||
 | 
					C_SOURCES += ../core/furi_ac.c
 | 
				
			||||||
C_SOURCES += Src/flipper_hal.c
 | 
					C_SOURCES += Src/flipper_hal.c
 | 
				
			||||||
C_SOURCES += Src/lo_os.c
 | 
					C_SOURCES += Src/lo_os.c
 | 
				
			||||||
C_SOURCES += Src/lo_hal.c
 | 
					C_SOURCES += Src/lo_hal.c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					C_SOURCES += ../applications/tests/furiac_test.c
 | 
				
			||||||
 | 
					C_SOURCES += ../applications/tests/furi_record_test.c
 | 
				
			||||||
 | 
					C_SOURCES += ../applications/tests/test_index.c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#######################################
 | 
					#######################################
 | 
				
			||||||
# binaries
 | 
					# binaries
 | 
				
			||||||
#######################################
 | 
					#######################################
 | 
				
			||||||
@ -56,10 +62,11 @@ C_DEFS =  \
 | 
				
			|||||||
# C includes
 | 
					# C includes
 | 
				
			||||||
C_INCLUDES =  \
 | 
					C_INCLUDES =  \
 | 
				
			||||||
-IInc \
 | 
					-IInc \
 | 
				
			||||||
-I../app
 | 
					-I../applications \
 | 
				
			||||||
 | 
					-I../core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# compile gcc flags
 | 
					# compile gcc flags
 | 
				
			||||||
CFLAGS = $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
 | 
					CFLAGS = $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -pthread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ifeq ($(DEBUG), 1)
 | 
					ifeq ($(DEBUG), 1)
 | 
				
			||||||
CFLAGS += -g -gdwarf-2
 | 
					CFLAGS += -g -gdwarf-2
 | 
				
			||||||
@ -78,7 +85,7 @@ CPPFLAGS = -fno-threadsafe-statics
 | 
				
			|||||||
# libraries
 | 
					# libraries
 | 
				
			||||||
LIBS = -lc -lm
 | 
					LIBS = -lc -lm
 | 
				
			||||||
LIBDIR = 
 | 
					LIBDIR = 
 | 
				
			||||||
LDFLAGS = $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
 | 
					LDFLAGS = $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -pthread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# default action: build all
 | 
					# default action: build all
 | 
				
			||||||
all: $(BUILD_DIR)/$(TARGET)
 | 
					all: $(BUILD_DIR)/$(TARGET)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,85 @@
 | 
				
			|||||||
#include "cmsis_os.h"
 | 
					#include "cmsis_os.h"
 | 
				
			||||||
#include <unistd.h>
 | 
					#include <unistd.h>
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <pthread.h>
 | 
				
			||||||
 | 
					#include <errno.h>
 | 
				
			||||||
 | 
					#include <signal.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void osDelay(uint32_t ms) {
 | 
					void osDelay(uint32_t ms) {
 | 
				
			||||||
	usleep(ms * 1000);
 | 
					 | 
				
			||||||
    printf("[DELAY] %d ms\n", ms);
 | 
					    printf("[DELAY] %d ms\n", ms);
 | 
				
			||||||
 | 
					    usleep(ms * 1000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// temporary struct to pass function ptr and param to wrapper
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    TaskFunction_t func;
 | 
				
			||||||
 | 
					    void * param;
 | 
				
			||||||
 | 
					} PthreadTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void* pthread_wrapper(void* p) {
 | 
				
			||||||
 | 
					    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0x00);
 | 
				
			||||||
 | 
					    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0x00);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PthreadTask* task = (PthreadTask*)p;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    task->func(task->param);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TaskHandle_t xTaskCreateStatic(
 | 
				
			||||||
 | 
					    TaskFunction_t pxTaskCode,
 | 
				
			||||||
 | 
					    const char * const pcName,
 | 
				
			||||||
 | 
					    const uint32_t ulStackDepth,
 | 
				
			||||||
 | 
					    void * const pvParameters,
 | 
				
			||||||
 | 
					    UBaseType_t uxPriority,
 | 
				
			||||||
 | 
					    StackType_t * const puxStackBuffer,
 | 
				
			||||||
 | 
					    StaticTask_t * const pxTaskBuffer
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    TaskHandle_t thread = malloc(sizeof(TaskHandle_t));
 | 
				
			||||||
 | 
					    PthreadTask* task = malloc(sizeof(PthreadTask));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    task->func = pxTaskCode;
 | 
				
			||||||
 | 
					    task->param = pvParameters;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pthread_create(thread, NULL, pthread_wrapper, (void*)task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return thread;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void vTaskDelete(TaskHandle_t xTask) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(xTask == NULL) {
 | 
				
			||||||
 | 
					        // kill itself
 | 
				
			||||||
 | 
					        pthread_exit(NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // maybe thread already join
 | 
				
			||||||
 | 
					    if (pthread_kill(*xTask, 0) == ESRCH) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // send thread_child signal to stop it сигнал, который ее завершает
 | 
				
			||||||
 | 
					    pthread_cancel(*xTask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // wait for join and close descriptor
 | 
				
			||||||
 | 
					    pthread_join(*xTask, 0x00);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // cleanup thread handler
 | 
				
			||||||
 | 
					    *xTask = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TaskHandle_t xTaskGetCurrentTaskHandle(void) {
 | 
				
			||||||
 | 
					    TaskHandle_t thread = malloc(sizeof(TaskHandle_t));
 | 
				
			||||||
 | 
					    *thread = pthread_self();
 | 
				
			||||||
 | 
					    return thread;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool task_equal(TaskHandle_t a, TaskHandle_t b) {
 | 
				
			||||||
 | 
					    if(a == NULL || b == NULL) return false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return pthread_equal(*a, *b) != 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t* pxMutexBuffer) {
 | 
				
			||||||
 | 
					    // TODO add posix mutex init
 | 
				
			||||||
 | 
					    return NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -8,29 +8,55 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Start and change application wrokflow
 | 
					### 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_start(void(app*)(void*), char* name, void* param)`**
 | 
				
			||||||
* `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.
 | 
					
 | 
				
			||||||
 | 
					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
 | 
					### Exit application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `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.
 | 
					**`void furiac_exit(void* param)`**
 | 
				
			||||||
* `furiac_kill(FuriApp app)` stop specified `app` without returning to `prev` application.
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `FuriRecord furi_create(char* name)` creates named FURI record. Returns NULL if registry have not enough memory for creating.
 | 
					**`bool furi_create(char* name, void* value, size_t size)`**
 | 
				
			||||||
* `FuriRecord furi_open(char* name, bool solo, bool no_mute)` opens existing FURI record by name. Returns NULL if record does not exist. If `solo` is true **another applications handlers set into "muted" state**. 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. If `no_mute` is true, another applications cannot mute this handler.
 | 
					
 | 
				
			||||||
* `bool furi_close(FuriRecord record)` close handler and unmute anothers.
 | 
					creates named FURI record. Returns NULL if registry have not enough memory for creating. If value is NULL, create FURI Pipe (only callbacks management, no data/mutex).
 | 
				
			||||||
* `bool furi_read(FuriRecord record, void* data, size_t size)` read message from record. Returns true if success, false otherwise.
 | 
					
 | 
				
			||||||
* `bool furi_write(FuriRecord record, const void* data, size_t size)` write message to record. Returns true if success, false otherwise (handler gone or muted).
 | 
					**`FuriRecordHandler furi_open(char* name, bool solo, bool no_mute, void(*FlipperRecordCallback)(const void*, size_t), void(*FlipperRecordStateCallback)(FlipperRecordState))`**
 | 
				
			||||||
* `bool furi_take(FuriRecord record, void* data, size_t size)` works as `furi_read` but lock global mutex. It can be useful if records contain pointer to buffer which you want to change. You must call `furi_give` after operation on data and you cannot block executing between `take` and `give` calls
 | 
					
 | 
				
			||||||
* `bool furi_give(FuriRecord record, const void* data, size_t size)` works as `furi_wrte` but unlock global mutex.
 | 
					opens existing FURI record by name. Returns NULL if record does not exist. If `solo` is true **another applications handlers set into "muted" state**. 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. If `no_mute` is true, another applications cannot mute this handler.
 | 
				
			||||||
* `bool furi_global_take()` lock global mutex (as `furi_take` but without read)
 | 
					
 | 
				
			||||||
* `boold furi_global_give()` unlock global mutex ((as `furi_give` but without write))
 | 
					**`bool furi_close(FuriRecordHandler* record)`**
 | 
				
			||||||
* `bool furi_unmute(FuriRecord record)` unmutes muted record.
 | 
					
 | 
				
			||||||
* `bool furi_mute(FuriRecord record)` mutes unmuted record.
 | 
					close handler and unmute anothers.
 | 
				
			||||||
* `bool furi_subscribe(FuriRecord record, void(cb*)(const void* data, size_t size))` set record change callback.
 | 
					
 | 
				
			||||||
* `bool furi_state_subscribe(FuriRecord record, void(cb*)(bool muted))` set record state change callback (mute/unmute). For example, you can unmute itself after some application open same record, or redraw your application UI when popup application ends.
 | 
					**`bool furi_read(FuriRecordHandler* record, void* data, size_t size)`**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					read message from record. Returns true if success, false otherwise.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**`bool furi_write(FuriRecordHandler* record, const void* data, size_t size)`**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					write message to record. Returns true if success, false otherwise (handler gone or muted).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**`void* furi_take(FuriRecordHandler* record)` works as `furi_read`**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lock value mutex. It can be useful if records contain pointer to buffer which you want to change. You must call `furi_give` after operation on data and you cannot block executing between `take` and `give` calls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**`bool furi_give(FuriRecordHandler* record)`**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unlock value mutex works as `furi_wrte` but unlock global mutex.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Usage example
 | 
					# Usage example
 | 
				
			||||||
_Diagram below describes furi states_
 | 
					_Diagram below describes furi states_
 | 
				
			||||||
@ -44,11 +70,5 @@ _Diagram below describes furi states_
 | 
				
			|||||||
* If "Your cool app" needs some backend app, it call this by `furiac_start` and then kill by `furiac_kill`
 | 
					* If "Your cool app" needs some backend app, it call this by `furiac_start` and then kill by `furiac_kill`
 | 
				
			||||||
* If background task needs to show popup message (for example "Low battery") it can call new app or simply open "/ui/fb" record.
 | 
					* If background task needs to show popup message (for example "Low battery") it can call new app or simply open "/ui/fb" record.
 | 
				
			||||||
* When "/ui/fb" record is opened by popup message, FURI mute framebuffer handle in "Your cool app". This prevent to overwrite popup message by application drawing.
 | 
					* When "/ui/fb" record is opened by popup message, FURI mute framebuffer handle in "Your cool app". This prevent to overwrite popup message by application drawing.
 | 
				
			||||||
* "Status bar" framebuffer handle also is muted, but it call `furi_unmute` and unmute itself.
 | 
					* "Status bar" framebuffer handle not is muted, beacuse open framebuffer with no_mute=true.
 | 
				
			||||||
* After popup message is closed by `furiac_exit` or closing "/ui/fb", FURI unmute previous muted "Your cool app" framebuffer handle.
 | 
					* After popup message is closed by `furiac_exit` or closing "/ui/fb", FURI unmute previous muted "Your cool app" framebuffer handle.
 | 
				
			||||||
 | 
					 | 
				
			||||||
_Status bar also get mute and unmute itself every time when Home screen, Menu or "Your cool app" open framebuffer but diagramm not show it_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Data storage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `furi_create_var(char* name)` create static-like value handler. You can use all furi_ calls for
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user