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:
33
core/app.cpp
33
core/app.cpp
@@ -2,23 +2,28 @@
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
FILE* get_debug();
|
||||
#include "startup.h"
|
||||
#include "furi.h"
|
||||
#include "debug.h"
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
bool is_alive = false;
|
||||
do {
|
||||
is_alive = false;
|
||||
for(size_t i = 0; i < sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0]); i++) {
|
||||
if(handlers[i]->handler != NULL) {
|
||||
is_alive = true;
|
||||
}
|
||||
}
|
||||
delay(500);
|
||||
// TODO add deferred event queue here
|
||||
} while(is_alive);
|
||||
}
|
@@ -18,7 +18,7 @@ ssize_t uart_write(void* cookie, const char * buffer, size_t size) {
|
||||
}
|
||||
|
||||
FILE* get_debug() {
|
||||
FILE* fp = fopencookie(NULL,"w+", (cookie_io_functions_t){
|
||||
FILE* fp = fopencookie(NULL, "w+", (cookie_io_functions_t){
|
||||
.read = NULL,
|
||||
.write = uart_write,
|
||||
.seek = NULL,
|
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" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include "flipper_hal.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "furi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user