1st version of dev doc

This commit is contained in:
Nico CARTRON 2021-12-20 21:42:34 +01:00
parent 424854c9e4
commit 048b7227f1

View File

@ -1,6 +1,284 @@
# Developer documentation
This page is meant to guide you through the source code, so you can find the
relevant files for what you're working on.
## Rough structure of the code
### FreeRTOS
Infinitime is based on FreeRTOS, a real-time operating system. FreeRTOS provides
several quality of life abstractions (for example easy software timers) and most
importantly supports multiple tasks. If you want to read up on real-time
operating systems, you can look
[here](https://www.freertos.org/implementation/a00002.html) and
[here](https://www.freertos.org/features.html). The main "process" creates at
least one task and then starts the FreeRTOS task scheduler. This main "process"
is the standard main() function inside
[main.cpp](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/src/main.cpp).
The task scheduler is responsible for giving every task enough cpu time. As
there is only one core on the SoC of the PineTime, real concurrency is
impossible and the scheduler has to swap tasks in and out to emulate it.
#### Tasks
Tasks are created by calling `xTaskCreate` and passing a function with the
signature `void functionName(void*)`.
For more info on task creation see the [FreeRTOS
Documentation](https://www.freertos.org/a00125.html).
In our case, main calls `systemTask.Start()`, which creates the **"MAIN" task**.
The function running inside that task is `SystemTask::Work()`.
You may also see this task being referred to as the **work task**.
Both functions are located inside
[systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp).
`SystemTask::Work()` initializes all the driver and controller objects.
It also starts the **task "displayapp"**, which is responsible for launching and
running apps, controlling the screen and handling touch events (or forwarding
them to the active app).
You can find the "displayapp" task inside
[displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
There are also other tasks that are responsible for Bluetooth ("ll" and "ble"
inside
[libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c](/src/libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c))
and periodic tasks like heartrate measurements
([heartratetask/HeartRateTask.cpp](/src/heartratetask/HeartRateTask.cpp)).
While it is possible for you to create your own task when you need it, it is
recommended to just add functionality to `SystemTask::Work()` if possible.
If you absolutely need to create another task, try to guess how much [stack
space](https://www.freertos.org/FAQMem.html#StackSize) (in words/4-byte packets)
it will need instead of just typing in a large-ish number.
You can use the define `configMINIMAL_STACK_SIZE` which is currently set to 120
words.
### Controllers
Controllers in InfiniTime are singleton objects that can provide access to
certain resources to apps.
Some of them interface with drivers, others are the driver for the resource.
The resources provided don't have to be hardware-based.
They are declared in main.cpp and initialized in
[systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp).
Some controllers can be passed by reference to apps that need access to the
resource (for example vibration motor).
They reside in [components/](/src/components/) inside their own subfolder.
For more detail see the [Apps page](./Apps.md)
### Bluetooth
Header files with short documentation for the functions are inside
[libs/mynewt-nimble/nimble/host/include/host/](/src/libs/mynewt-nimble/nimble/host/include/host/).
## How to implement an app
### Theory
The user interface of InfiniTime is made up of **screens**.
Screens that are opened from the app launcher are considered **apps**.
Every app in InfiniTime is it's own class.
An instance of the class is created when the app is launched and destroyed when
the user exits the app.
They run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
Apps are responsible for everything drawn on the screen when they are running.
By default, apps only do something (as in a function is executed) when they are
created or when a touch event is detected.
### Interface
Every app class has to be inside the namespace `Pinetime::Applications::Screens`
and inherit from `Screen`.
The constructor should have at least one parameter `DisplayApp* app`, which it
needs for the constructor of its parent class Screen.
Other parameters should be references to controllers that the app needs.
A destructor is needed to clean up LVGL and restore any changes (for example
re-enable sleeping).
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents
event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own
functionality for those events.
If an app only needs to display some text and do something upon a touch screen
button press,
it does not need to override any of these functions, as LVGL can also handle
touch events for you.
If you have any doubts, you can always look at how the other apps are doing
things.
#### Continuous updating
If your app needs to be updated continuously, you can do so by overriding the
`Refresh()` function in your class and calling `lv_task_create` inside the
constructor.
An example call could look like this:
```cpp
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
```
With `taskRefresh` being a member variable of your class and of type
`lv_task_t*`.
Remember to delete the task again using `lv_task_del`.
The function `RefreshTaskCallback` is inherited from `Screen` and just calls
your `Refresh` function.
### Creating your own app
A minimal app could look like this:
MyApp.h:
```cpp
#pragma once
#include "displayapp/screens/Screen.h"
#include <lvgl/lvgl.h>
namespace Pinetime {
namespace Applications {
namespace Screens {
class MyApp : public Screen {
public:
MyApp(DisplayApp* app);
~MyApp() override;
};
}
}
}
```
MyApp.cpp:
```cpp
#include "displayapp/screens/MyApp.h"
#include "displayapp/DisplayApp.h"
using namespace Pinetime::Applications::Screens;
MyApp::MyApp(DisplayApp* app) : Screen(app) {
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(title, "My test application");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
lv_obj_align(title, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
}
MyApp::~MyApp() {
lv_obj_clean(lv_scr_act());
}
```
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/)
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a
setting app.
Now we have our very own app, but InfiniTime does not know about it yet.
The first step is to include your MyApp.cpp (or any new cpp files for that
matter)
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
The next step to making it launchable is to give your app an id.
To do this, add an entry in the enum class `Pinetime::Applications::Apps`
([displayapp/Apps.h](/src/displayapp/Apps.h)).
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to
the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
Now, go to the function `DisplayApp::LoadApp` and add another case to the switch
statement.
The case will be the id you gave your app earlier.
If your app needs any additional arguments, this is the place to pass them.
If you want to add your app in the app launcher, add your app in
[displayapp/screens/ApplicationList.cpp](/src/displayapp/screens/ApplicationList.cpp)
to one of the `CreateScreen` functions, or add another `CreateScreen` function
if there are no empty spaces for your app. If your app is a setting, do the same
procedure in
[displayapp/screens/settings/Settings.cpp](/src/displayapp/screens/settings/Settings.cpp).
You should now be able to [build](../buildAndProgram.md) the firmware
and flash it to your PineTime. Yay!
Please remember to pay attention to the [UI guidelines](../ui_guidelines.md)
when designing an app that you want to be included in InfiniTime.
## Generating the fonts and symbols
## Creating a stopwatch in PineTime (article)
* [Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)
* [Awesome font from LVGL](https://lvgl.io/assets/others/FontAwesome5-Solid+Brands+Regular.woff)
* [Open Sans Light from Google](https://fonts.google.com/specimen/Open+Sans)
### Generate the fonts:
* Open the [LVGL font converter](https://lvgl.io/tools/fontconverter)
* Name : jetbrains_mono_bold_20
* Size : 20
* Bpp : 1 bit-per-pixel
* Do not enable font compression and horizontal subpixel hinting
* Load the file `JetBrainsMono-Bold.tff` (use the file in this repo to ensure the version matches) and specify the following range : `0x20-0x7f, 0x410-0x44f`
* Add a 2nd font, load the file `FontAwesome5-Solid+Brands+Regular.woff` and specify the following
range : `0xf293, 0xf294, 0xf244, 0xf240, 0xf242, 0xf243, 0xf241, 0xf54b, 0xf21e, 0xf1e6, 0xf54b, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf069, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf029, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf201, 0xf06e, 0xf015`
* Click on Convert, and download the file `jetbrains_mono_bold_20.c` and copy it in `src/DisplayApp/Fonts`
* Add the font .c file path to src/CMakeLists.txt
* Add an LV_FONT_DECLARE line in src/libs/lv_conf.h
Add new symbols:
* Browse the [cheatsheet](https://fontawesome.com/cheatsheet/free/solid) and find your new symbols
* For each symbol, add its hex code (0xf641 for the 'Ad' icon, for example) to the *Range* list (Remember to keep this
readme updated with newest range list)
* Convert this hex value into a UTF-8 code
using [this site](http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=f185&mode=hex)
* Define the new symbols in `src/displayapp/screens/Symbols.h`:
```
static constexpr const char* newSymbol = "\xEF\x86\x85";
```
### Simple method to generate a font
If you want to generate a basic font containing only numbers and letters, you can use the above settings but instead of specifying a range, simply list the characters you need in the Symbols field and leave the range blank. This is the approach used for the PineTimeStyle watchface.
This works well for fonts which will only be used to display numbers, but will fail if you try to add a colon or other punctuation.
* Open the [LVGL font converter](https://lvgl.io/tools/fontconverter)
* Name : open_sans_light
* Size : 150
* Bpp : 1 bit-per-pixel
* Do not enable font compression and horizontal subpixel hinting
* Load the file `open_sans_light.tff` (use the file in this repo to ensure the version matches) and specify the following symbols : `0123456789`
* Click on Convert, and download the file `open_sans_light.c` and copy it in `src/DisplayApp/Fonts`
* Add the font .c file path to src/CMakeLists.txt (search for jetbrains to find the appropriate location/format)
* Add an LV_FONT_DECLARE line in src/libs/lv_conf.h (as above)
#### Navigation font
To create the navigtion.ttf I use the web app [icomoon](https://icomoon.io/app)
this app can import the svg files from the folder
*src/displayapp/icons/navigation/unique* and create a ttf file the
project for the site is *lv_font_navi_80.json* you can import it to add or
remove icons
You can also use the online LVGL tool to create the .c
ttf file : navigation.ttf name : lv_font_navi_80 size : 80px Bpp : 2 bit-per-pixel range : 0xe900-0xe929
$lv_font_conv --font navigation.ttf -r '0xe900-0xe929' --size 80 --format lvgl --bpp 2 --no-prefilter -o
lv_font_navi_80.c
#### I use the method above to create the other ttf
## Creating a stopwatch in PineTime
[This article](https://pankajraghav.com/2021/04/03/PINETIME-STOPCLOCK.html) from
Pankaj Raghav describes in details how to create a stopwatch app in InfiniTime.
## Tips on designing an app UI
- Align objects all the way to the edge or corner
- Buttons should generally be at least 50px high
- Buttons should generally be on the bottom edge
- Make interactable objects **big**
- When using a page indicator, leave 8px for it on the right side
- It is acceptable to leave 8px on the left side as well to center the content
- Top bar takes at least 20px + padding
- Top bar right icons move 8px to the left when using a page indicator
- A black background helps to hide the screen border, allowing the UI to look
less cramped when utilizing the entire display area.
(add link to ![example layouts](./ui/example.png))
## BLE implementation and API