Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Get started with Flipper Zero by building a Custom "Hello world" plugin

NotificationsYou must be signed in to change notification settings

DroomOne/Flipper-Plugin-Tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tutorial on how to build a basic "Hello world" plugin for Flipper Zero.

This tutorial includes:

  • Sourcecode template for a custom flipper plugin!
  • A step by step story on how to create the base of a custom flipper plugin. (There are many other ways of creating apps for flipper, this example is based on the existing snake app)

The tutorial was written during development offlappybird for flipper.

Screenshot

Hello World - The story

This is the step-by-step story version of the tutorial. You can skip this and directly continue to the sourcecode if you know what your doing. Make sure you don't forget to add the application to the makefile, and register its functions inapplications.c (see chapter: Building the firmware + plugin)

In this tutorial a simple hello world plugin is added to flipper. The goal is to render something in the screen, and make the buttons move that object. In this case it will be the classic "Hello World" text.

Downloading the firmware

  1. Clone or downloadflipperzero-firmware.
git clone https://github.com/flipperdevices/flipperzero-firmware
  1. Create a folder for the custom plugin inflipperzero-firmware/applications/. For the hello-world app, this will be:hello_world.
mkdir flipperzero-firmware/applications/hello_world
  1. Create a new source file in the newly created folder. The file name has to match the name of the folder.
touch flipperzero-firmware/applications/hello_world/hello_world.c

Plugin Main

For flipper to activate the plugin, a main function for the plugin has to be added. Following the naming convention of existing flipper plugins, this needs to be:hello_world_app.

  • Create anint32_t hello_world_app(void* p) function that will function as the entry of the plguin.

For the plugin to keep track of what actions have been executed, we create a messagequeue.

  • A by callingosMessageQueueNew we create a newosMessageQueueId_t that keeps track of events.

The view_port is used to control the canvas (display) and userinput from the hardware. In order to use this, aview_port has to be allocated, and callbacks to their functions registered. (The callback functions will later be added to the code)

  • view_port_alloc() will allocate a newview_port.
  • draw andinput callbacks originating from theview_port can be registerd with the functions
    • view_port_draw_callback_set
    • view_port_input_callback_set
  • Register theview_port to theGUI
int32_thello_world_app() {FuriMessageQueue*event_queue=furi_message_queue_alloc(8,sizeof(PluginEvent));// Set system callbacksViewPort*view_port=view_port_alloc();view_port_draw_callback_set(view_port,render_callback,NULL);view_port_input_callback_set(view_port,input_callback,event_queue);// Open GUI and register view_portGui*gui=furi_record_open("gui");gui_add_view_port(gui,view_port,GuiLayerFullscreen);     ...}

Callbacks

Flipper will let the plugin know once it is ready to deal with a new frame or once a button is pressed by the user.

input_callback:Signals the plugin once a button is pressed. The event is queued in the event_queue. In the main thread the queue read and handled.

A refrence to the queue is passed during the setup of the application.

typedefenum {EventTypeTick,EventTypeKey,}EventType;typedefstruct {EventTypetype;InputEventinput;}PluginEvent;staticvoidinput_callback(InputEvent*input_event,FuriMessageQueue*event_queue) {furi_assert(event_queue);PluginEventevent= {.type=EventTypeKey, .input=*input_event};furi_message_queue_put(event_queue,&event,FuriWaitForever);}

render_callback:Signals the plugin when flipper is ready to draw a new frame in the canvas. For the hello-world example this will be a simple frame around the outer edges.

staticvoidrender_callback(Canvas*constcanvas,void*ctx) {canvas_draw_frame(canvas,0,0,128,64);}

Main Loop and plugin State

The main loop runs during the lifetime of the plugin. For each loop we try to pop an event from the queue, and handle the queue item such as button input / plugin events.

For this example we render a new frame, every time the loop is run. This can be done by callingview_port_update(view_port);.

PluginEventevent;for(boolprocessing= true;processing;) {FuriStatusevent_status=furi_message_queue_put(event_queue,&event,100);if(event_status==FuriStatusOK) {// press eventsif(event.type==EventTypeKey) {if(event.input.type==InputTypePress) {switch(event.input.key) {caseInputKeyUp:caseInputKeyDown:caseInputKeyRight:caseInputKeyLeft:caseInputKeyOk:caseInputKeyBack:// Exit the pluginprocessing= false;break;                    }                }            }         }else {FURI_LOG_D(TAG,"FuriMessageQueue: event timeout");// event timeout        }view_port_update(view_port);     }

Plugin State

Because of the callback system, the plugin is being manipulated by different threads. To overcome race conditions we have to create a shared object that is safe to use.

  1. Allocate a new PluginState struct, and initialise it before the main loop.
typedefstruct { }PluginState;// in main:PluginState*plugin_state=malloc(sizeof(PluginState));
  1. UsingValueMutex we create a mutex for the plugin state calledstate_mutex.
  2. Initalise the mutex forPluginState usinginit_mutex()
  3. Pass the mutex as argument toview_port_draw_callback_set() so we can safely access the shared state from flippers thread.
typedefstruct { }PluginState;int32_thello_world_app() {FuriMessageQueue*event_queue=furi_message_queue_alloc(8,sizeof(PluginEvent));PluginState*plugin_state=malloc(sizeof(PluginState));ValueMutexstate_mutex;if (!init_mutex(&state_mutex,plugin_state,sizeof(PluginState))) {FURI_LOG_E("Hello_World","cannot create mutex\r\n");free(plugin_state);return255;    }// Set system callbacksViewPort*view_port=view_port_alloc();view_port_draw_callback_set(view_port,render_callback,&state_mutex);view_port_input_callback_set(view_port,input_callback,event_queue);...

Main Loop

Let's deal with the mutex in our main loop. So we can update values from the main loop based on user input. As an example, we will move a hello-world text through the screen. Based on user input.

  1. For this we add aint x andint y to your state.
typedefstruct {intx;inty;}PluginState;
  1. Initialise the values of the struct using a newhello_world_state_init() function.
staticvoidhello_world_state_init(PluginState*constplugin_state) {plugin_state->x=50;plugin_state->y=30;}

Call it after allocating the object in the main function.

PluginState*plugin_state=malloc(sizeof(PluginState));hello_world_state_init(plugin_state);ValueMutexstate_mutex; ...
  1. Aquire a blocking mutex after a new event is handled in the queue. Write values to the locked plugin_state object when user presses buttons. And release when we finish working with the state.
PluginEventevent;for(boolprocessing= true;processing;) {FuriStatusevent_status=furi_message_queue_put(event_queue,&event,100);PluginState*plugin_state= (PluginState*)acquire_mutex_block(&state_mutex);if(event_status==FuriStatusOK) {// press eventsif(event.type==EventTypeKey) {if(event.input.type==InputTypePress) {switch(event.input.key) {caseInputKeyUp:plugin_state->y--;break;caseInputKeyDown:plugin_state->y++;break;caseInputKeyRight:plugin_state->x++;break;caseInputKeyLeft:plugin_state->x--;break;caseInputKeyOk:caseInputKeyBack:processing= false;break;                }            }        }     }else {FURI_LOG_D(TAG,"FuriMessageQueue: event timeout");// event timeout    }view_port_update(view_port);release_mutex(&state_mutex,plugin_state);}...

Drawing Graphics

Creating graphics on flipper has been made easy by flippers developers. An canvas around the outer edges of the screen could be easly added with a single line:canvas_draw_frame(canvas, 0, 0, 128, 64);.

However, when it comes to dealing with user input, moving objects, changing processes we have to take into account that objects might be used by other threads. In the previous part we added a mutex in order to block any other thread writing to an object. For safe drawing graphics, we have to do the same.

  1. Aquire a mutex by callingacquire_mutex(). Therender_callback() has the context in a argument. Previously we told the set_callback function to useplugin_state for this.
  2. Check if the mutex is valid, otherwise skip this render
  3. Do all the drawing that we like.. In this case a simple text Hello world, on thex andy positions we have set in the plugin_state.
  4. Close the mutex again.
staticvoidrender_callback(Canvas*constcanvas,void*ctx) {constPluginState*plugin_state=acquire_mutex((ValueMutex*)ctx,25);if(plugin_state==NULL) {return;    }// border around the edge of the screencanvas_draw_frame(canvas,0,0,128,64);canvas_set_font(canvas,FontPrimary);canvas_draw_str_aligned(canvas,plugin_state.x,plugin_state.y,AlignRight,AlignBottom,"Hello World");release_mutex((ValueMutex*)ctx,plugin_state);}

Building the firmware + plugin

Before the plugin is added to flipper. We have to let the compiler know, where to find the plugins files.

  1. The applications needs a manifest file to let the firmware know how to use it. Therefore create a fileapplications\hello_world\application.fam
App(appid="hello_world",name="Hello World",apptype=FlipperAppType.PLUGIN,entry_point="hello_world_app",cdefines=["APP_HELLO_WORLD"],requires=["gui",    ],stack_size=2*1024,order=20,)
  1. The application needs to be registered in the menu to be called. This is possible by adding an entry toapplications\meta\application.fam

Add the application id to the list of provides for the specific menu. In this case under basic_plugins:

App(appid="basic_plugins",name="Basic applications for plug-in menu",apptype=FlipperAppType.METAPACKAGE,provides=["music_player","bt_hid","picopass","hello_world",    ],)

Now you can build the application!

About

Get started with Flipper Zero by building a Custom "Hello world" plugin

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp