Core api concept (#144)

* add input debounce code from old fw

* exampl of input api

* change input API to get/release

* revert input API to read

* pointer instead of instance

* add input API description

* add display API

* rewrite display names

* migrate to valuemanager

* add LED API

* add closing brakets

* add sound api

* fix led api

* basic api

* rename API pages

* change pubsub implementation

* move FURI AC -> flapp, add valuemutex example, add valuemanager implementation

* pubsub usage example

* user led example

* update example

* simplify input

* add composed display

* add SPI/GPIO and CC1101 bus

* change cc1101 api

* spi api and devices

* spi api and devices

* move SPI to page, add GPIO

* not block pin open

* backlight API and more

* add minunit tests

* fix logging

* ignore unexisting time service on embedded targets

* fix warning, issue with printf

* Deprecate furi_open and furi_close (#167)

Rename existing furi_open and furi_close to deprecated version

* add exitcode

* migrate to printf

* indicate test by leds

* add testing description

* rename furi.h

* wip basic api

* add valuemutex, pubsub, split files

* add value expanders

* value mutex realization and tests

* valuemutex test added to makefile

* do not build unimplemented files

* fix build furmware target f2

* redesigned minunit tests to allow testing in separate files

* test file for valuemutex minunit testing

* minunit partial test valuemutex

* local cmsis_os2 mutex bindings

* implement furi open/create, tests

* migrate concurrent_access to ValueMutex

* add spi header

* Lib: add mlib submodule.

Co-authored-by: rusdacent <rusdacentx0x08@gmail.com>
Co-authored-by: DrZlo13 <who.just.the.doctor@gmail.com>
This commit is contained in:
coreglitch 2020-10-13 14:22:43 +06:00 committed by GitHub
parent b7c30154f4
commit 942bbfaefe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1874 additions and 692 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "lib/STM32CubeL4"] [submodule "lib/STM32CubeL4"]
path = lib/STM32CubeL4 path = lib/STM32CubeL4
url = https://github.com/STMicroelectronics/STM32CubeL4.git url = https://github.com/STMicroelectronics/STM32CubeL4.git
[submodule "lib/mlib"]
path = lib/mlib
url = https://github.com/P-p-H-d/mlib.git

View File

@ -16,6 +16,7 @@ C_SOURCES += $(APP_DIR)/tests/furiac_test.c
C_SOURCES += $(APP_DIR)/tests/furi_record_test.c C_SOURCES += $(APP_DIR)/tests/furi_record_test.c
C_SOURCES += $(APP_DIR)/tests/test_index.c C_SOURCES += $(APP_DIR)/tests/test_index.c
C_SOURCES += $(APP_DIR)/tests/minunit_test.c C_SOURCES += $(APP_DIR)/tests/minunit_test.c
C_SOURCES += $(APP_DIR)/tests/furi_valuemutex_test.c
endif endif
APP_EXAMPLE_BLINK ?= 0 APP_EXAMPLE_BLINK ?= 0

View File

@ -9,7 +9,8 @@ void coreglitch_demo_0(void* p) {
fuprintf(log, "coreglitch demo!\n"); fuprintf(log, "coreglitch demo!\n");
// open record // open record
FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); FuriRecordSubscriber* fb_record =
furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(log, "[widget] cannot create fb record\n"); fuprintf(log, "[widget] cannot create fb record\n");

View File

@ -143,7 +143,7 @@ void display_u8g2(void* p) {
&_u8g2); // send init sequence to the display, display is in sleep mode after this &_u8g2); // send init sequence to the display, display is in sleep mode after this
u8g2_SetContrast(&_u8g2, 36); u8g2_SetContrast(&_u8g2, 36);
if(!furi_create("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) { if(!furi_create_deprecated("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) {
fuprintf(log, "[display_u8g2] cannot create fb record\n"); fuprintf(log, "[display_u8g2] cannot create fb record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
@ -162,7 +162,7 @@ void display_u8g2(void* p) {
// subscribe to record. ctx will be passed to handle_fb_change // subscribe to record. ctx will be passed to handle_fb_change
FuriRecordSubscriber* fb_record = FuriRecordSubscriber* fb_record =
furi_open("u8g2_fb", false, false, handle_fb_change, NULL, &ctx); furi_open_deprecated("u8g2_fb", false, false, handle_fb_change, NULL, &ctx);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(log, "[display] cannot open fb record\n"); fuprintf(log, "[display] cannot open fb record\n");

View File

@ -49,14 +49,14 @@ void fatfs_list(void* p) {
furi_log = get_default_log(); furi_log = get_default_log();
FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n"); fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* event_record = FuriRecordSubscriber* event_record =
furi_open("input_events", false, false, event_cb, NULL, event_queue); furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue);
if(event_record == NULL) { if(event_record == NULL) {
fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n"); fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n");
furiac_exit(NULL); furiac_exit(NULL);

View File

@ -16,9 +16,9 @@ static void event_cb(const void* value, size_t size, void* ctx) {
void application_input_dump(void* p) { void application_input_dump(void* p) {
// open record // open record
FuriRecordSubscriber* state_record = FuriRecordSubscriber* state_record =
furi_open("input_state", false, false, state_cb, NULL, NULL); furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL);
FuriRecordSubscriber* event_record = FuriRecordSubscriber* event_record =
furi_open("input_events", false, false, event_cb, NULL, NULL); furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL);
for(;;) { for(;;) {
delay(100); delay(100);

View File

@ -60,7 +60,7 @@ void application_ipc_display(void* p) {
} }
// create record // create record
if(!furi_create("test_fb", (void*)_framebuffer, FB_SIZE)) { if(!furi_create_deprecated("test_fb", (void*)_framebuffer, FB_SIZE)) {
fuprintf(log, "[display] cannot create fb record\n"); fuprintf(log, "[display] cannot create fb record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
@ -79,7 +79,7 @@ void application_ipc_display(void* p) {
// subscribe to record. ctx will be passed to handle_fb_change // subscribe to record. ctx will be passed to handle_fb_change
FuriRecordSubscriber* fb_record = FuriRecordSubscriber* fb_record =
furi_open("test_fb", false, false, handle_fb_change, NULL, &ctx); furi_open_deprecated("test_fb", false, false, handle_fb_change, NULL, &ctx);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(log, "[display] cannot open fb record\n"); fuprintf(log, "[display] cannot open fb record\n");
@ -124,7 +124,8 @@ void application_ipc_widget(void* p) {
FuriRecordSubscriber* log = get_default_log(); FuriRecordSubscriber* log = get_default_log();
// open record // open record
FuriRecordSubscriber* fb_record = furi_open("test_fb", false, false, NULL, NULL, NULL); FuriRecordSubscriber* fb_record =
furi_open_deprecated("test_fb", false, false, NULL, NULL, NULL);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(log, "[widget] cannot create fb record\n"); fuprintf(log, "[widget] cannot create fb record\n");

View File

@ -5,7 +5,7 @@ void u8g2_example(void* p) {
FuriRecordSubscriber* log = get_default_log(); FuriRecordSubscriber* log = get_default_log();
// open record // open record
FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
if(fb_record == NULL) { if(fb_record == NULL) {
fuprintf(log, "[widget] cannot create fb record\n"); fuprintf(log, "[widget] cannot create fb record\n");

View File

@ -14,7 +14,7 @@ void u8g2_qrcode(void* p) {
FuriRecordSubscriber* log = get_default_log(); FuriRecordSubscriber* log = get_default_log();
// open record // open record
FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
// Allocate a chunk of memory to store the QR code // Allocate a chunk of memory to store the QR code
// https://github.com/ricmoo/QRCode // https://github.com/ricmoo/QRCode

View File

@ -1,7 +1,7 @@
#include <input/input.h> #include <input/input.h>
#include <input_priv.h> #include <input_priv.h>
#include <stdio.h> #include <stdio.h>
#include <furi.h> #include <flipper.h>
static volatile bool initialized = false; static volatile bool initialized = false;
static SemaphoreHandle_t event; static SemaphoreHandle_t event;
@ -16,25 +16,25 @@ void input_task(void* p) {
event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore);
if(!furi_create("input_state", (void*)&input_state, sizeof(input_state))) { if(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) {
printf("[input_task] cannot create the input_state record\n"); printf("[input_task] cannot create the input_state record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* input_state_record = FuriRecordSubscriber* input_state_record =
furi_open("input_state", false, false, NULL, NULL, NULL); furi_open_deprecated("input_state", false, false, NULL, NULL, NULL);
if(input_state_record == NULL) { if(input_state_record == NULL) {
printf("[input_task] cannot open the input_state record\n"); printf("[input_task] cannot open the input_state record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
if(!furi_create("input_events", NULL, 0)) { if(!furi_create_deprecated("input_events", NULL, 0)) {
printf("[input_task] cannot create the input_events record\n"); printf("[input_task] cannot create the input_events record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
FuriRecordSubscriber* input_events_record = FuriRecordSubscriber* input_events_record =
furi_open("input_events", false, false, NULL, NULL, NULL); furi_open_deprecated("input_events", false, false, NULL, NULL, NULL);
if(input_events_record == NULL) { if(input_events_record == NULL) {
printf("[input_task] cannot open the input_events record\n"); printf("[input_task] cannot open the input_events record\n");
furiac_exit(NULL); furiac_exit(NULL);

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "furi.h" #include "flipper.h"
#define FURI_LIB (const char*[]) #define FURI_LIB (const char*[])

View File

@ -1,245 +1,18 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "flipper.h" #include "flipper.h"
#include "flipper_v2.h"
#include "log.h" #include "log.h"
#include "minunit.h"
/* void test_furi_create_open() {
TEST: pipe record // 1. Create record
uint8_t test_data = 0;
1. create pipe record mu_check(furi_create("test/holding", (void*)&test_data));
2. Open/subscribe to it
3. write data
4. check that subscriber get data
5. try to read, get error
6. close record
7. try to write, get error
*/
static uint8_t pipe_record_value = 0;
void pipe_record_cb(const void* value, size_t size, void* ctx) {
// hold value to static var
pipe_record_value = *((uint8_t*)value);
}
bool test_furi_pipe_record() {
// 1. create pipe record
if(!furi_create("test/pipe", NULL, 0)) {
printf("cannot create record\n");
return false;
}
// 2. Open/subscribe to it
FuriRecordSubscriber* pipe_record =
furi_open("test/pipe", false, false, pipe_record_cb, NULL, NULL);
if(pipe_record == NULL) {
printf("cannot open record\n");
return false;
}
const uint8_t WRITE_VALUE = 1;
// 3. write data
if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
printf("cannot write to record\n");
return false;
}
// 4. check that subscriber get data
if(pipe_record_value != WRITE_VALUE) {
printf("wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE);
return false;
}
// 5. try to read, get error
uint8_t read_value = 0;
if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) {
printf("reading from pipe record not allowed\n");
return false;
}
// 6. close record
furi_close(pipe_record);
// 7. try to write, get error
if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
printf("writing to closed record not allowed\n");
return false;
}
return true;
}
/*
TEST: holding data
1. Create holding record
2. Open/Subscribe on it
3. Write data
4. Check that subscriber get data
5. Read and check data
6. Try to write/read wrong size of data
*/
static uint8_t holding_record_value = 0;
void holding_record_cb(const void* value, size_t size, void* ctx) {
// hold value to static var
holding_record_value = *((uint8_t*)value);
}
bool test_furi_holding_data() {
// 1. Create holding record
uint8_t holder = 0;
if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
printf("cannot create record\n");
return false;
}
// 2. Open/Subscribe on it
FuriRecordSubscriber* holding_record =
furi_open("test/holding", false, false, holding_record_cb, NULL, NULL);
if(holding_record == NULL) {
printf("cannot open record\n");
return false;
}
const uint8_t WRITE_VALUE = 1;
// 3. write data
if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
printf("cannot write to record\n");
return false;
}
// 4. check that subscriber get data
if(holding_record_value != WRITE_VALUE) {
printf("wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE);
return false;
}
// 5. Read and check data
uint8_t read_value = 0;
if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) {
printf("cannot read from record\n");
return false;
}
if(read_value != WRITE_VALUE) {
printf("wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE);
return false;
}
// 6. Try to write/read wrong size of data
if(furi_write(holding_record, &WRITE_VALUE, 100)) {
printf("overflowed write not allowed\n");
return false;
}
if(furi_read(holding_record, &read_value, 100)) {
printf("overflowed read not allowed\n");
return false;
}
return true;
}
/*
TEST: concurrent access
1. Create holding record
2. Open it twice
3. Change value simultaneously in two app and check integrity
*/
// TODO this test broke because mutex in furi is not implemented
typedef struct {
// a and b must be equal
uint8_t a;
uint8_t b;
} ConcurrentValue;
void furi_concurent_app(void* p) {
FuriRecordSubscriber* holding_record =
furi_open("test/concurrent", false, false, NULL, NULL, NULL);
if(holding_record == NULL) {
printf("cannot open record\n");
furiac_exit(NULL);
}
for(size_t i = 0; i < 10; i++) {
ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
if(value == NULL) {
printf("cannot take record\n");
furi_give(holding_record);
furiac_exit(NULL);
}
// emulate read-modify-write broken by context switching
uint8_t a = value->a;
uint8_t b = value->b;
a++;
b++;
delay(2); // this is only for test, do not add delay between take/give in prod!
value->a = a;
value->b = b;
furi_give(holding_record);
}
furiac_exit(NULL);
}
bool test_furi_concurrent_access() {
// 1. Create holding record
ConcurrentValue holder = {.a = 0, .b = 0};
if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
printf("cannot create record\n");
return false;
}
// 2. Open it // 2. Open it
FuriRecordSubscriber* holding_record = void* record = furi_open("test/holding");
furi_open("test/concurrent", false, false, NULL, NULL, NULL); mu_assert_pointers_eq(record, &test_data);
if(holding_record == NULL) {
printf("cannot open record\n");
return false;
}
// 3. Create second app for interact with it
FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", NULL);
// 4. multiply ConcurrentValue::a
for(size_t i = 0; i < 4; i++) {
ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
if(value == NULL) {
printf("cannot take record\n");
furi_give(holding_record);
return false;
}
// emulate read-modify-write broken by context switching
uint8_t a = value->a;
uint8_t b = value->b;
a++;
b++;
value->a = a;
delay(10); // this is only for test, do not add delay between take/give in prod!
value->b = b;
furi_give(holding_record);
}
delay(50);
if(second_app->handler != NULL) {
printf("second app still alive\n");
return false;
}
if(holder.a != holder.b) {
printf("broken integrity: a=%d, b=%d\n", holder.a, holder.b);
return false;
}
return true;
} }
/* /*
@ -309,14 +82,14 @@ void mute_record_state_cb(FlipperRecordState state, void* ctx) {
void furi_mute_parent_app(void* p) { void furi_mute_parent_app(void* p) {
// 1. Create pipe record // 1. Create pipe record
if(!furi_create("test/mute", NULL, 0)) { if(!furi_create_deprecated("test/mute", NULL, 0)) {
printf("cannot create record\n"); printf("cannot create record\n");
furiac_exit(NULL); furiac_exit(NULL);
} }
// 2. Open watch handler: solo=false, no_mute=false, subscribe to data // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
FuriRecordSubscriber* watch_handler = FuriRecordSubscriber* watch_handler =
furi_open("test/mute", false, false, mute_record_cb, NULL, NULL); furi_open_deprecated("test/mute", false, false, mute_record_cb, NULL, NULL);
if(watch_handler == NULL) { if(watch_handler == NULL) {
printf("cannot open watch handler\n"); printf("cannot open watch handler\n");
furiac_exit(NULL); furiac_exit(NULL);
@ -336,7 +109,7 @@ bool test_furi_mute_algorithm() {
// 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state. // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
FuriRecordSubscriber* handler_a = FuriRecordSubscriber* handler_a =
furi_open("test/mute", false, false, NULL, mute_record_state_cb, NULL); furi_open_deprecated("test/mute", false, false, NULL, mute_record_state_cb, NULL);
if(handler_a == NULL) { if(handler_a == NULL) {
printf("cannot open handler A\n"); printf("cannot open handler A\n");
return false; return false;
@ -356,7 +129,8 @@ bool test_furi_mute_algorithm() {
} }
// 3. Open handler B: solo=true, no_mute=true, NULL subscriber. // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
FuriRecordSubscriber* handler_b = furi_open("test/mute", true, true, NULL, NULL, NULL); FuriRecordSubscriber* handler_b =
furi_open_deprecated("test/mute", true, true, NULL, NULL, NULL);
if(handler_b == NULL) { if(handler_b == NULL) {
printf("cannot open handler B\n"); printf("cannot open handler B\n");
return false; return false;
@ -395,7 +169,8 @@ bool test_furi_mute_algorithm() {
} }
// 4. Open hadler C: solo=true, no_mute=false, NULL subscriber. // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
FuriRecordSubscriber* handler_c = furi_open("test/mute", true, false, NULL, NULL, NULL); FuriRecordSubscriber* handler_c =
furi_open_deprecated("test/mute", true, false, NULL, NULL, NULL);
if(handler_c == NULL) { if(handler_c == NULL) {
printf("cannot open handler C\n"); printf("cannot open handler C\n");
return false; return false;
@ -406,7 +181,8 @@ bool test_furi_mute_algorithm() {
// TODO: Try to write data to C and check that subscriber get data. // TODO: Try to write data to C and check that subscriber get data.
// 5. Open handler D: solo=false, no_mute=false, NULL subscriber. // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
FuriRecordSubscriber* handler_d = furi_open("test/mute", false, false, NULL, NULL, NULL); FuriRecordSubscriber* handler_d =
furi_open_deprecated("test/mute", false, false, NULL, NULL, NULL);
if(handler_d == NULL) { if(handler_d == NULL) {
printf("cannot open handler D\n"); printf("cannot open handler D\n");
return false; return false;

View File

@ -0,0 +1,123 @@
#include <stdio.h>
#include <string.h>
#include "flipper_v2.h"
#include "log.h"
#include "minunit.h"
void test_furi_valuemutex() {
const int init_value = 0xdeadbeef;
const int changed_value = 0x12345678;
int value = init_value;
bool result;
ValueMutex valuemutex;
// init mutex case
result = init_mutex(&valuemutex, &value, sizeof(value));
mu_assert(result, "init mutex failed");
// acquire mutex case
int* value_pointer = acquire_mutex(&valuemutex, 100);
mu_assert_pointers_eq(value_pointer, &value);
// second acquire mutex case
int* value_pointer_second = acquire_mutex(&valuemutex, 100);
mu_assert_pointers_eq(value_pointer_second, NULL);
// change value case
*value_pointer = changed_value;
mu_assert_int_eq(value, changed_value);
// release mutex case
result = release_mutex(&valuemutex, &value);
mu_assert(result, "release mutex failed");
// TODO
//acquire mutex blocking case
//write mutex blocking case
//read mutex blocking case
}
/*
TEST: concurrent access
1. Create holding record
2. Open it twice
3. Change value simultaneously in two app and check integrity
*/
// TODO this test broke because mutex in furi is not implemented
typedef struct {
// a and b must be equal
uint8_t a;
uint8_t b;
} ConcurrentValue;
void furi_concurent_app(void* p) {
ValueMutex* mutex = (ValueMutex*)p;
if(mutex == NULL) {
printf("cannot open mutex\n");
furiac_exit(NULL);
}
for(size_t i = 0; i < 10; i++) {
ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(mutex);
if(value == NULL) {
printf("cannot take record\n");
release_mutex(mutex, value);
furiac_exit(NULL);
}
// emulate read-modify-write broken by context switching
uint8_t a = value->a;
uint8_t b = value->b;
a++;
b++;
delay(2);
value->a = a;
value->b = b;
release_mutex(mutex, value);
}
furiac_exit(NULL);
}
void test_furi_concurrent_access() {
// 1. Create holding record
ConcurrentValue value = {.a = 0, .b = 0};
ValueMutex mutex;
mu_check(init_mutex(&mutex, &value, sizeof(value)));
// 3. Create second app for interact with it
FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", (void*)&mutex);
// 4. multiply ConcurrentValue::a
for(size_t i = 0; i < 4; i++) {
ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(&mutex);
if(value == NULL) {
release_mutex(&mutex, value);
mu_fail("cannot take record\n");
}
// emulate read-modify-write broken by context switching
uint8_t a = value->a;
uint8_t b = value->b;
a++;
b++;
value->a = a;
delay(10); // this is only for test, do not add delay between take/give in prod!
value->b = b;
release_mutex(&mutex, value);
}
delay(50);
mu_assert_pointers_eq(second_app->handler, NULL);
mu_assert_int_eq(value.a, value.b);
}

View File

@ -6,12 +6,15 @@
bool test_furi_ac_create_kill(); bool test_furi_ac_create_kill();
bool test_furi_ac_switch_exit(); bool test_furi_ac_switch_exit();
bool test_furi_pipe_record();
bool test_furi_holding_data();
bool test_furi_concurrent_access();
bool test_furi_nonexistent_data(); bool test_furi_nonexistent_data();
bool test_furi_mute_algorithm(); bool test_furi_mute_algorithm();
// v2 tests
void test_furi_create_open();
void test_furi_valuemutex();
void test_furi_concurrent_access();
static int foo = 0; static int foo = 0;
void test_setup(void) { void test_setup(void) {
@ -34,27 +37,22 @@ MU_TEST(mu_test_furi_ac_switch_exit) {
mu_assert_int_eq(test_furi_ac_switch_exit(), true); mu_assert_int_eq(test_furi_ac_switch_exit(), true);
} }
MU_TEST(mu_test_furi_pipe_record) {
mu_assert_int_eq(test_furi_pipe_record(), true);
}
MU_TEST(mu_test_furi_holding_data) {
mu_assert_int_eq(test_furi_holding_data(), true);
}
MU_TEST(mu_test_furi_concurrent_access) {
mu_assert_int_eq(test_furi_concurrent_access(), true);
}
MU_TEST(mu_test_furi_nonexistent_data) { MU_TEST(mu_test_furi_nonexistent_data) {
mu_assert_int_eq(test_furi_nonexistent_data(), true); mu_assert_int_eq(test_furi_nonexistent_data(), true);
} }
/* // v2 tests
MU_TEST(mu_test_furi_mute_algorithm) { MU_TEST(mu_test_furi_create_open) {
mu_assert_int_eq(test_furi_mute_algorithm(test_log), true); test_furi_create_open();
}
MU_TEST(mu_test_furi_valuemutex) {
test_furi_valuemutex();
}
MU_TEST(mu_test_furi_concurrent_access) {
test_furi_concurrent_access();
} }
*/
MU_TEST_SUITE(test_suite) { MU_TEST_SUITE(test_suite) {
MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
@ -62,11 +60,14 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(test_check); MU_RUN_TEST(test_check);
MU_RUN_TEST(mu_test_furi_ac_create_kill); MU_RUN_TEST(mu_test_furi_ac_create_kill);
MU_RUN_TEST(mu_test_furi_ac_switch_exit); MU_RUN_TEST(mu_test_furi_ac_switch_exit);
MU_RUN_TEST(mu_test_furi_pipe_record);
MU_RUN_TEST(mu_test_furi_holding_data);
MU_RUN_TEST(mu_test_furi_concurrent_access);
MU_RUN_TEST(mu_test_furi_nonexistent_data); MU_RUN_TEST(mu_test_furi_nonexistent_data);
// MU_RUN_TEST(mu_test_furi_mute_algorithm);
// v2 tests
MU_RUN_TEST(mu_test_furi_create_open);
MU_RUN_TEST(mu_test_furi_valuemutex);
MU_RUN_TEST(mu_test_furi_concurrent_access);
} }
int run_minunit() { int run_minunit() {

47
core/api-basic/flapp.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include "flipper.h"
// == Flipper Application control (flapp) ==
typedef FlappHandler uint32_t; // TODO
/*
simply starts application. It call `app` entrypoint with `param` passed as argument
Useful for daemon applications and pop-up.
*/
FlappHandler* flapp_start(void(app*)(void*), char* name, void* param);
/*
swtich to other application.
System **stop current app**, call `app` entrypoint with `param` passed
as argument and save current application entrypoint to `prev` field in
current application registry. Useful for UI or "active" application.
*/
FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param);
/*
Exit application
stop current application (stop thread and clear application's stack),
start application from `prev` entry in current application registry,
cleanup current application registry.
*/
void flapp_exit(void* param);
/*
stop specified `app` without returning to `prev` application.
*/
bool flapp_kill(FlappHandler* app);
/*
If case one app depend on other, notify that app is ready.
*/
void flapp_ready();
/*
Register on-exit callback.
It called before app will be killed.
Not recommended to use in user scenario, only for system purpose
(unregister callbacks, release mutexes, etc.)
*/
bool flapp_on_exit(void(cb*)(void*), void* ctx);

14
core/api-basic/furi.c Normal file
View File

@ -0,0 +1,14 @@
#include "furi.h"
#include "furi-deprecated.h"
bool furi_create(const char* name, void* ptr) {
return furi_create_deprecated(name, ptr, sizeof(size_t));
}
void* furi_open(const char* name) {
FuriRecordSubscriber* record = furi_open_deprecated(name, false, false, NULL, NULL, NULL);
void* res = furi_take(record);
furi_give(record);
return res;
}

26
core/api-basic/furi.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "flipper.h"
/*
== Flipper universal registry implementation (FURI) ==
## Requirements
* start daemon app
* kill app
* start child thread (kill when parent app was killed)
* switch between UI apps
*/
/*
Create record.
creates new record in registry and store pointer into it
*/
bool furi_create(const char* name, void* ptr);
/*
Open record.
get stored pointer by its name
*/
void* furi_open(const char* name);

View File

@ -0,0 +1,48 @@
#include "pubsub.h"
void init_pubsub(PubSub* pubsub) {
pubsub->count = 0;
for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) {
pubsub->items[i].
}
}
// TODO add mutex to reconfigurate PubSub
PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) {
if(pubsub->count >= NUM_OF_CALLBACKS) return NULL;
pubsub->count++;
PubSubItem* current = pubsub->items[pubsub->count];
current->cb = cb;
currrnt->ctx = ctx;
pubsub->ids[pubsub->count].self = pubsub;
pubsub->ids[pubsub->count].item = current;
flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count]));
return current;
}
void unsubscribe_pubsub(PubSubId* pubsub_id) {
// TODO: add, and rearrange all items to keep subscribers item continuous
// TODO: keep ids link actual
// TODO: also add mutex on every pubsub changes
// trivial implementation for NUM_OF_CALLBACKS = 1
if(NUM_OF_CALLBACKS != 1) return;
if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return;
pubsub_id->self->count = 0;
pubsub_id->item = NULL;
}
void notify_pubsub(PubSub* pubsub, void* arg) {
// iterate over subscribers
for(size_t i = 0; i < pubsub->count; i++) {
pubsub->items[i]->cb(arg, pubsub->items[i]->ctx);
}
}

83
core/api-basic/pubsub.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include "flipper.h"
/*
== PubSub ==
PubSub allows users to subscribe on notifies and notify subscribers.
Notifier side can pass `void*` arg to subscriber callback,
and also subscriber can set `void*` context pointer that pass into
callback (you can see callback signature below).
*/
typedef void(PubSubCallback*)(void*, void*);
typedef struct {
PubSubCallback cb;
void* ctx;
} PubSubItem;
typedef struct {
PubSub* self;
PubSubItem* item;
} PubSubId;
typedef struct {
PubSubItem items[NUM_OF_CALLBACKS];
PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
size_t count; ///< count of callbacks
} PubSub;
/*
To create PubSub you should create PubSub instance and call `init_pubsub`.
*/
void init_pubsub(PubSub* pubsub);
/*
Use `subscribe_pubsub` to register your callback.
*/
PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
/*
Use `unsubscribe_pubsub` to unregister callback.
*/
void unsubscribe_pubsub(PubSubId* pubsub_id);
/*
Use `notify_pubsub` to notify subscribers.
*/
void notify_pubsub(PubSub* pubsub, void* arg);
/*
```C
// MANIFEST
// name="test"
// stack=128
void example_pubsub_handler(void* arg, void* ctx) {
printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
}
void pubsub_test() {
const char* app_name = "test app";
PubSub example_pubsub;
init_pubsub(&example_pubsub);
if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
printf("critical error\n");
flapp_exit(NULL);
}
uint32_t counter = 0;
while(1) {
notify_pubsub(&example_pubsub, (void*)&counter);
counter++;
osDelay(100);
}
}
```
*/

View File

@ -0,0 +1,24 @@
#include "value-expanders.h"
bool commit_managed(ValueManager* managed, void* value) {
if(value != managed->mutex->value) return false;
notify_pubsub(&managed->pubsub, value);
if(!osMutexGive(managed->mutex)) return false;
return true;
}
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(managed->mutex, timeout);
if(value == NULL) return false;
memcpy(value, data, len):
notify_pubsub(&managed->pubsub, value);
if(!release_mutex(managed->mutex, value)) return false;
return true;
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "flipper.h"
/*
== Value composer ==
*/
typedef void(ValueComposerCallback)(void* ctx, void* state);
void COPY_COMPOSE(void* ctx, void* state) {
read_mutex((ValueMutex*)ctx, state, 0);
}
typedef enum {
UiLayerBelowNotify
UiLayerNotify,
UiLayerAboveNotify
} UiLayer;
ValueComposerHandle* add_compose_layer(
ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer
);
bool remove_compose_layer(ValueComposerHandle* handle);
void request_compose(ValueComposerHandle* handle);
// See [LED](LED-API) or [Display](Display-API) API for examples.
/*
== ValueManager ==
More complicated concept is ValueManager.
It is like ValueMutex, but user can subscribe to value updates.
First of all you can use value and pubsub part as showing above:
aquire/release mutex, read value, subscribe/unsubscribe pubsub.
There are two specific methods for ValueManager: write_managed, commit_managed
*/
typedef struct {
ValueMutex value;
PubSub pubsub;
} ValueManager;
/*
acquire value, changes it and send notify with current value.
*/
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout);
/*
commit_managed works as `release_mutex` but send notify with current value.
*/
bool commit_managed(ValueManager* managed, void* value);

View File

@ -0,0 +1,52 @@
#include "valuemutex.h"
#include <string.h>
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
// mutex without name,
// no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
// with dynamic memory allocation
const osMutexAttr_t value_mutext_attr = {
.name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
valuemutex->mutex = osMutexNew(&value_mutext_attr);
if(valuemutex->mutex == NULL) return false;
valuemutex->value = value;
valuemutex->size = size;
return true;
}
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) {
return valuemutex->value;
} else {
return NULL;
}
}
bool release_mutex(ValueMutex* valuemutex, void* value) {
if(value != valuemutex->value) return false;
if(osMutexRelease(valuemutex->mutex) != osOK) return false;
return true;
}
bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(data, value, len > 0 ? len : valuemutex->size);
if(!release_mutex(valuemutex, value)) return false;
return true;
}
bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(value, data, len > 0 ? len : valuemutex->size);
if(!release_mutex(valuemutex, value)) return false;
return true;
}

123
core/api-basic/valuemutex.h Normal file
View File

@ -0,0 +1,123 @@
#pragma once
#include "flipper.h"
/*
== ValueMutex ==
The most simple concept is ValueMutex.
It is wrapper around mutex and value pointer.
You can take and give mutex to work with value and read and write value.
*/
typedef struct {
void* value;
size_t size;
osMutexId_t mutex;
} ValueMutex;
/*
Creates ValueMutex.
*/
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size);
/*
Call for work with data stored in mutex.
Returns pointer to data if success, NULL otherwise.
*/
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout);
/*
Helper: infinitly wait for mutex
*/
static inline void* acquire_mutex_block(ValueMutex* valuemutex) {
return acquire_mutex(valuemutex, osWaitForever);
}
/*
Release mutex after end of work with data.
Call `release_mutex` and pass ValueData instance and pointer to data.
*/
bool release_mutex(ValueMutex* valuemutex, void* value);
/*
Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions.
Both functions return true in case of success, false otherwise.
*/
bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return write_mutex(valuemutex, data, len, osWaitForever);
}
inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return read_mutex(valuemutex, data, len, osWaitForever);
}
/*
Usage example
```C
// MANIFEST
// name="example-provider-app"
// stack=128
void provider_app(void* _p) {
// create record with mutex
uint32_t example_value = 0;
ValueMutex example_mutex;
// call `init_mutex`.
if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) {
printf("critical error\n");
flapp_exit(NULL);
}
if(furi_create("provider/example", (void*)&example_mutex)) {
printf("critical error\n");
flapp_exit(NULL);
}
// we are ready to provide record to other apps
flapp_ready();
// get value and increment it
while(1) {
uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever);
if(value != NULL) {
value++;
}
release_mutex(&example_mutex, value);
osDelay(100);
}
}
// MANIFEST
// name="example-consumer-app"
// stack=128
// require="example-provider-app"
void consumer_app(void* _p) {
// this app run after flapp_ready call in all requirements app
// open mutex value
ValueMutex* counter_mutex = furi_open("provider/example");
if(counter_mutex == NULL) {
printf("critical error\n");
flapp_exit(NULL);
}
// continously read value every 1s
uint32_t counter;
while(1) {
if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) {
printf("counter value: %d\n", counter);
}
osDelay(1000);
}
}
```
*/

133
core/api-hal/api-spi.h Normal file
View File

@ -0,0 +1,133 @@
#include "flipper_v2.h"
/*
struct used for handling SPI info.
*/
typedef struct {
SPI_HandleTypeDef* spi;
PubSubCallback cb;
void* ctx;
} SpiHandle;
/*
For transmit/receive data use `spi_xfer` function.
* `tx_data` and `rx_data` size must be equal (and equal `len`)
* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback.
*/
bool spi_xfer(
SPI_HandleTypeDef* spi,
uint8_t* tx_data, uint8_t* rx_data, size_t len,
PubSubCallback cb, void* ctx);
/*
Blocking verison:
*/
static inline bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) {
semaphoreInfo s;
osSemaphore block = createSemaphoreStatic(s);
if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) {
osReleaseSemaphore(block);
return false;
}
osWaitSemaphore(block);
return false;
}
/*
Common implementation of SPI bus: serial interface + CS pin
*/
typedef struct {
GpioPin* cs; ///< CS pin
ValueMutex* spi; ///< <SpiHandle*>
} SpiBus;
/*
For dedicated work with one device there is `SpiDevice` entity.
It contains ValueMutex around SpiBus: after you acquire device
you can acquire spi to work with it (don't forget SPI bus is shared
around many device, release it after every transaction as quick as possible).
*/
typedef struct {
ValueMutex* bus; ///< <SpiBus*>
} SpiDevice;
## SPI IRQ device
/*
Many devices (like CC1101 and NFC) present as SPI bus and IRQ line.
For work with it there is special entity `SpiIrqDevice`.
Use `subscribe_pubsub` for subscribinq to irq events.
*/
typedef struct {
ValueMutex* bus; ///< <SpiBus*>
PubSub* irq;
} SpiIrqDevice;
/*
Special implementation of SPI bus: serial interface + CS, Res, D/I lines.
*/
typedef struct {
GpioPin* cs; ///< CS pin
GpioPin* res; ///< reset pin
GpioPin* di; ///< D/I pin
ValueMutex* spi; ///< <SPI_HandleTypeDef*>
} DisplayBus;
typedef struct {
ValueMutex* bus; ///< <DisplayBus*>
} DisplayDevice;
/*
# SPI devices (F2)
* `/dev/sdcard` - SD card SPI, `SpiDevice`
* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice`
* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice`
* `/dev/display` - `DisplayDevice`
* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7)
### Application example
```C
// Be careful, this function called from IRQ context
void handle_irq(void* _arg, void* _ctx) {
}
void cc1101_example() {
SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus");
if(cc1101_device == NULL) return; // bus not available, critical error
subscribe_pubsub(cc1101_device->irq, handle_irq, NULL);
{
// acquire device as device bus
SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0);
if(spi_bus == NULL) {
printf("Device busy\n");
// wait for device
spi_bus = acquire_mutex_block(cc1101_device->bus);
}
// make transaction
uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF};
uint8_t response[4];
{
SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi);
gpio_write(spi_bus->cs, false);
spi_xfer_block(spi, request, response, 4);
gpio_write(spi_bus->cs, true);
release_mutex(cc1101_device->spi, spi);
}
// release device (device bus)
release_mutex(cc1101_device->bus, spi_bus);
}
}
```
*/

View File

@ -2,7 +2,6 @@
extern "C" { extern "C" {
#include "flipper.h" #include "flipper.h"
#include "furi.h"
#include "log.h" #include "log.h"
#include "startup.h" #include "startup.h"
#include "tty_uart.h" #include "tty_uart.h"

View File

@ -3,4 +3,5 @@ CORE_DIR = $(PROJECT_ROOT)/core
CFLAGS += -I$(CORE_DIR) CFLAGS += -I$(CORE_DIR)
ASM_SOURCES += $(wildcard $(CORE_DIR)/*.s) ASM_SOURCES += $(wildcard $(CORE_DIR)/*.s)
C_SOURCES += $(wildcard $(CORE_DIR)/*.c) C_SOURCES += $(wildcard $(CORE_DIR)/*.c)
C_SOURCES += $(wildcard $(CORE_DIR)/api-basic/*.c)
CPP_SOURCES += $(wildcard $(CORE_DIR)/*.cpp) CPP_SOURCES += $(wildcard $(CORE_DIR)/*.cpp)

View File

@ -7,7 +7,8 @@ extern "C" {
#include "main.h" #include "main.h"
#include "flipper_hal.h" #include "flipper_hal.h"
#include "cmsis_os.h" #include "cmsis_os.h"
#include "furi.h" #include "furi-deprecated.h"
#include "log.h" #include "log.h"
#include "input/input.h" #include "input/input.h"

7
core/flipper_v2.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "api-basic/furi.h"
//#include "api-basic/flapp.h"
#include "cmsis_os2.h"
#include "api-basic/valuemutex.h"
//#include "api-basic/pubsub.h"

View File

@ -1,5 +1,4 @@
#include "furi.h" #include "furi-deprecated.h"
#include "cmsis_os.h"
#include <string.h> #include <string.h>
// TODO: this file contains printf, that not implemented on uC target // TODO: this file contains printf, that not implemented on uC target
@ -28,7 +27,7 @@ static FuriRecord* find_record(const char* name) {
} }
// TODO: change open-create to only open // TODO: change open-create to only open
bool furi_create(const char* name, void* value, size_t size) { bool furi_create_deprecated(const char* name, void* value, size_t size) {
#ifdef FURI_DEBUG #ifdef FURI_DEBUG
printf("[FURI] creating %s record\n", name); printf("[FURI] creating %s record\n", name);
#endif #endif
@ -73,7 +72,7 @@ bool furi_create(const char* name, void* value, size_t size) {
return true; return true;
} }
FuriRecordSubscriber* furi_open( FuriRecordSubscriber* furi_open_deprecated(
const char* name, const char* name,
bool solo, bool solo,
bool no_mute, bool no_mute,
@ -94,7 +93,7 @@ FuriRecordSubscriber* furi_open(
#endif #endif
// create record if not exist // create record if not exist
if(!furi_create(name, NULL, 0)) { if(!furi_create_deprecated(name, NULL, 0)) {
return NULL; return NULL;
} }

View File

@ -127,7 +127,7 @@ If NULL, create FURI Pipe (only callbacks management, no data/mutex)
Returns false if registry have not enough memory for creating. Returns false if registry have not enough memory for creating.
*/ */
bool furi_create(const char* name, void* value, size_t size); bool furi_create_deprecated(const char* name, void* value, size_t size);
/*! /*!
Opens existing FURI record by name. Opens existing FURI record by name.
@ -137,7 +137,7 @@ When appication has exited or record has closed, all handlers is unmuted.
It may be useful for concurrently acces to resources like framebuffer or beeper. It may be useful for concurrently acces to resources like framebuffer or beeper.
\param[in] no_mute if true, another applications cannot mute this handler. \param[in] no_mute if true, another applications cannot mute this handler.
*/ */
FuriRecordSubscriber* furi_open( FuriRecordSubscriber* furi_open_deprecated(
const char* name, const char* name,
bool solo, bool solo,
bool no_mute, bool no_mute,

View File

@ -1,5 +1,4 @@
#include "furi.h" #include "flipper.h"
#include "cmsis_os.h"
// TODO: this file contains printf, that not implemented on uC target // TODO: this file contains printf, that not implemented on uC target

View File

@ -5,7 +5,7 @@
#include <string.h> #include <string.h>
#include "log.h" #include "log.h"
#include "furi.h" #include "flipper.h"
#define PRINT_STR_SIZE 64 #define PRINT_STR_SIZE 64
@ -21,5 +21,5 @@ void fuprintf(FuriRecordSubscriber* f, const char* format, ...) {
} }
FuriRecordSubscriber* get_default_log() { FuriRecordSubscriber* get_default_log() {
return furi_open("tty", false, false, NULL, NULL, NULL); return furi_open_deprecated("tty", false, false, NULL, NULL, NULL);
} }

View File

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

View File

@ -1,6 +1,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include "furi.h" #include "flipper.h"
#include "main.h" #include "main.h"
extern UART_HandleTypeDef DEBUG_UART; extern UART_HandleTypeDef DEBUG_UART;
@ -12,7 +12,7 @@ void handle_uart_write(const void* data, size_t size, void* ctx) {
static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) {
FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0); FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0);
if(log == NULL) { if(log == NULL) {
log = furi_open("tty", false, false, NULL, NULL, NULL); log = furi_open_deprecated("tty", false, false, NULL, NULL, NULL);
if(log == NULL) { if(log == NULL) {
return -1; return -1;
} }
@ -33,11 +33,11 @@ static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) {
} }
bool register_tty_uart() { bool register_tty_uart() {
if(!furi_create("tty", NULL, 0)) { if(!furi_create_deprecated("tty", NULL, 0)) {
return false; return false;
} }
if(furi_open("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { if(furi_open_deprecated("tty", false, false, handle_uart_write, NULL, NULL) == NULL) {
return false; return false;
} }

View File

@ -68,3 +68,31 @@ void* pvTaskGetThreadLocalStoragePointer(TaskHandle_t xTaskToQuery, BaseType_t x
void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue); void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue);
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize); QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
typedef struct {
const char *name; ///< name of the mutex
uint32_t attr_bits; ///< attribute bits
void *cb_mem; ///< memory for control block
uint32_t cb_size; ///< size of provided memory for control block
} osMutexAttr_t;
typedef SemaphoreHandle_t osMutexId_t;
osMutexId_t osMutexNew(const osMutexAttr_t *attr);
/// Status code values returned by CMSIS-RTOS functions.
typedef enum {
osOK = 0, ///< Operation completed successfully.
osError = -1, ///< Unspecified RTOS error: run-time error but no other error message fits.
osErrorTimeout = -2, ///< Operation not completed within the timeout period.
osErrorResource = -3, ///< Resource not available.
osErrorParameter = -4, ///< Parameter error.
osErrorNoMemory = -5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
osErrorISR = -6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
osStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
} osStatus_t;
osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout);
osStatus_t osMutexRelease (osMutexId_t mutex_id);
#define osWaitForever portMAX_DELAY

View File

@ -0,0 +1 @@
#include "cmsis_os.h"

View File

@ -230,3 +230,26 @@ void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xInde
pthread_setspecific(tls_keys[xIndex], pvValue); pthread_setspecific(tls_keys[xIndex], pvValue);
} }
osMutexId_t osMutexNew(const osMutexAttr_t *attr) {
StaticSemaphore_t* pxMutexBuffer = malloc(sizeof(StaticSemaphore_t));
xSemaphoreCreateMutexStatic(pxMutexBuffer);
return (osMutexId_t)pxMutexBuffer;
}
osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) {
if(xSemaphoreTake((SemaphoreHandle_t)mutex_id, (TickType_t)timeout) == pdTRUE) {
return osOK;
} else {
return osErrorTimeout;
}
}
osStatus_t osMutexRelease (osMutexId_t mutex_id) {
if(xSemaphoreGive((SemaphoreHandle_t)mutex_id) == pdTRUE) {
return osOK;
} else {
return osError;
}
}

View File

@ -2,6 +2,10 @@ LIB_DIR = $(PROJECT_ROOT)/lib
CFLAGS += -I$(LIB_DIR) CFLAGS += -I$(LIB_DIR)
# Mlib containers
CFLAGS += -I$(LIB_DIR)/mlib
# U8G2 display library
U8G2_DIR = $(LIB_DIR)/u8g2 U8G2_DIR = $(LIB_DIR)/u8g2
CFLAGS += -I$(U8G2_DIR) CFLAGS += -I$(U8G2_DIR)
C_SOURCES += $(U8G2_DIR)/u8x8_d_st7565.c C_SOURCES += $(U8G2_DIR)/u8x8_d_st7565.c

1
lib/mlib Submodule

@ -0,0 +1 @@
Subproject commit eb7556f88faf0bbfd6a4ae99a3d53dcbe2064b88

View File

@ -1,13 +1,27 @@
# Basic concepts: # [Basic concepts](Basic-API)
* ValueMutex * ValueMutex
* PubSub, Publisher, Subscriber * PubSub
* ValueManager * ValueManager
* LayeredReducer * ValueComposer
# HAL # [HAL and devices](HAL-API)
We use [Zephyr HAL](https://docs.zephyrproject.org/latest/reference/peripherals/index.html). * GPIO
* PWM
* ADC
* I2C
* IR RX (unimplemented)
* Comparator RX (touch key and RFID 125 kHz RX) (unimplemented)
# [SPI Devices](SPI-Devices-API.md)
* Sub-GHz chip
* NFC
* SD card
* display
* external SPI
# OS # OS
@ -15,68 +29,20 @@ We use [CMSIS OS v2](https://www.keil.com/pack/doc/CMSIS_Dev/RTOS2/html/group__C
# UI # UI
* **[Input](Input-API)**
* **[Input](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Input)** * **[Display](Display-API)**
* **[Display](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Display)** * **[LED](LED-API)**
* **[LED](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:LED)** * **[Backlight](Backlight-API)** (unimplemented)
* **vibro** # [Power](Power-API)
* **[Sound](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Sound)** * batt voltage
* batt charge
* **backlight** # [UART](Serial-API)
# System
## batt voltage
## batt charge
# CC1101
## SPI
## IRQ
# SD Card
## SPI
# NFC
## SPI
## IRQ
# IR
## TX LED
## RX ADC
# RFID 125 kHz
## Carrier
## Pull
## Comparator RX (shared with touch key)
# Touch key
## Pull
## Comparator RX (shared with RFID 125 kHz)
# External GPIO
# External SPI
# External I2C
# UART
# USB # USB

View File

@ -6,27 +6,7 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f
# Application registry and control (FURIAC) # Application registry and control (FURIAC)
### Start and change application wrokflow
**`FuriApp* furiac_start(void(app*)(void*), char* name, void* param)`**
simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
**`FuriApp furiac_switch(void(app*)(void*), char* name, void* param)`**
swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
### Exit application
**`void furiac_exit(void* param)`**
stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
**`bool furiac_kill(FuriApp app)`**
stop specified `app` without returning to `prev` application.
# Data exchange # Data exchange

View File

@ -1,61 +0,0 @@
All display operations based on [u8g2](https://github.com/olikraus/u8g2) library.
API available as struct, contains u8g2 functions, instance and fonts:
```C
typedef struct {
ValueManager* display; /// ValueManager<u8g2_t*>
void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t *font);
void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color);
void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent);
u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);
Fonts fonts;
} Display;
typedef struct {
const uint8_t* u8g2_font_6x10_mf;
} Fonts;
```
First of all you can open display API instance by calling `open_display`
```C
/// Get display instance and API
inline Display* open_display(const char* name) {
return (Display*)furi_open(name);
}
```
Default display name is `/dev/display`.
For draw something to display you can get display instance pointer by calling `take_display`, do something and commit your changes by calling `commit_display`:
```C
/// return pointer in case off success, NULL otherwise
inline u8g2_t* take_display(Display* api, uint32_t timeout) {
return (u8g2_t*)take_mutex(api->display->value, timeout);
}
inline void commit_display(Display* api, u8g2_t* display) {
commit_valuemanager(api->display, display);
}
```
## Usage example
```C
void u8g2_example(void* p) {
Display* display_api = open_display("/dev/display");
if(display_api == NULL) return; // display not available, critical error
u8g2_t* display = take_display(display_api);
if(display != NULL) {
display_api->u8g2_SetFont(display, display_api->fonts.u8g2_font_6x10_mf);
display_api->u8g2_SetDrawColor(display, 1);
display_api->u8g2_SetFontMode(display, 1);
display_api->u8g2_DrawStr(display, 2, 12, "hello world!");
}
commit_display(display_api, display);
}
```

View File

@ -1,124 +0,0 @@
LED state describes by struct:
```C
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} Rgb;
```
LED API provided by struct:
```C
typedef struct {
LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Rgb*>
Subscriber* updates; /// LED value changes Supscriber<Rgb*>
ValueMutex* state; /// LED state, ValueMutex<Rgb*>
} LedApi;
```
You can get API instance by calling `open_led`:
```C
/// Add new layer to LED:
inline LedApi* open_led(const char* name) {
return (LedApi*)furi_open(name);
}
```
Default system led is `/dev/led`.
Then add new layer to control LED by calling `add_led_layer`:
```C
inline ValueManager* add_led_layer(Rgb* layer, uint8_t priority) {
ValueManager* manager = register_valuemanager((void*)layer);
if(manager == NULL) return NULL;
if(!add_layered_reducer(manager, priority, layer_compose_default)) {
unregister_valuemanager(manager);
return NULL;
}
return manager;
}
```
For change led you can get display instance pointer by calling `take_led`, do something and commit your changes by calling `commit_led`. Or you can call `write_led`:
```C
/// return pointer in case off success, NULL otherwise
inline Rgb* take_led(ValueManager* led, uint32_t timeout) {
return (Rgb*)take_mutex(led->value, timeout);
}
inline void commit_led(ValueManager* led, Rgb* value) {
commit_valuemanager(led, value);
}
/// return true if success, false otherwise
inline bool write_led(ValueManager* led, Rgb* value, uint32_t timeout) {
return write_valuemanager(state, (void*)value, sizeof(Rgb), timeout);
}
```
To read current led state you should use `read_led` function:
```C
/// return true if success, false otherwise
inline bool read_led(ValueManager* led, Rgb* value, uint32_t timeout) {
return read_mutex(led->value, (void*)value, sizeof(Rgb), timeout);
}
```
Also you can subscribe to led state changes:
Use `subscribe_led_changes` to register your callback:
```C
/// return true if success, false otherwise
inline bool subscribe_led_changes(Subscriber* updates, void(*cb)(Rgb*, void*), void* ctx) {
return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx);
}
```
## Usage example
```C
void handle_led_state(Rgb* rgb, void* _ctx) {
printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue);
}
void led_example(void* p) {
LedApi* led_api = open_display("/dev/led");
if(led_api == NULL) return; // led not available, critical error
// subscribe to led state updates
subscribe_led_changes(led_api->updates, handle_led_state, NULL);
Rgb current_state;
if(read_led(led_api->state, &current_state, OsWaitForever)) {
printf(
"initial led: #%02X%02X%02X\n",
current_state->red,
current_state->green,
current_state->blue
);
}
// add layer to control led
ValueManager* led_manager = add_led_layer(&current_state, UI_LAYER_APP);
// write only blue by getting pointer
Rgb* rgb = take_led(led_manager, OsWaitForever);
if(rgb != NULL) {
rgb->blue = 0;
}
commit_led(led_manager, rgb);
// write RGB value
write_led(led_manager, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}), OsWaitForever);
}
```

View File

@ -1,122 +0,0 @@
sound state describes by struct:
```C
typedef struct {
float freq; /// frequency in Hz
float width; /// pulse witdh 0...1
} Tone;
```
sound API provided by struct:
```C
typedef struct {
LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Tone*>
Subscriber* updates; /// sound value changes Supscriber<Tone*>
ValueMutex* state; /// sound state, ValueMutex<Tone*>
} SoundApi;
```
You can get API instance by calling `open_sound`:
```C
/// Add new layer to sound:
inline SoundApi* open_sound(const char* name) {
return (SoundApi*)furi_open(name);
}
```
Default system sound is `/dev/sound`.
Then add new layer to control sound by calling `add_sound_layer`:
```C
inline ValueManager* add_sound_layer(Tone* layer, uint8_t priority) {
ValueManager* manager = register_valuemanager((void*)layer);
if(manager == NULL) return NULL;
if(!add_layered_reducer(manager, priority, layer_compose_default)) {
unregister_valuemanager(manager);
return NULL;
}
return manager;
}
```
For change sound you can get display instance pointer by calling `take_sound`, do something and commit your changes by calling `commit_sound`. Or you can call `write_sound`:
```C
/// return pointer in case off success, NULL otherwise
inline Tone* take_sound(ValueManager* sound, uint32_t timeout) {
return (Tone*)take_mutex(sound->value, timeout);
}
inline void commit_sound(ValueManager* sound, Tone* value) {
commit_valuemanager(sound, value);
}
/// return true if success, false otherwise
inline bool write_sound(ValueManager* sound, Tone* value, uint32_t timeout) {
return write_valuemanager(state, (void*)value, sizeof(Tone), timeout);
}
```
To read current sound state you should use `read_sound` function:
```C
/// return true if success, false otherwise
inline bool read_sound(ValueManager* sound, Tone* value, uint32_t timeout) {
return read_mutex(sound->value, (void*)value, sizeof(Tone), timeout);
}
```
Also you can subscribe to sound state changes:
Use `subscribe_sound_changes` to register your callback:
```C
/// return true if success, false otherwise
inline bool subscribe_sound_changes(Subscriber* updates, void(*cb)(Tone*, void*), void* ctx) {
return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx);
}
```
## Usage example
```C
void handle_sound_state(Tone* tone, void* _ctx) {
printf("sound: %d Hz, %d %%\n", (uint16_t)tone->freq, (uint8_t)(tone->witdh * 100));
}
void sound_example(void* p) {
soundApi* sound_api = open_display("/dev/sound");
if(sound_api == NULL) return; // sound not available, critical error
// subscribe to sound state updates
subscribe_sound_changes(sound_api->updates, handle_sound_state, NULL);
Tone current_state;
if(read_sound(sound_api->state, &current_state, OsWaitForever)) {
printf(
"sound: %d Hz, %d %%\n",
(uint16_t)current_state->freq,
(uint8_t)(current_state->witdh * 100)
);
}
// add layer to control sound
ValueManager* sound_manager = add_sound_layer(&current_state, UI_LAYER_APP);
// write only freq by getting pointer
Tone* tone = take_sound(sound_manager, OsWaitForever);
if(tone != NULL) {
tone->freq = 440;
}
commit_sound(sound_manager, tone);
// write tone value
write_sound(sound_manager, &(Tone{.freq = 110., witdh = 0.5}), OsWaitForever);
}
```

View File

@ -0,0 +1,100 @@
Backlight state describes by `uint8_t level;` brightness level.
LED API provided by struct:
```C
typedef struct {
ValueComposer* composer; /// every app add its value to compose, <uint8_t*>
ValueManager* state; /// value state and changes <uint8_t*>
} BacklightApi;
```
You can get API instance by calling `open_backlight`:
```C
/// Add new layer to LED:
inline BacklightApi* open_backlight(const char* name) {
return (BacklightApi*)furi_open(name);
}
```
Default system led is `/dev/backlight`.
To read current backlight state you should use `read_backlight` function:
```C
/// return true if success, false otherwise
inline bool read_backlight(BacklightApi* api, uint8_t* value, uint32_t timeout) {
return read_mutex(api->state->value, (void*)value, sizeof(uint8_t), timeout);
}
```
Also you can subscribe to backlight state changes:
Use `subscribe_backlight_changes` to register your callback:
```C
/// return true if success, false otherwise
inline bool subscribe_backlight_changes(LedApi* led, void(*cb)(uint8_t*, void*), void* ctx) {
return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx);
}
```
Userspace helpers
```C
typedef struct {
uint8_t value;
ValueMutex value_mutex;
ValueComposerHandle* composer_handle;
} Backlight;
inline bool init_backlight_composer(Backlight* backlight, BacklightApi* api, uint32_t layer) {
if(!init_mutex(&backlight->value_mutex, (void*)&backlight->value, sizeof(uint8_t))) {
return false;
}
backlight->composer_handle = add_compose_layer(
api->composer, COPY_COMPOSE, &backlight->value_mutex, layer
); // just copy backlight state on update
return backlight->composer_handle != NULL;
}
inline void write_backlight(Backlight* backlight, uint8_t value) {
write_mutex(&backlight->value_mutex, (void*)&value, sizeof(uint8_t), OsWaitForever);
request_compose(backlight->composer_handle);
}
```
## Usage example
```C
void handle_backlight_state(uint8_t* value, void* _ctx) {
printf("backlight: %d %%\n", (*value * 100) / 256);
}
void backlight_example(void* p) {
BacklightApi* backlight_api = open_backlight("/dev/backlight");
if(backlight_api == NULL) return; // backlight not available, critical error
// subscribe to led state updates
subscribe_backlight_changes(backlight_api, handle_backlight_state, NULL);
// get current backlight value
uint8_t backlight_value;
if(read_backlight(backlight_api, &backlight_value, OsWaitForever)) {
printf(
"initial backlight: %d %%\n",
backlight_value * 100 / 256
);
}
// create compose to control led
Backlight backlight;
if(!init_led_composer(&backlight, backlight_api, UiLayerBelowNotify)) return;
// write RGB value
write_backlight(&backlight, 127);
}
```

402
wiki/fw/api/Basic-API.md Normal file
View File

@ -0,0 +1,402 @@
# Flipper universal registry implementation (FURI)
Create record.
```C
// creates new record in registry and store pointer into it
bool furi_create(const char* name, void* ptr);
```
Open record.
```C
// get stored pointer by its name
void* furi_open(const char* name);
```
# Flipper Application control (flapp)
## (in progress. Old verison)
**`FlappHandler* flapp_start(void(app*)(void*), char* name, void* param)`**
simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
**`FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param)`**
swtich to other application. System **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
### Exit application
**`void flapp_exit(void* param)`**
stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
**`bool flapp_kill(FlappHandler* app)`**
stop specified `app` without returning to `prev` application.
**`void flapp_ready()`**
If case one app depend on other, notify that app is ready.
## Requirements
* start daemon app
* kill app
* start child thread (kill when parent app was killed)
* switch between UI apps
**`bool flapp_on_exit(void(cb*)(void*), void* ctx);`**
Register on-exit callback. It called before app will be killed. Not recommended to use in user scenario, only for system purpose (unregister callbacks, release mutexes, etc.)
# ValueMutex
The most simple concept is ValueMutex. It is wrapper around mutex and value pointer. You can take and give mutex to work with value and read and write value.
```C
typedef struct {
void* value;
size_t size;
osMutex mutex;
osMutexDescriptor __static // some internals;
} ValueMutex;
```
Create ValueMutex. Create instance of ValueMutex and call `init_mutex`.
```C
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
valuemutex->mutex = osMutexCreateStatic(valuemutex->__static);
if(valuemutex->mutex == NULL) return false;
valuemutex->value = value;
valuemutex->size = size;
return true;
}
```
For work with data stored in mutex you should call `acquire_mutex`. It return pointer to data if success, NULL otherwise.
You must release mutex after end of work with data. Call `release_mutex` and pass ValueData instance and pointer to data.
```C
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
if(osMutexTake(valuemutex->mutex, timeout) == osOk) {
return valuemutex->value;
} else {
return NULL;
}
}
// infinitly wait for mutex
inline static void* acquire_mutex_block(ValueMutex* valuemutex) {
return acquire_mutex(valuemutex, OsWaitForever);
}
bool release_mutex(ValueMutex* valuemutex, void* value) {
if(value != valuemutex->value) return false;
if(!osMutexGive(valuemutex->mutex)) return false;
return true;
}
```
Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. Both functions return true in case of success, false otherwise.
```C
bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(data, value, len > 0 ? len : valuemutex->size):
if(!release_mutex(valuemutex, value)) return false;
return true;
}
inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return read_mutex(valuemutex, data, len, OsWaitForever);
}
bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(value, data, len > 0 ? len : valuemutex->size):
if(!release_mutex(valuemutex, value)) return false;
return true;
}
inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return write_mutex(valuemutex, data, len, OsWaitForever);
}
```
## Usage example
```C
/*
MANIFEST
name="example-provider-app"
stack=128
*/
void provider_app(void* _p) {
// create record with mutex
uint32_t example_value = 0;
ValueMutex example_mutex;
if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) {
printf("critical error\n");
flapp_exit(NULL);
}
if(furi_create("provider/example", (void*)&example_mutex)) {
printf("critical error\n");
flapp_exit(NULL);
}
// we are ready to provide record to other apps
flapp_ready();
// get value and increment it
while(1) {
uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever);
if(value != NULL) {
value++;
}
release_mutex(&example_mutex, value);
osDelay(100);
}
}
/*
MANIFEST
name="example-consumer-app"
stack=128
require="example-provider-app"
*/
void consumer_app(void* _p) {
// this app run after flapp_ready call in all requirements app
// open mutex value
ValueMutex* counter_mutex = furi_open("provider/example");
if(counter_mutex == NULL) {
printf("critical error\n");
flapp_exit(NULL);
}
// continously read value every 1s
uint32_t counter;
while(1) {
if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) {
printf("counter value: %d\n", counter);
}
osDelay(1000);
}
}
```
# PubSub
PubSub allows users to subscribe on notifies and notify subscribers. Notifier side can pass `void*` arg to subscriber callback, and also subscriber can set `void*` context pointer that pass into callback (you can see callback signature below).
```C
typedef void(PubSubCallback*)(void*, void*);
typedef struct {
PubSubCallback cb;
void* ctx;
} PubSubItem;
typedef struct {
PubSub* self;
PubSubItem* item;
} PubSubId;
typedef struct {
PubSubItem items[NUM_OF_CALLBACKS];
PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
size_t count; ///< count of callbacks
} PubSub;
```
To create PubSub you should create PubSub instance and call `init_pubsub`.
```C
void init_pubsub(PubSub* pubsub) {
pubsub->count = 0;
for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) {
pubsub->items[i].
}
}
```
Use `subscribe_pubsub` to register your callback.
```C
// TODO add mutex to reconfigurate PubSub
PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) {
if(pubsub->count >= NUM_OF_CALLBACKS) return NULL;
pubsub->count++;
PubSubItem* current = pubsub->items[pubsub->count];
current->cb = cb;
currrnt->ctx = ctx;
pubsub->ids[pubsub->count].self = pubsub;
pubsub->ids[pubsub->count].item = current;
flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count]));
return current;
}
```
Use `unsubscribe_pubsub` to unregister callback.
```C
void unsubscribe_pubsub(PubSubId* pubsub_id) {
// TODO: add, and rearrange all items to keep subscribers item continuous
// TODO: keep ids link actual
// TODO: also add mutex on every pubsub changes
// trivial implementation for NUM_OF_CALLBACKS = 1
if(NUM_OF_CALLBACKS != 1) return;
if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return;
pubsub_id->self->count = 0;
pubsub_id->item = NULL;
}
```
Use `notify_pubsub` to notify subscribers.
```C
void notify_pubsub(PubSub* pubsub, void* arg) {
// iterate over subscribers
for(size_t i = 0; i < pubsub->count; i++) {
pubsub->items[i]->cb(arg, pubsub->items[i]->ctx);
}
}
```
## Usage example
```C
/*
MANIFEST
name="test"
stack=128
*/
void example_pubsub_handler(void* arg, void* ctx) {
printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
}
void pubsub_test() {
const char* app_name = "test app";
PubSub example_pubsub;
init_pubsub(&example_pubsub);
if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
printf("critical error\n");
flapp_exit(NULL);
}
uint32_t counter = 0;
while(1) {
notify_pubsub(&example_pubsub, (void*)&counter);
counter++;
osDelay(100);
}
}
```
# ValueComposer
```C
typedef void(ValueComposerCallback)(void* ctx, void* state);
void COPY_COMPOSE(void* ctx, void* state) {
read_mutex((ValueMutex*)ctx, state, 0);
}
typedef enum {
UiLayerBelowNotify
UiLayerNotify,
UiLayerAboveNotify
} UiLayer;
```
```C
ValueComposerHandle* add_compose_layer(
ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer
);
```
```C
bool remove_compose_layer(ValueComposerHandle* handle);
```
```C
void request_compose(ValueComposerHandle* handle);
```
See [LED](LED-API) or [Display](Display-API) API for examples.
# ValueManager
More complicated concept is ValueManager. It is like ValueMutex, but user can subscribe to value updates.
```C
typedef struct {
ValueMutex value;
PubSub pubsub;
} ValueManager;
```
First of all you can use value and pubsub part as showing above: aquire/release mutex, read value, subscribe/unsubscribe pubsub. There are two specific methods for ValueManager:
`write_managed` acquire value, changes it and send notify with current value.
```C
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(managed->mutex, timeout);
if(value == NULL) return false;
memcpy(value, data, len):
notify_pubsub(&managed->pubsub, value);
if(!release_mutex(managed->mutex, value)) return false;
return true;
}
```
`commit_managed` works as `release_mutex` but send notify with current value.
```C
bool commit_managed(ValueManager* managed, void* value) {
if(value != managed->mutex->value) return false;
notify_pubsub(&managed->pubsub, value);
if(!osMutexGive(managed->mutex)) return false;
return true;
}
```

View File

@ -0,0 +1,68 @@
All display operations based on [u8g2](https://github.com/olikraus/u8g2) library.
API available as `ValueComposer`.
Driver call render callback and pass API contains u8g2 functions, instance and fonts:
```C
typedef struct {
u8g2_t* display;
void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t *font);
void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color);
void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent);
u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);
Fonts fonts;
} DisplayApi;
typedef struct {
const uint8_t* u8g2_font_6x10_mf;
} Fonts;
```
First of all you can open display API instance by calling `open_display`
```C
/// Get display instance and API
inline Display* open_display(const char* name) {
return (Display*)furi_open(name);
}
```
Default display name is `/dev/display`.
For draw something to display you need to register new layer in display composer:
```C
typedef void (RenderCallback*)(void* ctx, DisplayApi* api);
inline ValueComposerHandle* init_display_composer(
Display* api, RenderCallback render, void* ctx, uint32_t layer) {
return add_compose_layer(api->composer, (ValueComposerCallback)render, ctx, layer);
}
```
And then call `request_compose` every time you need to redraw your image.
## Usage example
```C
void example_render(void* ctx, DisplayApi* api) {
api->u8g2_SetFont(api->display, display_api->fonts.u8g2_font_6x10_mf);
api->u8g2_SetDrawColor(api->display, 1);
api->u8g2_SetFontMode(api->display, 1);
api->u8g2_DrawStr(api->display, 2, 12, (char*)ctx); // ctx contains some static text
}
void u8g2_example(void* p) {
Display* display_api = open_display("/dev/display");
if(display_api == NULL) return; // display not available, critical error
ValueComposerHandle display_handler = init_display_composer(
display_api, example_render, (void*)"Hello world", UiLayerBelowNotify);
request_compose(display_handler);
}
```

158
wiki/fw/api/HAL-API.md Normal file
View File

@ -0,0 +1,158 @@
# GPIO
GPIO defined as struct `GpioPin`.
GPIO functions:
```C
// Init GPIO
void gpio_init(GpioPin* gpio, GpioMode mode);
typedef enum { GpioModeInput, GpioModeOutput, GpioModeOpenDrain } GpioMode;
// write value to GPIO
void gpio_write(GpioPin* gpio, bool state);
// read value from GPIO, f = LOW, t = HIGH
bool gpio_read(GpioPin* gpio);
```
When application is exited, system place pin to Z-state by calling `gpio_disable`.
```C
// put GPIO to Z-state (used for restore pin state on app exit)
void gpio_disable(ValueMutex* gpio_mutex) {
GpioPin* gpio = acquire_mutex(gpio_mutex, 0);
gpio_init(gpio, GpioModeInput);
release_mutex(gpio_mutex, gpio);
}
```
Available GPIO stored in FURI as `ValueMutex<GpioPin*>`.
```C
inline static ValueMutex* open_gpio_mutex(const char* name) {
ValueMutex* gpio_mutex = (ValueMutex*)furi_open(name);
if(gpio_mutex != NULL) flapp_on_exit(gpio_disable, gpio_mutex);
return gpio_mutex;
}
// helper
inline static GpioPin* open_gpio(const char* name) {
ValueMutex* gpio_mutex = open_gpio(name);
return (GpioPin*)acquire_mutex(gpio_mutex, 0);
}
```
## Available GPIO (target F2)
* PA4
* PA5
* PA6
* PA7
* PB2
* PC3
* PC0
* PC1
* PB6
* PB7
* PA13
* PA14
* RFID_PULL
* IR_TX
* IBUTTON
* VIBRO
## Usage example
```C
void gpio_example() {
GpioPin* pin = open_gpio("PB6");
if(pin == NULL) {
printf("pin not available\n");
return;
}
gpio_init(pin, GpioModeOutput);
while(1) {
gpio_write(pin, true);
delay(100);
gpio_write(pin, false);
delay(100);
}
}
```
# PWM
PWM defined as `PwmPin`. To set PWM channel:
```C
void pwm_set(PwmPin* pwm, float value, float freq);
```
When application is exited, system disable pwm by calling `pwm_disable`.
```C
// put GPIO to Z-state (used for restore pin state on app exit)
void pwm_disable(ValueMutex* pwm_mutex) {
PwmPin* pwm = acquire_mutex(pwm_mutex, 0);
pwm_set(pwm, 0., 0.);
release_mutex(pwm_mutex, pwm);
}
```
Available PWM stored in FURI as `ValueMutex<PwmPin*>`.
```C
inline static ValueMutex* open_pwm_mutex(const char* name) {
ValueMutex* pwm_mutex = (ValueMutex*)furi_open(name);
if(pwm_mutex != NULL) flapp_on_exit(pwm_disable, pwm_mutex);
return pwm_mutex;
}
// helper
inline static PwmPin* open_pwm(const char* name) {
ValueMutex* pwm_mutex = open_gpio(name);
return (PwmPin*)acquire_mutex(pwm_mutex, 0);
}
```
## Available PWM (target F2)
* SPEAKER
* RFID_OUT
## Usage example
```C
void sound_example() {
PwmPin* speaker = open_pwm("SPEAKER");
if(speaker == NULL) {
printf("speaker not available\n");
return;
}
while(1) {
pwm_set(speaker, 1000., 0.1);
delay(2);
pwm_set(speaker, 110., 0.5);
delay(198);
pwm_set(speaker, 330., 0.5);
delay(200);
}
}
```
# ADC
Coming soon...
# I2C
Coming soon...

View File

@ -13,7 +13,7 @@ You can get API instance by calling `open_input`:
```C ```C
/// Get input struct /// Get input struct
inline Input* open_input(const char* name) { inline Input* open_input(const char* name) {
return furi_open(name); return (Input*)furi_open(name);
} }
``` ```
@ -37,8 +37,8 @@ To read buttons state you should use `read_state` function:
```C ```C
/// read current state of all buttons. Return true if success, false otherwise /// read current state of all buttons. Return true if success, false otherwise
inline bool read_state(ValueMutex* state, InputState* value, uint32_t timeout) { inline bool read_state(Input* api, InputState* value, uint32_t timeout) {
return read_mutex(state, (void*)value, sizeof(InputState), timeout); return read_mutex(api->state, (void*)value, sizeof(InputState), timeout);
} }
``` ```
@ -94,7 +94,7 @@ void input_example(void* p) {
// blocking way // blocking way
InputState state; InputState state;
while(1) { while(1) {
if(read_state(input->state, &state, OsWaitForever)) { if(read_state(input, &state, OsWaitForever)) {
if(state.up) { if(state.up) {
printf("up is pressed"); printf("up is pressed");
delay(1000); delay(1000);

110
wiki/fw/api/LED-API.md Normal file
View File

@ -0,0 +1,110 @@
LED state describes by struct:
```C
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} Rgb;
```
LED API provided by struct:
```C
typedef struct {
ValueComposer* composer; /// every app add its value to compose, <Rgb*>
ValueManager* state; /// LED value state and changes <Rgb*>
} LedApi;
```
You can get API instance by calling `open_led`:
```C
/// Add new layer to LED:
inline LedApi* open_led(const char* name) {
return (LedApi*)furi_open(name);
}
```
Default system led is `/dev/led`.
To read current led state you should use `read_led` function:
```C
/// return true if success, false otherwise
inline bool read_led(LedApi* led, Rgb* value, uint32_t timeout) {
return read_mutex(led->state->value, (void*)value, sizeof(Rgb), timeout);
}
```
Also you can subscribe to led state changes:
Use `subscribe_led_changes` to register your callback:
```C
/// return true if success, false otherwise
inline bool subscribe_led_changes(LedApi* led, void(*cb)(Rgb*, void*), void* ctx) {
return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx);
}
```
Userspace helpers
```C
typedef struct {
Rgb value;
ValueMutex value_mutex;
ValueComposerHandle* composer_handle;
} SystemLed;
inline bool init_led_composer(SystemLed* led, LedApi* api, uint32_t layer) {
if(!init_mutex(&led->value_mutex, (void*)&led->value, sizeof(Rgb))) {
return false;
}
led->composer_handle = add_compose_layer(
api->composer, COPY_COMPOSE, &led->value_mutex, layer
); // just copy led state on update
return led->composer_handle != NULL;
}
inline void write_led(SystemLed* led, Rgb* value) {
write_mutex(&led->value_mutex, (void*)value, sizeof(Rgb), OsWaitForever);
request_compose(led->composer_handle);
}
```
## Usage example
```C
void handle_led_state(Rgb* rgb, void* _ctx) {
printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue);
}
void led_example(void* p) {
LedApi* led_api = open_led("/dev/led");
if(led_api == NULL) return; // led not available, critical error
// subscribe to led state updates
subscribe_led_changes(led_api, handle_led_state, NULL);
// get current led value
Rgb led_value;
if(read_led(led_api, &led_value, OsWaitForever)) {
printf(
"initial led: #%02X%02X%02X\n",
led_value->red,
led_value->green,
led_value->blue
);
}
// create compose to control led
SystemLed system_led;
if(!init_led_composer(&system_led, led_api, UiLayerBelowNotify)) return;
// write RGB value
write_led(&system_led, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}));
}
```

View File

@ -0,0 +1,130 @@
# SPI
HAL struct `SPI_HandleTypeDef*` used for handling SPI info.
For transmit/receive data use `spi_xfer` function:
```C
bool spi_xfer(
SPI_HandleTypeDef* spi,
uint8_t* tx_data, uint8_t* rx_data, size_t len,
PubSubCallback cb, void* ctx);
```
* `tx_data` and `rx_data` size must be equal (and equal `len`)
* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback.
Blocking verison:
```C
inline static bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) {
semaphoreInfo s;
osSemaphore block = createSemaphoreStatic(s);
if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) {
osReleaseSemaphore(block);
return false;
}
osWaitSemaphore(block);
return false;
}
```
## SPI Bus
Common implementation of SPI bus: serial interface + CS pin
```C
typedef struct {
GpioPin* cs; ///< CS pin
ValueMutex* spi; ///< <SPI_HandleTypeDef*>
} SpiBus;
```
## SPI device
For dedicated work with one device there is `SpiDevice` entity. It contains ValueMutex around SpiBus: after you acquire device you can acquire spi to work with it (don't forget SPI bus is shared around many device, release it after every transaction as quick as possible).
```C
typedef struct {
ValueMutex* bus; ///< <SpiBus*>
} SpiDevice;
```
## SPI IRQ device
Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. For work with it there is special entity `SpiIrqDevice`. Use `subscribe_pubsub` for subscribinq to irq events.
```C
typedef struct {
ValueMutex* bus; ///< <SpiBus*>
PubSub* irq;
} SpiIrqDevice;
```
## Display device
Special implementation of SPI bus: serial interface + CS, Res, D/I lines.
```C
typedef struct {
GpioPin* cs; ///< CS pin
GpioPin* res; ///< reset pin
GpioPin* di; ///< D/I pin
ValueMutex* spi; ///< <SPI_HandleTypeDef*>
} DisplayBus;
```C
typedef struct {
ValueMutex* bus; ///< <DisplayBus*>
} DisplayDevice;
```
# SPI devices (F2)
* `/dev/sdcard` - SD card SPI, `SpiDevice`
* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice`
* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice`
* `/dev/display` - `DisplayDevice`
* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7)
### Application example
```C
// Be careful, this function called from IRQ context
void handle_irq(void* _arg, void* _ctx) {
}
void cc1101_example() {
SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus");
if(cc1101_device == NULL) return; // bus not available, critical error
subscribe_pubsub(cc1101_device->irq, handle_irq, NULL);
{
// acquire device as device bus
SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0);
if(spi_bus == NULL) {
printf("Device busy\n");
// wait for device
spi_bus = acquire_mutex_block(cc1101_device->bus);
}
// make transaction
uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF};
uint8_t response[4];
{
SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi);
gpio_write(spi_bus->cs, false);
spi_xfer_block(spi, request, response, 4);
gpio_write(spi_bus->cs, true);
release_mutex(cc1101_device->spi, spi);
}
// release device (device bus)
release_mutex(cc1101_device->bus, spi_bus);
}
}
```