Skip to content

Commit 69ef5bf

Browse files
authored
Merge pull request #722 from clemensvonmolo/code-structure-doc
Add docs for app creation and code structure
2 parents 91b2e50 + c3cc83a commit 69ef5bf

3 files changed

Lines changed: 150 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ As of now, here is the list of achievements of this project:
7272
## Documentation
7373

7474
### Develop
75-
75+
- [Rough structure of the code](doc/code/Intro.md)
76+
- [How to implement an application](doc/code/Apps.md)
7677
- [Generate the fonts and symbols](src/displayapp/fonts/README.md)
7778
- [Creating a stopwatch in Pinetime(article)](https://pankajraghav.com/2021/04/03/PINETIME-STOPCLOCK.html)
7879

doc/code/Apps.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Apps
2+
This page will teach you:
3+
- what apps in InfiniTime are
4+
- how to implement your own app
5+
6+
## Theory
7+
Apps are the things you can launch from the app selection you get by swiping up.
8+
At the moment, settings and even the app launcher itself or the clock are implemented very similarly, this might change in the future though.
9+
Every app in InfiniTime is it's own class.
10+
An instance of the class is created when the app is launched and destroyed when the user exits the app.
11+
They run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
12+
Apps are responsible for everything drawn on the screen when they are running.
13+
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected.
14+
15+
## Interface
16+
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`.
17+
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen.
18+
Other parameters should be references to controllers that the app needs.
19+
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
20+
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.
21+
If an app only needs to display some text and do something upon a touch screen button press,
22+
it does not need to override any of these functions, as LVGL can also handle touch events for you.
23+
If you have any doubts, you can always look at how the other apps are doing things.
24+
25+
### Continuous updating
26+
If your app needs to be updated continuously, yo can do so by overriding the `Refresh()` function in your class
27+
and calling `lv_task_create` inside the constructor.
28+
An example call could look like this: <br>
29+
`taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);` <br>
30+
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`.
31+
Remember to delete the task again using `lv_task_del`.
32+
The function `RefreshTaskCallback` is inherited from screen and just calls your `Refresh` function.
33+
34+
### Apps with multiple screens
35+
InfiniTime provides a mini-library in [displayapp/screens/ScreenList.h](/src/displayapp/screens/ScreenList.h)
36+
which makes it relatively easy to add multiple screens to your app.
37+
To use it, #include it in the header file of your app and add a ScreenList member to your class.
38+
The template argument should be the number of screens you need.
39+
You will also need to add `CreateScreen` functions that return `std::unique_ptr<Screen>`
40+
to your class, one for every screen you have.
41+
There are still some things left to to that I won't cover here.
42+
To figure them out, have a look at the "apps" ApplicationList, Settings and SystemInfo.
43+
44+
45+
## Creating your own app
46+
A minimal app could look like this: <br>
47+
MyApp.h:
48+
```cpp
49+
#pragma once
50+
51+
#include "displayapp/screens/Screen.h"
52+
#include <lvgl/lvgl.h>
53+
54+
namespace PineTime {
55+
namespace Applications {
56+
namespace Screens {
57+
class MyApp : public Screen {
58+
public:
59+
MyApp(DisplayApp* app);
60+
~MyApp() override;
61+
}
62+
}
63+
}
64+
}
65+
```
66+
67+
MyApp.cpp:
68+
```cpp
69+
#include "MyApp.h"
70+
#include "displayapp/DisplayApp.h"
71+
72+
using namespace Pinetime::Applications::Screens;
73+
74+
MyApp::MyApp(DisplayApp* app) : Screen(app) {
75+
lv_obj_t* title = lv_label_create(lv_scr_act(), NULL);
76+
lv_label_set_text_static(title, "My test application");
77+
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
78+
lv_obj_align(title, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
79+
}
80+
81+
MyApp::~MyApp() {
82+
lv_obj_clean(lv_scr_act());
83+
}
84+
```
85+
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/)
86+
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
87+
88+
Now we have our very own app, but InfiniTime does not know about it yet.
89+
The first step is to include your MyApp.cpp (or any new cpp files for that matter)
90+
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
91+
The next step to making it launchable is to give your app an id.
92+
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
93+
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
94+
Now, go to the function `DisplayApp::LoadApp` and add another case to the switch statement.
95+
The case will be the id you gave your app earlier.
96+
If your app needs any additional arguments, this is the place to pass them.
97+
98+
If you want your app to be launched from the regular app launcher, go to [displayapp/screens/ApplicationList.cpp](/src/displayapp/screens/ApplicationList.cpp).
99+
Add your app to one of the `CreateScreen` functions, or add another `CreateScreen` function if there are no empty spaces for your app. <br>
100+
If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.cpp](/src/displayapp/screens/settings/Settings.cpp).
101+
102+
You should now be able to [build](../buildAndProgram.md) the firmware
103+
and flash it to your PineTime. Yay!
104+
105+
Please remember to pay attention to the [UI guidelines](../ui_guidelines.md)
106+
when designing an app that you want to include in mainstream InfiniTime.

doc/code/Intro.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Introduction to the code
2+
This page is meant to guide you through the source code, so you can find the relevant files for what you're working on.
3+
4+
## FreeRTOS
5+
Infinitime is based on FreeRTOS, a real-time operating system.
6+
FreeRTOS provides several quality of life abstractions (for example easy software timers)
7+
and most importantly supports multiple tasks.
8+
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).
9+
The main "process" creates at least one task and then starts the FreeRTOS task scheduler.
10+
This main "process" is the standard main() function inside [main.cpp](/src/main.cpp).
11+
The task scheduler is responsible for giving every task enough cpu time.
12+
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.
13+
14+
### Tasks
15+
Tasks are created by calling `xTaskCreate` and passing a function with the signature `void functionName(void*)`.
16+
For more info on task creation see the [FreeRTOS Documentation](https://www.freertos.org/a00125.html).
17+
In our case, main calls `systemTask.Start()`, which creates the **"MAIN" task**.
18+
The function running inside that task is `SystemTask::Work()`.
19+
You may also see this task being referred to as the **work task**.
20+
Both functions are located inside [systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp). `SystemTask::Work()` initializes all the driver and controller objects.
21+
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).
22+
You can find the "displayapp" task inside [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
23+
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))
24+
and periodic tasks like heartrate measurements ([heartratetask/HeartRateTask.cpp](/src/heartratetask/HeartRateTask.cpp)). <br>
25+
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.
26+
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)
27+
it will need instead of just typing in a large-ish number.
28+
You can use the define `configMINIMAL_STACK_SIZE` which is currently set to 120 words.
29+
30+
## Controllers
31+
Controllers in InfiniTime are singleton objects that can provide access to certain resources to apps.
32+
Some of them interface with drivers, others are the driver for the resource.
33+
The resources provided don't have to be hardware-based.
34+
They are declared in main.cpp and initialized in [systemtask/SystemTask.cpp](/src/systemtask/SystemTask.cpp).
35+
Some controllers can be passed by reference to apps that need access to the resource (for example vibration motor).
36+
They reside in [components/](/src/components/) inside their own subfolder.
37+
38+
## Apps
39+
For more detail see the [Apps page](./Apps.md)
40+
41+
## Bluetooth
42+
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/).

0 commit comments

Comments
 (0)