system-prompts/prompts/gpts/knowledge/Flipper Zero App Builder/Flipper Zero App Builder.txt
2023-11-28 11:36:31 +08:00

226 lines
16 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

BUILDING AN APP FOR FLIPPER ZERO, PART 1: GETTING STARTED
2023-05-05 11-minute read
FlipperZero is a digital signals multi-tool. Heres a short guide to the basics of building an app for it…
The Flipper Zero is a digital signals multi-tool device, with some fun applications. It has an infrared module, a sub-GHz radio, RFID and NFC capability, iButton, USB, a screen, input controls, and GPIO pins. Its small enough to fit in your hand, and it can communicate with your home appliances, or help you to learn about the signals that fly around our world. Its also extremely customisable.
Lets build an app for Flipper Zero
Flipper Zero features several apps built directly into the firmware, but you can also create and install external apps onto the SD card which are then available through the Applications menu.
Resistance is futile
With the help of several folks from the Flipper dev community, I recently built an app to calculate the value of resistors by decoding the coloured bands on them.
More detail and source code at:
instantiator/flipper-zero-experimental-apps
Thanks to
Derek Jamison for helping to debug a thorny issue or two, and
Kuronons for the graphics in the app.
In this tutorial, well walk through the process of setting up an app, and building a simple user interface, using the same techniques.
Community
Before getting started, its helpful to know that theres a thriving and welcoming FlipperZero community on a public Discord server:
Flipper Devices
With thanks
This part of the tutorial series would not have been possible without the help and support of various people in the Flipper Zero community. Many thanks to:
Derek Jamison for his plugin tutorials, and accompanying wiki
FlipperZero folks for the ufbt tool, and
Aleksey Korolev for the flipc.org Flipper app catalogue.
The toolchain
Im using a few tools:
ufbt (“micro Flipper Build Tool”) - a version of fbt (the Flipper Built Tool) that supports everything you need for app development
Visual Studio Code - an editor and IDE from Microsoft
Pinta - a Mac clone of paint.net (but any graphics tool that allows you to edit 1-bit PNGs will do)
The experience working with Visual Studio Code is brilliant. It provides project management features, C syntax awareness and highlighting, and ufbt provides a nice integration to support build and debug options from the IDE.
Versions and firmwares
Flipper apps are written in C, and theyre compiled against the version of the firmware you want them to run against. This means that if your firmware changes, your app must too.
This create a little complexity for distributing an app - as the firmware versions regularly frequently, and many people are using different flavours of firmware altogether (eg. DarkFlipper/Unleashed, RogueMaster).
NB. An exploration of the various firmware features is beyond the scope of this tutorial.
Theres a nice app distribution site called flipc.org that solves this problem by compiling apps from source code as theyre needed. Provided your source code is in a public GitHub repository, you can use flipc.org to publish it.
Coming to C from C-type languages
If youre not familiar with C, but youve worked in C-type languages, youre off to a good start. There are few things to know, though. Feel free to skip this section if youre already a confident C developer.
Caveat: Ive not been working in C for long, but as Ive experienced most of these pitfalls, I wanted to share them with you to help you avoid the same mistakes! Some of these are very difficult to debug - especially memory leaks.
Memory safety
C isnt managed code, and theres no garbage collection, so youre going to have to take good care of the memory you allocate to data structures (structs) with malloc and free functions. If you allocate some memory, youre going to have to free it - particularly if your code can do the allocation more than once. If you dont release it, this can cause a memory leak - where you eventually run out of assignable memory.
C also wont prevent you from accessing memory you shouldnt (eg. if you malloc the wrong size for your struct, and then try to write to it (or if you write to an incorrect offset to a pointer), you could end up writing to memory reserved for other variables or even code). This sort of issue manifests as weird crashes or unexpected values in variables, often long after the actual buggy code has been executed. That makes it difficult to debug.
Namespaces
C doesnt have objects or enforced namespaces, so as soon as youre writing something reasonably complex I recommend you start naming things carefully, so that its absolutely clear whats what.
For instance, enums are named in code, but this is a thin veil over integers, and you can use enum values as integers anywhere in the code. I recommend giving enum values distinct names prefixed with the name of the enum type itself, eg.
typedef enum {
TagEventTypeInput,
TagEventTypeSubGhzDataDetected,
TagEventTypeInfraredMessage,
} TagEventType;
CapitalisationOfWords or using_underscore_delimiters are both acceptable ways to clarify namespaces and meanings in my book. Im sure theres an established convention. My advice is to be consistent in whatever you do.
static and const
Its worth understanding what keywords like static and const mean in C - they wont be perfectly aligned to how youve been using them in (say) Java, C++, or C#…
static (function) - a static function is only available to other methods in the same .c file
static (global variable) - a static global variable is only available to methods in the same .c file
static (local variable) - a static variable in a function keeps its value between function calls
Hiding functions with static is similar to creating private methods in other languages, and its considered good practice.
The const keyword distinguishes between pointers and variables, and has a few variants. Its used to indicate that a particular pointer or variable cannot be assigned to after initialisation. Dont worry if this doesnt all go in, its just worth knowing…
int var = 5; an assignable integer
int const var = 5; an unassignable integer
int* var - an assignable pointer to an assignable integer
const int* var - an assignable pointer to an unassignable integer
int* const var - an unassignable pointer to an assignable integer
const int* const var - an unassignable pointer to an unassignable integer
Data structures
struct and enum are similar to what youd expect although, as mentioned earlier, enum values are actually integers - and so not as type-safe as you might assume.
struct is about as close as you can get to a data class - and youll see them used all over C to represent rich data structures.
Code, headers and interfaces
.c files contain code. Its possible to write an entire application in a single .c file - and Ive seen several of these. Its a perfectly acceptable approach, but you risk encountering complexity and difficulties as you go.
The C compiler cares about the order that things are defined in. So, for instance, if you want to refer to a function, it needs to have been defined before you first refer to it. If not, youll see a couple of errors.
For instance, if you refer to a function called do_something before you define it, youll see something like this where you first call it:
implicit declaration of function 'do_something'
and then, where you define it:
conflicting types for 'do_something'
This is because when you first referred to function do_something, you implicitly defined it. Then, later, when the compiler encountered your actual definition it doesnt know what to do - it already has an implicit definition.
There are two solutions to this:
Place the function definition before its first reference in the .c file
Define the functions footprint in a .h header file, and include that header file at the beginning of your .c file
In this simple scenario, its probably easier just to reorder your functions.
As soon as you want to start defining a group or library of functions and structures that are all linked, youll want to place them into a separate .c file and then create a header file you can use to define the interface (the methods youre sharing with other .c files).
As a rule of thumb, if you want to include a header file from your own code, use inverted commas:
#include "my_lib.h"
If you want to include a library header (eg. from the firmware), use angle brackets:
#include <gui/gui.h>
The good news is that ufbt is smart. It will find all your .c code files, and compile them in a sensible order. (The days of having to list all your code files for gcc are a long distant nightmare…)
Firmware headers
A lot of this tutorial will use the gui libraries made available through the official firmware. If you ever need to figure out how something works, its definitely worth taking a look.
If you do poke around youll see plenty of .c code files and .h header files. Those header files define exactly which functions, structs, enums, global variables, and macros youll be able to use to develop your app.
Youll also see some header files that end with the suffix _i.h. These are internal headers. This means that their content is not made available to app developers. You shouldnt need anything in them, although occasionally you may see something in there youd like to use. Try searching to see if its called as a part of another function. Theres often a good reason why a function has not been exposed to app developers. If you still think you need it, you can always raise the question on Discord.
Setting up your environment
First, create the directory you want to work in, open a terminal, and navigate to that directory.
$ mkdir test_app
$ cd test_app
I often open a terminal in Visual Studio Code, as its easiest to work in a single tool and see whats happening as I go.
Fetch the FlipperZero firmware
Update ufbt to collect the Software Development Kit (SDK) for the current firmware. You can specify the firmware channel using the --channel=[dev|rc|release] option.
$ ufbt update --channel=dev
13:36:51.062 [I] Deploying SDK for f7
13:36:51.062 [I] Fetching version info for UpdateChannel.DEV from https://update.flipperzero.one/firmware/directory.json
13:36:51.201 [I] Using version: a7d1ec03
13:36:51.201 [I] uFBT SDK dir: /Users/lewiswestbury/.ufbt/current
13:36:53.304 [I] Deploying SDK
13:36:53.556 [I] SDK deployed.
NB. I recommend working against the dev channel firmware. It enables quite a lot of safeguards to help catch certain kinds of mistakes early (youll see theses as furi_assert assertions in firmware code, and you can use them yourself).
NNB. If a furi_assert fails (ie. the assertion isnt true), it will hang your program. It can sometimes be quite difficult to identify or locate the problem, particularly if you havent tried to debug your code yet, but at least youll know theres an issue. Ill cover debugging in a future tutorial (once I have the supporting hardware). In the meantime, you could check out Derek Jamisons guide on YouTube: Flipper Zero: Debugging FURI_ASSERT failures
Your Flippers firmware should match the firmware youre working against, or the app wont launch. You can use ufbt to flash the firmware on your Flipper:
ufbt flash_usb
ufbt will also flash through an ST-link if you have that available:
ufbt flash
The qFlipper application also supports this:
the qFlipper app showing the firmware view and the big green update button
Create a skeleton app
ufbt create will create a nice working environment for your app. Give your app a simple id, eg. test_app - youll be able to change this later. Provide an id for the application, with the APPID= option.
NB. I recommend using characters from the simple set of a-zA-Z0-9_. Restrict yourself to characters that can appear in a C function name, or you may end up having to rename the main function, and the entrypoint in your application manifest before you can build.
Running for the first time, itll warn that the application.fam file doesnt exist (and then create one for you):
$ ufbt create APPID=test_app
scons: Entering directory `/Users/lewiswestbury/.ufbt/current/scripts/ufbt'
fbt: warning: Folder app: manifest application.fam is missing
LoadAppManifest, line 31, in file "/Users/lewiswestbury/.ufbt/current/scripts/fbt_tools/fbt_apps.py"
Creating '/Users/lewiswestbury/src/test_app/test_app.c'
INSTALL /Users/lewiswestbury/src/test_app/test_app.png
Creating '/Users/lewiswestbury/src/test_app/application.fam'
Mkdir("/Users/lewiswestbury/src/test_app/images")
Touch("/Users/lewiswestbury/src/test_app/images/.gitkeep")
You should see the following files in the directory now:
application.fam - this is the app manifest, with details about your app
ufbt-test.c - application source code
ufbt-test.png - a 10x10 1-bit png icon for the app
images/ - a directory where you can place 1-bit png icons
A screenshot of visual studio code showing a terminal with ufbt create, and the application manifest it has created
Test a build
With no other options, ufbt will build your app.
$ ufbt
scons: Entering directory `/Users/lewiswestbury/.ufbt/current/scripts/ufbt'
ICONS /Users/lewiswestbury/.ufbt/build/test_app/test_app_icons.c
CDB /Users/lewiswestbury/src/test_app/.vscode/compile_commands.json
CC /Users/lewiswestbury/src/test_app/test_app.c
CC /Users/lewiswestbury/.ufbt/build/test_app/test_app_icons.c
LINK /Users/lewiswestbury/.ufbt/build/test_app_d.elf
INSTALL /Users/lewiswestbury/src/test_app/dist/debug/test_app_d.elf
APPMETA /Users/lewiswestbury/.ufbt/build/test_app.fap
FAP /Users/lewiswestbury/.ufbt/build/test_app.fap
INSTALL /Users/lewiswestbury/src/test_app/dist/test_app.fap
APPCHK /Users/lewiswestbury/.ufbt/build/test_app.fap
Target: 7, API: 23.3
When complete youll find a file called test_app.fap inside the dist directory. This is your entire application, bundled for the Flipper Zero. If you upload this to your Flipper Zero, into the Examples directory of your SD card, itll appear in the Applications menu. (It doesnt do much, but you can run it from there.)
You can change the directory for the app, by altering the fap_category entry in application.fam.
Visual Studio integration
Visual Studio Code can support some nice shortcuts to make your life easer, and ufbt can install them.
$ ufbt vscode_dist
scons: Entering directory `/Users/lewiswestbury/.ufbt/current/scripts/ufbt'
Creating '/Users/lewiswestbury/src/test_app/.vscode/c_cpp_properties.json'
Creating '/Users/lewiswestbury/src/test_app/.vscode/extensions.json'
Creating '/Users/lewiswestbury/src/test_app/.vscode/launch.json'
Creating '/Users/lewiswestbury/src/test_app/.vscode/settings.json'
Creating '/Users/lewiswestbury/src/test_app/.vscode/tasks.json'
INSTALL /Users/lewiswestbury/src/test_app/.clang-format
INSTALL /Users/lewiswestbury/src/test_app/.editorconfig
INSTALL /Users/lewiswestbury/src/test_app/.gitignore
Visual Studio Code now becomes aware of references to the SDK in your code, which is very helpful for syntax and error highlighting. You can now also use shortcuts in the IDE:
Shift + Command + B = build menu
Shift + Command + D = debugging menu
Quick review
Weve looked at Flipper Zero firmware, pitfalls of working in C, setting up your environment, creating a skeleton app, and testing your toolchain with a simple build.
In the next part, well explore the various approaches, structures and libraries available to help you build a graphical interface for the Flipper.