diff --git a/README.md b/README.md index 9b9c3287..ee1fad00 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ As of now, here is the list of achievements of this project: ## Documentation ### Develop - + - [Rough structure of the code](doc/code/Intro.md) + - [How to implement an application](doc/code/Apps.md) - [Generate the fonts and symbols](src/displayapp/fonts/README.md) - [Creating a stopwatch in Pinetime(article)](https://pankajraghav.com/2021/04/03/PINETIME-STOPCLOCK.html) diff --git a/doc/code/Apps.md b/doc/code/Apps.md new file mode 100644 index 00000000..b3bab042 --- /dev/null +++ b/doc/code/Apps.md @@ -0,0 +1,106 @@ +# Apps +This page will teach you: +- what apps in InfiniTime are +- how to implement your own app + +## Theory +Apps are the things you can launch from the app selection you get by swiping up. +At the moment, settings and even the app launcher itself or the clock are implemented very similarly, this might change in the future though. +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, yo 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:
+`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. + +### Apps with multiple screens +InfiniTime provides a mini-library in [displayapp/screens/ScreenList.h](/src/displayapp/screens/ScreenList.h) +which makes it relatively easy to add multiple screens to your app. +To use it, #include it in the header file of your app and add a ScreenList member to your class. +The template argument should be the number of screens you need. +You will also need to add `CreateScreen` functions that return `std::unique_ptr` +to your class, one for every screen you have. +There are still some things left to to that I won't cover here. +To figure them out, have a look at the "apps" ApplicationList, Settings and SystemInfo. + + +## Creating your own app +A minimal app could look like this:
+MyApp.h: +```cpp +#pragma once + +#include "displayapp/screens/Screen.h" +#include + +namespace PineTime { + namespace Applications { + namespace Screens { + class MyApp : public Screen { + public: + MyApp(DisplayApp* app); + ~MyApp() override; + } + } + } +} +``` + +MyApp.cpp: +```cpp +#include "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(), NULL); + 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 your app to be launched from the regular app launcher, go to [displayapp/screens/ApplicationList.cpp](/src/displayapp/screens/ApplicationList.cpp). +Add your app 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 include in mainstream InfiniTime. diff --git a/doc/code/Intro.md b/doc/code/Intro.md new file mode 100644 index 00000000..762102fe --- /dev/null +++ b/doc/code/Intro.md @@ -0,0 +1,42 @@ +# Introduction to the code +This page is meant to guide you through the source code, so you can find the relevant files for what you're working on. + +## 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](/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. + +## Apps +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/).