flipperzero-firmware/wiki/fw/api/Basic-API.md
coreglitch 942bbfaefe
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>
2020-10-13 11:22:43 +03:00

10 KiB

Flipper universal registry implementation (FURI)

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);

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.

typedef struct {
    void* value;
    size_t size;
    osMutex mutex;
    
    osMutexDescriptor __static // some internals;
} ValueMutex;

Create ValueMutex. Create instance of ValueMutex and call init_mutex.

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.

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.

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

/*
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).

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) {
    pubsub->count = 0;

    for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) {
        pubsub->items[i].
    }
}

Use subscribe_pubsub to register your callback.

// 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.

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.

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

/*
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

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 or Display API for examples.

ValueManager

More complicated concept is ValueManager. It is like ValueMutex, but user can subscribe to value updates.

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.

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.

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;
}