InfiniTime.git

commit 2d421ee25657381cb1bb99bc3867f357ed25a3e9

Author: Adam <git@apiote.xyz>

update to 1.14.0

 .devcontainer/Dockerfile | 1 
 .github/ISSUE_TEMPLATE/bug-report.yaml | 2 
 .github/ISSUE_TEMPLATE/issue-report.yaml | 2 
 .github/workflows/main.yml | 10 
 .vscode/c_cpp_properties.json | 2 
 CMakeLists.txt | 8 
 README.md | 20 
 doc/SimpleWeatherService.md | 69 +
 doc/ble.md | 5 
 doc/buildAndProgram.md | 18 
 doc/buildWithDocker.md | 10 
 doc/code/Apps.md | 170 ++
 docker/Dockerfile | 15 
 src/CMakeLists.txt | 69 
 src/components/ble/NimbleController.h | 6 
 src/components/ble/NotificationManager.h | 7 
 src/components/ble/SimpleWeatherService.cpp | 160 ++
 src/components/ble/SimpleWeatherService.h | 145 ++
 src/components/ble/weather/WeatherData.h | 385 ------
 src/components/ble/weather/WeatherService.cpp | 605 ----------
 src/components/ble/weather/WeatherService.h | 169 --
 src/components/datetime/DateTimeController.cpp | 4 
 src/components/gfx/Gfx.cpp | 4 
 src/components/motion/MotionController.cpp | 121 +
 src/components/motion/MotionController.h | 32 
 src/components/settings/Settings.h | 29 
 src/displayapp/Apps.h | 43 
 src/displayapp/Controllers.h | 56 
 src/displayapp/DisplayApp.cpp | 156 +-
 src/displayapp/DisplayApp.h | 8 
 src/displayapp/DisplayAppRecovery.cpp | 9 
 src/displayapp/DisplayAppRecovery.h | 8 
 src/displayapp/UserApps.h | 58 
 src/displayapp/WatchFaces.h | 14 
 src/displayapp/apps/Apps.h.in | 77 +
 src/displayapp/apps/CMakeLists.txt | 36 
 src/displayapp/fonts/CMakeLists.txt | 2 
 src/displayapp/fonts/README.md | 14 
 src/displayapp/fonts/fonts.json | 17 
 src/displayapp/screens/Alarm.cpp | 4 
 src/displayapp/screens/Alarm.h | 30 
 src/displayapp/screens/ApplicationList.cpp | 26 
 src/displayapp/screens/ApplicationList.h | 40 
 src/displayapp/screens/CheckboxList.h | 2 
 src/displayapp/screens/Clock.cpp | 135 --
 src/displayapp/screens/Clock.h | 60 
 src/displayapp/screens/HeartRate.h | 11 
 src/displayapp/screens/InfiniPaint.h | 13 
 src/displayapp/screens/List.h | 2 
 src/displayapp/screens/Metronome.h | 11 
 src/displayapp/screens/Motion.h | 12 
 src/displayapp/screens/Music.h | 13 
 src/displayapp/screens/Navigation.cpp | 293 +++-
 src/displayapp/screens/Navigation.h | 19 
 src/displayapp/screens/Paddle.h | 13 
 src/displayapp/screens/Steps.h | 13 
 src/displayapp/screens/StopWatch.cpp | 6 
 src/displayapp/screens/StopWatch.h | 91 
 src/displayapp/screens/Symbols.h | 3 
 src/displayapp/screens/SystemInfo.cpp | 8 
 src/displayapp/screens/Tile.cpp | 3 
 src/displayapp/screens/Tile.h | 3 
 src/displayapp/screens/Timer.cpp | 2 
 src/displayapp/screens/Timer.h | 67 
 src/displayapp/screens/Twos.cpp | 2 
 src/displayapp/screens/Twos.h | 13 
 src/displayapp/screens/WatchFaceCasioStyleG7710.h | 23 
 src/displayapp/screens/WatchFaceDigital.h | 21 
 src/displayapp/screens/WatchFaceInfineat.h | 22 
 src/displayapp/screens/WatchFaceMine.cpp | 30 
 src/displayapp/screens/WatchFaceMine.h | 28 
 src/displayapp/screens/WatchFacePineTimeStyle.cpp | 40 
 src/displayapp/screens/WatchFacePineTimeStyle.h | 31 
 src/displayapp/screens/WatchFaceTerminal.h | 21 
 src/displayapp/screens/Weather.cpp | 221 ---
 src/displayapp/screens/Weather.h | 45 
 src/displayapp/screens/WeatherSymbols.h | 46 
 src/displayapp/screens/settings/SettingDisplay.cpp | 4 
 src/displayapp/screens/settings/SettingWakeUp.cpp | 6 
 src/displayapp/screens/settings/SettingWakeUp.h | 3 
 src/displayapp/screens/settings/SettingWatchFace.cpp | 47 
 src/displayapp/screens/settings/SettingWatchFace.h | 29 
 src/displayapp/screens/settings/SettingWeatherFormat.cpp | 63 +
 src/displayapp/screens/settings/SettingWeatherFormat.h | 26 
 src/displayapp/screens/settings/Settings.cpp | 2 
 src/displayapp/screens/settings/Settings.h | 6 
 src/drivers/SpiMaster.cpp | 12 
 src/drivers/Watchdog.cpp | 2 
 src/resources/CMakeLists.txt | 4 
 src/resources/generate-img.py | 3 
 src/resources/images.json | 14 
 src/resources/lv_img_conv.py | 193 +++
 src/systemtask/SystemTask.cpp | 9 
 src/utility/CircularBuffer.h | 51 
 src/utility/Math.cpp | 49 
 src/utility/Math.h | 10 


diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 46e2facbdeb0ab996d1d8fa8b383ae1134e952e6..e4ad5c4fa8034c4162d30a482da02cb378f5aefa 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -11,6 +11,7 @@       git \
       make \
       python3 \
       python3-pip \
+      python3-pil \
       tar \
       unzip \
       wget \ 




diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
index f032d0348e595ae1262b23b34b4358d0e17de3d7..8bf4a4268f2d0acc73b95bc45a457a24d71d50e4 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -7,7 +7,7 @@     attributes:
       label: Verification
       description: Before submitting a bug report, check if similar issues already exist and use those issues to provide your feedback instead.
       options:
-      - label: I searched for similar bug reports and found none was relevant.
+      - label: I searched for similar bug reports (including closed issues) and found none was relevant.
         required: true
   - type: input
     id: desc-brief




diff --git a/.github/ISSUE_TEMPLATE/issue-report.yaml b/.github/ISSUE_TEMPLATE/issue-report.yaml
index 05cca32c4c1e8b0f077a411095906c180f17a670..add6b7d5b1e0b2e797b89957a2cad4c35fd164f7 100644
--- a/.github/ISSUE_TEMPLATE/issue-report.yaml
+++ b/.github/ISSUE_TEMPLATE/issue-report.yaml
@@ -6,7 +6,7 @@   attributes:
     label: Verification
     description: Before submitting an issue, check if similar issues already exist and use those issues to provide your feedback instead.
     options:
-      - label: I searched for similar issues and found none was relevant.
+      - label: I searched for similar issues (including closed issues) and found none was relevant.
         required: true
 - type: textarea
   attributes:




diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 89c1b2764df56f69941e83dcf49ddecd0b0f811b..c24e23748f9e6cd7bc1fa89be77a301fe362eae5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -31,6 +31,10 @@       - name: Checkout source files
         uses: actions/checkout@v3
         with:
           submodules: recursive
+      - name: Install resource build dependencies
+        run:  |
+          apt-get update
+          apt-get -y install --no-install-recommends python3-pil
       - name: Build
         shell: bash
         run: /opt/build.sh all
@@ -61,10 +65,10 @@
   build-simulator:
     runs-on: ubuntu-22.04
     steps:
-    - name: Install SDL2 development package
+    - name: Install SDL2 and libpng development package
       run:  |
         sudo apt-get update
-        sudo apt-get -y install libsdl2-dev
+        sudo apt-get -y install libsdl2-dev libpng-dev
 
     - name: Install Ninja
       run:  |
@@ -82,7 +86,7 @@
     - name: Get InfiniSim repo
       run:  |
         git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main
-        git -C InfiniSim submodule update --init lv_drivers libpng
+        git -C InfiniSim submodule update --init lv_drivers
 
     - name: CMake
       # disable BUILD_RESOURCES as this is already done when building the firmware




diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index f8da48d59e567e016654efda75afccf743784d34..392f4151e477c45506a59427e8c02a70d75c49fd 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -10,7 +10,7 @@             ],
             "defines": [],
             "compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
             "cStandard": "c11",
-            "cppStandard": "c++14",
+            "cppStandard": "c++20",
             "intelliSenseMode": "linux-gcc-arm",
             "configurationProvider": "ms-vscode.cpp-tools",
             "compileCommands": "${workspaceFolder}/build/compile_commands.json"




diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f95edadda997d0660f1ba7cd40ba2279eeb4dae..9b5669b003254e62d8813cc0fdaddc4a05c75bb1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 3.10)
 
 set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
 
-project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
+project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM)
 
 set(CMAKE_C_STANDARD 99)
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 
 # set(CMAKE_GENERATOR "Unix Makefiles")
 set(CMAKE_C_EXTENSIONS OFF)
@@ -13,7 +13,6 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-
 set(NRF_TARGET "nrf52")
 
 if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
@@ -33,7 +32,7 @@   set(BUILD_RESOURCES true)
 endif()
 
 set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device")
-set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY-TFK5 MOY-TIN5 MOY-TON5 MOY-UNK)
+set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY_TFK5 MOY_TIN5 MOY_TON5 MOY_UNK)
 
 set(PROJECT_GIT_COMMIT_HASH "")
 
@@ -69,6 +68,5 @@
 set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!")
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh)
-
 
 add_subdirectory(src)




diff --git a/README.md b/README.md
index 2d397818a93b78e37c72b45177e8eb660c19d171..8d612360c48bdcad218a8ef145224e88faba8c50 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,11 @@
 - [Gadgetbridge](https://gadgetbridge.org/) (Android)
 - [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
 - [Siglo](https://github.com/alexr4535/siglo) (Linux)
-- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
+- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
 - [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
 - [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
 
-***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
+***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
 
 ## Development
 
@@ -72,17 +72,17 @@ This project is released under the GNU General Public License version 3 or, at your option, any later version.
 
 It integrates the following projects:
 
-- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
-- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
-- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
-- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
+- RTOS: **[FreeRTOS](https://freertos.org)** under the MIT license
+- UI: **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
+- BLE stack: **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
+- Font: **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
 
 ## Credits
 
-I’m not working alone on this project. First, many people create PR for this project. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
+I’m not working alone on this project. First, many people create pull requests for this project. Then, there is the whole #pinetime community: a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums.
 
 Here are some people I would like to highlight:
 
-- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
-- [Koen](https://github.com/bosmoment) : He’s working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
-- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!
+- [Atc1441](https://github.com/atc1441/): He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
+- [Koen](https://github.com/bosmoment): He’s working on a firmware based on RiotOS. He integrated similar libs as me: NimBLE, LittleVGL,… His help was invaluable too!
+- [Lup Yuen Lee](https://github.com/lupyuen): He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!




diff --git a/doc/SimpleWeatherService.md b/doc/SimpleWeatherService.md
new file mode 100644
index 0000000000000000000000000000000000000000..e0fffba78850eb782686d62c1ea94914d337deaf
--- /dev/null
+++ b/doc/SimpleWeatherService.md
@@ -0,0 +1,69 @@
+# Simple Weather Service
+
+## Introduction
+
+The Simple Weather Service provides a simple and straightforward API to specify the current weather and the forecast for the next 5 days.
+It effectively replaces the original Weather Service (from InfiniTime 1.8) since InfiniTime 1.14.
+
+## Service
+
+The service UUID is `00050000-78fc-48fe-8e23-433b3a1942d0`.
+
+## Characteristics
+
+## Weather data (UUID 00050001-78fc-48fe-8e23-433b3a1942d0)
+
+The host uses this characteristic to update the current weather information and the forecast for the next 5 days.
+
+This characteristics accepts a byte array with the following 2-Bytes header:
+
+ - [0] Message Type : 
+   - `0` : Current weather
+   - `1` : Forecast
+ - [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases
+
+### Current Weather 
+
+The byte array must contain the following data:
+
+ - [0] : Message type = `0`
+ - [1] : Message version = `0`
+ - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970)  in local time (the same timezone as the one used to set the time)
+ - [10, 11] : Current temperature (°C * 100)
+ - [12, 13] : Minimum temperature (°C * 100)
+ - [14, 15] : Maximum temperature (°C * 100)
+ - [16]..[47] : location (string, unused characters should be set to `0`)
+ - [48] : icon ID 
+   - 0 = Sun, clear sky
+   - 1 = Few clouds
+   - 2 = Clouds
+   - 3 = Heavy clouds
+   - 4 = Clouds & rain
+   - 5 = Rain
+   - 6 = Thunderstorm
+   - 7 = Snow
+   - 8 = Mist, smog
+
+### Forecast
+
+The byte array must contain the following data:
+
+  - [0] : Message type = `1`
+  - [1] : Message version = `0`
+  - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time)
+  - [10] Number of days (Max 5, fields for unused days should be set to `0`)
+  - [11,12] Day 0 Minimum temperature (°C * 100)
+  - [13,14] Day 0 Maximum temperature (°C * 100)
+  - [15] Day 0 Icon ID
+  - [16,17] Day 1 Minimum temperature (°C * 100)
+  - [18,19] Day 1 Maximum temperature (°C * 100)
+  - [20] Day 1 Icon ID
+  - [21,22] Day 2 Minimum temperature (°C * 100)
+  - [23,24] Day 2 Maximum temperature (°C * 100)
+  - [25] Day 2 Icon ID
+  - [26,27] Day 3 Minimum temperature (°C * 100)
+  - [28,29] Day 3 Maximum temperature (°C * 100)
+  - [30] Day 3 Icon ID
+  - [31,32] Day 4 Minimum temperature (°C * 100)
+  - [33,34] Day 4 Maximum temperature (°C * 100)
+  - [35] Day 4 Icon ID




diff --git a/doc/ble.md b/doc/ble.md
index 9c170418caa096a3bd1a45f8e9e298c73b5390cc..2a4ecf4cb8eb9131ef220ae137a3dbda3f4d8407 100644
--- a/doc/ble.md
+++ b/doc/ble.md
@@ -92,7 +92,10 @@   - [Motion Service](MotionService.md): `00030000-78fc-48fe-8e23-433b3a1942d0`
 
 - Since InfiniTime 1.8:
 
-  - [Weather Service](/src/components/ble/weather/WeatherService.h): `00040000-78fc-48fe-8e23-433b3a1942d0`
+  - ~~Weather Service: `00040000-78fc-48fe-8e23-433b3a1942d0`~~ (replaced by Simple Weather Service in InfiniTime 1.14)
+
+- Since InfiniTime 1.14
+  - [Simple Weather Service](SimpleWeatherService.md) : `00050000-78fc-48fe-8e23-433b3a1942d0`
 
 ---
 




diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md
index 7909c59ccf7ec62d3f3a62fc2c71e13239572d7f..69e4bfd836efbaf44f1a569dae08852a3b69c4da 100644
--- a/doc/buildAndProgram.md
+++ b/doc/buildAndProgram.md
@@ -42,13 +42,23 @@ **ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-10.3-2021.10/`|
 **NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
 **CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
 **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
-**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1`
-**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
+**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [python3-pil/pillow](https://pillow.readthedocs.io) module). |`-DBUILD_RESOURCES=1`
+**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY_TFK5, MOY_TIN5, MOY_TON5, MOY_UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
 
 #### (\*) Note about **CMAKE_BUILD_TYPE**
 By default, this variable is set to *Release*. It compiles the code with size and speed optimizations. We use this value for all the binaries we publish when we [release](https://github.com/InfiniTimeOrg/InfiniTime/releases) new versions of InfiniTime.
 
-The *Debug* mode disables all optimizations, which makes the code easier to debug. However, the binary size will likely be too big to fit in the internal flash memory. If you want to build and debug a *Debug* binary, you'll need to disable some parts of the code. For example, the icons for the **Navigation** app use a lot of memory space. You can comment the content of `m_iconMap` in the [Navigation](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Navigation.h#L148) application to free some memory.
+The *Debug* mode disables all optimizations, which makes the code easier to debug. However, the binary size will likely be too big to fit in the internal flash memory. If you want to build and debug a *Debug* binary, you can disable some parts of the code that are not needed for the test you want to achieve. You can also apply the *Debug* mode selectively on parts of the application by applying the `DEBUG_FLAGS` only for the part (CMake target) you want to debug. For example, let's say you want to debug code related to LittleFS, simply set the compilation options for the RELEASE configuration of the target to `DEBUG_FLAGS` (in `src/CMakeLists.txt`). This will force the compilation of that target in *Debug* mode while the rest of the project will be built in *Release* mode. Example:
+
+```
+target_compile_options(littlefs PRIVATE
+        ${COMMON_FLAGS}
+        $<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
+        $<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}> # Change from RELEASE_FLAGS to DEBUG_FLAGS
+        $<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
+        $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
+        )
+```
 
 #### (\*\*) Note about **BUILD_DFU**
 DFU files are the files you'll need to install your build of InfiniTime using OTA (over-the-air) mechanism. To generate the DFU file, the Python tool [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) is needed on your system. Check that this tool is properly installed before enabling this option.
@@ -88,4 +98,4 @@ - **pinetime-mcuboot-app.map** : map file
 - **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
 - **pinetime-mcuboot-app-dfu** : DFU file of the firmware
 
-The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader**
+The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**




diff --git a/doc/buildWithDocker.md b/doc/buildWithDocker.md
index c0d425e5403a0bf2cb9ea2046dab07eb1a4ced24..670590a6455d44aeb1b6c650d9a4e24d9d99d54c 100644
--- a/doc/buildWithDocker.md
+++ b/doc/buildWithDocker.md
@@ -11,6 +11,16 @@ - MCUBoot
 - adafruit-nrfutil
 - lv_font_conv
 
+## Clone the repository
+
+Before building, local repository must be fully initialized.
+
+```
+git clone https://github.com/InfiniTimeOrg/InfiniTime.git
+cd InfiniTime
+git submodule update --init
+```
+
 ## Run a container to build the project
 
 The `infinitime-build` image contains all the dependencies you need.




diff --git a/doc/code/Apps.md b/doc/code/Apps.md
index cb7cd518034dbf10aa776f3cf55b0b63c0ce92af..b325fe98e1a6e2a673968bd01c887351a69b5927 100644
--- a/doc/code/Apps.md
+++ b/doc/code/Apps.md
@@ -9,59 +9,144 @@ ## 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.
+Every app in InfiniTime is its own class.
 An instance of the class is created when the app is launched, and destroyed when the user exits the app.
-Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
+Apps 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.
+Apps can be refreshed periodically and reacts to external events (touch or button).
 
 ## 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 function for reference.
+Every app class is declared inside the namespace `Pinetime::Applications::Screens` 
+and inherits
+from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
+
+Each app defines its own constructor.
+The constructors mostly take references to InfiniTime `Controllers` (ex: Alarm, DateTime, BLE services, Settings,...)
+the app needs for its operations. The constructor is responsible for initializing the UI of the app.
+
+The **destructor** cleans up LVGL and restores 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.
+
+Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
+that will call the method `Refresh()` periodically.
+
+## App types
+
+There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**.
+
+**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
+Settings, notifications and the application launcher are examples of such system applications.
+
+**User** applications are optionally built into the firmware. They extend the functionalities of the system.
+
+**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
+
+The distinction between **system** apps,  **user** apps and watch faces allows for more flexibility and customization.
+This allows to easily select which user applications and watch faces must be built into the firmware
+without overflowing the system memory.
+
+## Apps and watch faces initialization
+
+Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
+This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
+
+The constructor of **system** apps is called directly. If the application is a **user** app,
+the corresponding `AppDescription` is first retrieved from `userApps`
+and then the function `create` is called to create an instance of the app.
 
-### Continuous updating
+Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`.
 
-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.
+## User application selection at build time
 
-An example call could look like this:
+The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
+in `UserApps.h`. This method takes the list of applications that must be built into the firmware image.
+This list of applications is defined as a list `Apps` enum values named `UserAppTypes` in `Apps.h`.
+For each application listed in `UserAppTypes`, an entry of type `AppDescription` is added to the array `userApps`.
+This entry is created by using the information provided by a template `AppTraits`
+that is customized for every user application.
 
-```cpp
-taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
+Here is an example of an AppTraits customized for the Alarm application. 
+It defines the type of application, its icon and a function that returns an instance of the application.
+
+```c++
+template <>
+struct AppTraits<Apps::Alarm> {
+  static constexpr Apps app = Apps::Alarm;
+  static constexpr const char* icon = Screens::Symbols::clock;
+
+  static Screens::Screen* Create(AppControllers& controllers) {
+    return new Screens::Alarm(controllers.alarmController,
+                              controllers.settingsController.GetClockType(),
+                              *controllers.systemTask,
+                              controllers.motorController);
+  };
+};
 ```
 
-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.
+This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
+to list all available applications.
+
+## Watch face selection at build time
+
+The list of available watch faces is also generated at build time by the `consteval`
+function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps.
+Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
+Here is an example of `WatchFaceTraits`:
+```c++
+    template <>
+    struct WatchFaceTraits<WatchFace::Analog> {
+      static constexpr WatchFace watchFace = WatchFace::Analog;
+      static constexpr const char* name = "Analog face";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceAnalog(controllers.dateTimeController,
+                                            controllers.batteryController,
+                                            controllers.bleController,
+                                            controllers.notificationManager,
+                                            controllers.settingsController);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
+        return true;
+      }
+    };
+```
 
 ## Creating your own app
 
-A minimal app could look like this:
+A minimal user app could look like this:
 
 MyApp.h:
 
 ```cpp
 #pragma once
 
+#include "displayapp/Apps.h"
 #include "displayapp/screens/Screen.h"
-#include <lvgl/lvgl.h>
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
   namespace Applications {
     namespace Screens {
       class MyApp : public Screen {
       public:
-        MyApp(DisplayApp* app);
+        MyApp();
         ~MyApp() override;
       };
     }
+    
+    template <>
+    struct AppTraits<Apps:MyApp> {
+      static constexpr Apps app = Apps::MyApp;
+      static constexpr const char* icon = Screens::Symbol::myApp;
+      static Screens::Screens* Create(AppController& controllers) {
+        return new Screens::MyApp();
+      }
+    };
   }
 }
 ```
@@ -70,11 +155,10 @@ MyApp.cpp:
 
 ```cpp
 #include "displayapp/screens/MyApp.h"
-#include "displayapp/DisplayApp.h"
 
 using namespace Pinetime::Applications::Screens;
 
-MyApp::MyApp(DisplayApp* app) : Screen(app) {
+MyApp::MyApp() {
   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);
@@ -86,20 +170,42 @@   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.
+Both of these files should be in [displayapp/screens/](/src/displayapp/screens/).
 
 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)
+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.
+The next step to making it launch-able 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::LoadScreen` and add another case to the switch statement.
+Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
+to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
+
+If your application is a **system** application, go to the function `DisplayApp::LoadScreen`
+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.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h).
+If your application is a **user** application, you don't need to add anything in DisplayApp,
+everything will be automatically generated for you.
+The user application will also be automatically be added to the app launcher menu.
+
+Since the list of **user** application is generated by CMake, you need to add the variable `ENABLE_USERAPPS` to the command line of CMake.
+This variable must be set with a string composed of an ordered list of the **user** applications that must be built into the firmware.
+The items of the list are fields from the enumeration `Apps`.
+Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the application will be listed in this specific order in the application menu).
+
+```cmake
+$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ... 
+```
+
+Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake.
+It must be set with the comma separated list of watch faces that will be built into the firmware.
+
+Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat:
+
+```cmake
+$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ...
+```
 
 You should now be able to [build](../buildAndProgram.md) the firmware
 and flash it to your PineTime. Yay!




diff --git a/docker/Dockerfile b/docker/Dockerfile
index 927160dbebe60c37b17326665d1099353daabbfc..22bf7bd7ddefe9d73f7e7f8cc239a974200d836d 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,7 +1,13 @@
 FROM ubuntu:22.04
 
 ARG DEBIAN_FRONTEND=noninteractive
+ARG NODE_MAJOR=20
 RUN apt-get update -qq \
+    && apt-get install -y ca-certificates curl gnupg \
+    && mkdir -p /etc/apt/keyrings \
+    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
+    && apt-get update -qq \
     && apt-get install -y \
 # x86_64 / generic packages
       bash \
@@ -9,13 +15,14 @@       build-essential \
       cmake \
       git \
       make \
+      nodejs \
       python3 \
       python3-pip \
+      python3-pil \
       python-is-python3 \
       tar \
       unzip \
       wget \
-      curl \
       # aarch64 packages
       libffi-dev \
       libssl-dev \
@@ -28,8 +35,6 @@       libcairo2-dev \
       libpango-1.0-0 \
       ibpango1.0-dev \
       libpangocairo-1.0-0 \
-    && curl -sL https://deb.nodesource.com/setup_18.x | bash - \
-    && apt-get install -y nodejs \
     && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
 
 # Git needed for PROJECT_GIT_COMMIT_HASH variable setting
@@ -38,10 +43,6 @@ RUN pip3 install adafruit-nrfutil
 RUN pip3 install -Iv cryptography==3.3
 RUN pip3 install cbor
 RUN npm i lv_font_conv@1.5.2 -g
-
-RUN npm i ts-node@10.9.1 -g
-RUN npm i @swc/core -g
-RUN npm i lv_img_conv@0.3.0 -g
 
 # build.sh knows how to compile
 COPY build.sh /opt/




diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4d6663a56251dda6feba9df834b357a61ce435ee..eadd6ed6158c1e8f09c2d8ff92881f0f1f9aa40f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -355,14 +355,6 @@         libs/lvgl/src/lv_widgets/lv_tileview.c
         libs/lvgl/src/lv_widgets/lv_win.c
         )
 
-set(QCBOR_SRC
-        libs/QCBOR/src/ieee754.c
-        libs/QCBOR/src/qcbor_decode.c
-        libs/QCBOR/src/qcbor_encode.c
-        libs/QCBOR/src/qcbor_err_to_str.c
-        libs/QCBOR/src/UsefulBuf.c
-        )
-
 list(APPEND IMAGE_FILES
         displayapp/icons/battery/batteryicon.c
         )
@@ -373,7 +365,6 @@         BootloaderVersion.cpp
         logging/NrfLogger.cpp
         displayapp/DisplayApp.cpp
         displayapp/screens/Screen.cpp
-        displayapp/screens/Clock.cpp
         displayapp/screens/Tile.cpp
         displayapp/screens/InfiniPaint.cpp
         displayapp/screens/Paddle.cpp
@@ -385,7 +376,6 @@         displayapp/screens/SystemInfo.cpp
         displayapp/screens/Label.cpp
         displayapp/screens/FirmwareUpdate.cpp
         displayapp/screens/Music.cpp
-        displayapp/screens/Weather.cpp
         displayapp/screens/Navigation.cpp
         displayapp/screens/Metronome.cpp
         displayapp/screens/Motion.cpp
@@ -394,7 +384,6 @@         displayapp/screens/ApplicationList.cpp
         displayapp/screens/Notifications.cpp
         displayapp/screens/Twos.cpp
         displayapp/screens/HeartRate.cpp
-        displayapp/screens/Motion.cpp
         displayapp/screens/FlashLight.cpp
         displayapp/screens/List.cpp
         displayapp/screens/CheckboxList.cpp
@@ -416,6 +405,7 @@         displayapp/screens/settings/QuickSettings.cpp
         displayapp/screens/settings/Settings.cpp
         displayapp/screens/settings/SettingWatchFace.cpp
         displayapp/screens/settings/SettingTimeFormat.cpp
+        displayapp/screens/settings/SettingWeatherFormat.cpp
         displayapp/screens/settings/SettingWakeUp.cpp
         displayapp/screens/settings/SettingDisplay.cpp
         displayapp/screens/settings/SettingSteps.cpp
@@ -430,7 +420,6 @@         ## Watch faces
         displayapp/screens/WatchFaceDigital.cpp
         displayapp/screens/WatchFaceInfineat.cpp
         displayapp/screens/WatchFaceTerminal.cpp
-        displayapp/screens/WatchFacePineTimeStyle.cpp
         displayapp/screens/WatchFaceCasioStyleG7710.cpp
         displayapp/screens/WatchFaceMine.cpp
 
@@ -461,7 +450,7 @@         components/ble/DfuService.cpp
         components/ble/CurrentTimeService.cpp
         components/ble/AlertNotificationService.cpp
         components/ble/MusicService.cpp
-        components/ble/weather/WeatherService.cpp
+        components/ble/SimpleWeatherService.cpp
         components/ble/NavigationService.cpp
         components/ble/BatteryInformationService.cpp
         components/ble/FSService.cpp
@@ -493,6 +482,8 @@         components/heartrate/Ppg.cpp
 
         buttonhandler/ButtonHandler.cpp
         touchhandler/TouchHandler.cpp
+
+        utility/Math.cpp
         )
 
 list(APPEND RECOVERY_SOURCE_FILES
@@ -528,7 +519,7 @@         components/ble/DfuService.cpp
         components/ble/CurrentTimeService.cpp
         components/ble/AlertNotificationService.cpp
         components/ble/MusicService.cpp
-        components/ble/weather/WeatherService.cpp
+        components/ble/SimpleWeatherService.cpp
         components/ble/BatteryInformationService.cpp
         components/ble/FSService.cpp
         components/ble/ImmediateAlertService.cpp
@@ -558,6 +549,8 @@         components/motor/MotorController.cpp
         components/fs/FS.cpp
         buttonhandler/ButtonHandler.cpp
         touchhandler/TouchHandler.cpp
+
+        utility/Math.cpp
         )
 
 list(APPEND RECOVERYLOADER_SOURCE_FILES
@@ -594,7 +587,6 @@         displayapp/DisplayApp.h
         displayapp/Messages.h
         displayapp/TouchEvents.h
         displayapp/screens/Screen.h
-        displayapp/screens/Clock.h
         displayapp/screens/Tile.h
         displayapp/screens/InfiniPaint.h
         displayapp/screens/StopWatch.h
@@ -610,7 +602,6 @@         displayapp/screens/FirmwareValidation.h
         displayapp/screens/ApplicationList.h
         displayapp/screens/CheckboxList.h
         displayapp/Apps.h
-        displayapp/WatchFaces.h
         displayapp/screens/Notifications.h
         displayapp/screens/HeartRate.h
         displayapp/screens/Metronome.h
@@ -655,7 +646,7 @@         components/ble/ServiceDiscovery.h
         components/ble/BleClient.h
         components/ble/HeartRateService.h
         components/ble/MotionService.h
-        components/ble/weather/WeatherService.h
+        components/ble/SimpleWeatherService.h
         components/settings/Settings.h
         components/timer/Timer.h
         components/alarm/AlarmController.h
@@ -677,6 +668,7 @@         libs/arduinoFFT/src/types.h
         components/motor/MotorController.h
         buttonhandler/ButtonHandler.h
         touchhandler/TouchHandler.h
+        utility/Math.h
         )
 
 include_directories(
@@ -801,18 +793,18 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
 if(TARGET_DEVICE STREQUAL "PINETIME")
   add_definitions(-DDRIVER_PINMAP_PINETIME)
   add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
-elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
+elseif(TARGET_DEVICE STREQUAL "MOY_TFK5") # P8a
   add_definitions(-DDRIVER_PINMAP_P8)
   add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
-elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
+elseif(TARGET_DEVICE STREQUAL "MOY_TIN5") # P8a variant 2
   add_definitions(-DDRIVER_PINMAP_P8)
   add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
-elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
+elseif(TARGET_DEVICE STREQUAL "MOY_TON5") # P8b
   add_definitions(-DDRIVER_PINMAP_P8)
   add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
   add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
   add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
-elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
+elseif(TARGET_DEVICE STREQUAL "MOY_UNK") # P8b mirrored
   add_definitions(-DDRIVER_PINMAP_P8)
   add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
   add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
@@ -853,6 +845,8 @@         $<$: ${CXX_FLAGS}>
         $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
         )
 
+add_subdirectory(displayapp/apps)
+
 # NRF SDK
 add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
 target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
@@ -890,27 +884,6 @@         $<$: ${CXX_FLAGS}>
         $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
         )
 
-# QCBOR
-add_library(QCBOR STATIC ${QCBOR_SRC})
-target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc)
-# This is required with the current configuration
-target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE)
-# These are for space-saving
-target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT)
-target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA)
-target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS)
-#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS)
-target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS)
-target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN)
-set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C)
-target_compile_options(QCBOR PRIVATE
-        ${COMMON_FLAGS}
-        $<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
-        $<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
-        $<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
-        -O3
-        )
-
 # LITTLEFS_SRC
 add_library(littlefs STATIC ${LITTLEFS_SRC})
 target_include_directories(littlefs SYSTEM PUBLIC . ../)
@@ -929,7 +902,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
 add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
 set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
-target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
 target_compile_options(${EXECUTABLE_NAME} PUBLIC
         ${COMMON_FLAGS}
         ${WARNING_FLAGS}
@@ -963,7 +936,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
 set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
 add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
 set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
 target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
         ${COMMON_FLAGS}
@@ -1005,7 +978,7 @@ # InfiniTime recovery firmware (autonomous)
 set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
 set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
 set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
 target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
@@ -1037,7 +1010,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
 set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
 set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
 target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
 target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
@@ -1077,7 +1050,7 @@ # Build binary that writes the recovery image into the SPI flash memory
 set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
 set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
 set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
 target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
         ${COMMON_FLAGS}
@@ -1112,7 +1085,7 @@ set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
 set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex)
 set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
 add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
+target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
 set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
 target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
         ${COMMON_FLAGS}




diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index 8f1dfed7cda83e8ad6e6de5f62c4addf81857b77..29a395ea51f540d1b95b220a0ace02da1c501947 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -21,7 +21,7 @@ #include "components/ble/MusicService.h"
 #include "components/ble/NavigationService.h"
 #include "components/ble/ServiceDiscovery.h"
 #include "components/ble/MotionService.h"
-#include "components/ble/weather/WeatherService.h"
+#include "components/ble/SimpleWeatherService.h"
 #include "components/fs/FS.h"
 
 namespace Pinetime {
@@ -67,7 +67,7 @@       Pinetime::Controllers::AlertNotificationService& alertService() {
         return anService;
       };
 
-      Pinetime::Controllers::WeatherService& weather() {
+      Pinetime::Controllers::SimpleWeatherService& weather() {
         return weatherService;
       };
 
@@ -99,7 +99,7 @@       AlertNotificationService anService;
       AlertNotificationClient alertNotificationClient;
       CurrentTimeService currentTimeService;
       MusicService musicService;
-      WeatherService weatherService;
+      SimpleWeatherService weatherService;
       NavigationService navService;
       BatteryInformationService batteryInformationService;
       ImmediateAlertService immediateAlertService;




diff --git a/src/components/ble/NotificationManager.h b/src/components/ble/NotificationManager.h
index 09b5a5610edc67ad9bbf67f00a8e51884c4375d1..189a7e13555340325c6ac425485701be9fbfe2c0 100644
--- a/src/components/ble/NotificationManager.h
+++ b/src/components/ble/NotificationManager.h
@@ -27,11 +27,12 @@
       struct Notification {
         using Id = uint8_t;
         using Idx = uint8_t;
+
+        std::array<char, MessageSize + 1> message;
+        uint8_t size;
+        Categories category = Categories::Unknown;
         Id id = 0;
         bool valid = false;
-        uint8_t size;
-        std::array<char, MessageSize + 1> message;
-        Categories category = Categories::Unknown;
 
         const char* Message() const;
         const char* Title() const;




diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d545d45b7ce9a1c620c482a91e98b932f17a013d
--- /dev/null
+++ b/src/components/ble/SimpleWeatherService.cpp
@@ -0,0 +1,160 @@
+/*  Copyright (C) 2023 Jean-François Milants
+
+    This file is part of InfiniTime.
+
+    InfiniTime is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    InfiniTime is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "components/ble/SimpleWeatherService.h"
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <nrf_log.h>
+
+using namespace Pinetime::Controllers;
+
+namespace {
+  enum class MessageType : uint8_t { CurrentWeather, Forecast, Unknown };
+
+  uint64_t ToUInt64(const uint8_t* data) {
+    return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24) + (static_cast<uint64_t>(data[4]) << 32) +
+           (static_cast<uint64_t>(data[5]) << 40) + (static_cast<uint64_t>(data[6]) << 48) + (static_cast<uint64_t>(data[7]) << 56);
+  }
+
+  int16_t ToInt16(const uint8_t* data) {
+    return data[0] + (data[1] << 8);
+  }
+
+  SimpleWeatherService::CurrentWeather CreateCurrentWeather(const uint8_t* dataBuffer) {
+    SimpleWeatherService::Location cityName;
+    std::memcpy(cityName.data(), &dataBuffer[16], 32);
+    cityName[32] = '\0';
+    return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
+                                                ToInt16(&dataBuffer[10]),
+                                                ToInt16(&dataBuffer[12]),
+                                                ToInt16(&dataBuffer[14]),
+                                                SimpleWeatherService::Icons {dataBuffer[16 + 32]},
+                                                std::move(cityName));
+  }
+
+  SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
+    auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
+
+    std::array<SimpleWeatherService::Forecast::Day, SimpleWeatherService::MaxNbForecastDays> days;
+    const uint8_t nbDaysInBuffer = dataBuffer[10];
+    const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
+    for (int i = 0; i < nbDays; i++) {
+      days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]),
+                                                     ToInt16(&dataBuffer[13 + (i * 5)]),
+                                                     SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
+    }
+    return SimpleWeatherService::Forecast {timestamp, nbDays, days};
+  }
+
+  MessageType GetMessageType(const uint8_t* data) {
+    auto messageType = static_cast<MessageType>(*data);
+    if (messageType > MessageType::Unknown) {
+      return MessageType::Unknown;
+    }
+    return messageType;
+  }
+
+  uint8_t GetVersion(const uint8_t* dataBuffer) {
+    return dataBuffer[1];
+  }
+}
+
+int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+  return static_cast<Pinetime::Controllers::SimpleWeatherService*>(arg)->OnCommand(ctxt);
+}
+
+SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
+}
+
+void SimpleWeatherService::Init() {
+  ble_gatts_count_cfg(serviceDefinition);
+  ble_gatts_add_svcs(serviceDefinition);
+}
+
+int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
+  const auto* buffer = ctxt->om;
+  const auto* dataBuffer = buffer->om_data;
+
+  switch (GetMessageType(dataBuffer)) {
+    case MessageType::CurrentWeather:
+      if (GetVersion(dataBuffer) == 0) {
+        currentWeather = CreateCurrentWeather(dataBuffer);
+        NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s",
+                     currentWeather->timestamp,
+                     currentWeather->temperature,
+                     currentWeather->minTemperature,
+                     currentWeather->maxTemperature,
+                     currentWeather->iconId,
+                     currentWeather->location.data());
+      }
+      break;
+    case MessageType::Forecast:
+      if (GetVersion(dataBuffer) == 0) {
+        forecast = CreateForecast(dataBuffer);
+        NRF_LOG_INFO("Forecast : Timestamp : %d", forecast->timestamp);
+        for (int i = 0; i < 5; i++) {
+          NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d",
+                       i,
+                       forecast->days[i].minTemperature,
+                       forecast->days[i].maxTemperature,
+                       forecast->days[i].iconId);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+std::optional<SimpleWeatherService::CurrentWeather> SimpleWeatherService::Current() const {
+  if (currentWeather) {
+    auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
+    auto weatherTpSecond = std::chrono::seconds {currentWeather->timestamp};
+    auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
+    auto delta = currentTime - weatherTp;
+
+    if (delta < std::chrono::hours {24}) {
+      return currentWeather;
+    }
+  }
+  return {};
+}
+
+std::optional<SimpleWeatherService::Forecast> SimpleWeatherService::GetForecast() const {
+  if (forecast) {
+    auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch();
+    auto weatherTpSecond = std::chrono::seconds {forecast->timestamp};
+    auto weatherTp = std::chrono::duration_cast<std::chrono::seconds>(weatherTpSecond);
+    auto delta = currentTime - weatherTp;
+
+    if (delta < std::chrono::hours {24}) {
+      return this->forecast;
+    }
+  }
+  return {};
+}
+
+bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const {
+  return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp &&
+         this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
+         std::strcmp(this->location.data(), other.location.data()) == 0;
+}




diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h
new file mode 100644
index 0000000000000000000000000000000000000000..cec2bb8bb78c95dfe97c995012d312e8faabcd8a
--- /dev/null
+++ b/src/components/ble/SimpleWeatherService.h
@@ -0,0 +1,145 @@
+/*  Copyright (C) 2023 Jean-François Milants
+
+    This file is part of InfiniTime.
+
+    InfiniTime is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    InfiniTime is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#include <host/ble_uuid.h>
+#include <optional>
+#include <cstring>
+#undef max
+#undef min
+
+#include "components/datetime/DateTimeController.h"
+
+int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
+
+namespace Pinetime {
+  namespace Controllers {
+
+    class SimpleWeatherService {
+    public:
+      explicit SimpleWeatherService(const DateTime& dateTimeController);
+
+      void Init();
+
+      int OnCommand(struct ble_gatt_access_ctxt* ctxt);
+
+      static constexpr uint8_t MaxNbForecastDays = 5;
+
+      enum class Icons : uint8_t {
+        Sun = 0,       // ClearSky
+        CloudsSun = 1, // FewClouds
+        Clouds = 2,    // Scattered clouds
+        BrokenClouds = 3,
+        CloudShowerHeavy = 4, // shower rain
+        CloudSunRain = 5,     // rain
+        Thunderstorm = 6,
+        Snow = 7,
+        Smog = 8, // Mist
+        Unknown = 255
+      };
+
+      using Location = std::array<char, 33>; // 32 char + \0 (end of string)
+
+      struct CurrentWeather {
+        CurrentWeather(uint64_t timestamp,
+                       int16_t temperature,
+                       int16_t minTemperature,
+                       int16_t maxTemperature,
+                       Icons iconId,
+                       Location&& location)
+          : timestamp {timestamp},
+            temperature {temperature},
+            minTemperature {minTemperature},
+            maxTemperature {maxTemperature},
+            iconId {iconId},
+            location {std::move(location)} {
+        }
+
+        uint64_t timestamp;
+        int16_t temperature;
+        int16_t minTemperature;
+        int16_t maxTemperature;
+        Icons iconId;
+        Location location;
+
+        bool operator==(const CurrentWeather& other) const;
+      };
+
+      struct Forecast {
+        uint64_t timestamp;
+        uint8_t nbDays;
+
+        struct Day {
+          int16_t minTemperature;
+          int16_t maxTemperature;
+          Icons iconId;
+        };
+
+        std::array<Day, MaxNbForecastDays> days;
+      };
+
+      std::optional<CurrentWeather> Current() const;
+      std::optional<Forecast> GetForecast() const;
+
+      static int16_t CelsiusToFahrenheit(int16_t celsius) {
+        return celsius * 9 / 5 + 3200;
+      }
+
+    private:
+      // 00050000-78fc-48fe-8e23-433b3a1942d0
+      static constexpr ble_uuid128_t BaseUuid() {
+        return CharUuid(0x00, 0x00);
+      }
+
+      // 0005yyxx-78fc-48fe-8e23-433b3a1942d0
+      static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+        return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
+                              .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x05, 0x00}};
+      }
+
+      ble_uuid128_t weatherUuid {BaseUuid()};
+
+      ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
+
+      const struct ble_gatt_chr_def characteristicDefinition[2] = {{.uuid = &weatherDataCharUuid.u,
+                                                                    .access_cb = WeatherCallback,
+                                                                    .arg = this,
+                                                                    .flags = BLE_GATT_CHR_F_WRITE,
+                                                                    .val_handle = &eventHandle},
+                                                                   {0}};
+      const struct ble_gatt_svc_def serviceDefinition[2] = {
+        {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
+        {0}};
+
+      uint16_t eventHandle {};
+
+      const Pinetime::Controllers::DateTime& dateTimeController;
+
+      std::optional<CurrentWeather> currentWeather;
+      std::optional<Forecast> forecast;
+    };
+  }
+}




diff --git a/src/components/ble/weather/WeatherData.h b/src/components/ble/weather/WeatherData.h
deleted file mode 100644
index 1a995eb96f4c589ec37ec570d713b1ca83c49444..0000000000000000000000000000000000000000
--- a/src/components/ble/weather/WeatherData.h
+++ /dev/null
@@ -1,385 +0,0 @@
-/*  Copyright (C) 2021 Avamander
-
-    This file is part of InfiniTime.
-
-    InfiniTime is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published
-    by the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    InfiniTime is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-#pragma once
-
-/**
- * Different weather events, weather data structures used by {@link WeatherService.h}
- *
- * How to upload events to the timeline?
- *
- * All timeline write payloads are simply CBOR-encoded payloads of the structs described below.
- *
- * All payloads have a mandatory header part and the dynamic part that
- * depends on the event type specified in the header. If you don't,
- * you'll get an error returned. Data is relatively well-validated,
- * so keep in the bounds of the data types given.
- *
- * Write all struct members (CamelCase keys) into a single finite-sized map, and write it to the characteristic.
- * Mind the MTU.
- *
- * How to debug?
- *
- * There's a Screen that you can compile into your firmware that shows currently valid events.
- * You can adapt that to display something else. That part right now is very much work in progress
- * because the exact requirements are not yet known.
- *
- *
- * Implemented based on and other material:
- * https://en.wikipedia.org/wiki/METAR
- * https://www.weather.gov/jetstream/obscurationtypes
- * http://www.faraim.org/aim/aim-4-03-14-493.html
- */
-
-namespace Pinetime {
-  namespace Controllers {
-    class WeatherData {
-    public:
-      /**
-       * Visibility obscuration types
-       */
-      enum class obscurationtype {
-        /** No obscuration */
-        None = 0,
-        /** Water particles suspended in the air; low visibility; does not fall */
-        Fog = 1,
-        /** Tiny, dry particles in the air; invisible to the eye; opalescent */
-        Haze = 2,
-        /** Small fire-created particles suspended in the air */
-        Smoke = 3,
-        /** Fine rock powder, from for example volcanoes */
-        Ash = 4,
-        /** Fine particles of earth suspended in the air by the wind */
-        Dust = 5,
-        /** Fine particles of sand suspended in the air by the wind */
-        Sand = 6,
-        /** Water particles suspended in the air; low-ish visibility; temperature is near dewpoint */
-        Mist = 7,
-        /** This is SPECIAL in the sense that the thing raining down is doing the obscuration */
-        Precipitation = 8,
-        Length
-      };
-
-      /**
-       * Types of precipitation
-       */
-      enum class precipitationtype {
-        /**
-         * No precipitation
-         *
-         * Theoretically we could just _not_ send the event, but then
-         * how do we differentiate between no precipitation and
-         * no information about precipitation
-         */
-        None = 0,
-        /** Drops larger than a drizzle; also widely separated drizzle */
-        Rain = 1,
-        /** Fairly uniform rain consisting of fine drops */
-        Drizzle = 2,
-        /** Rain that freezes upon contact with objects and ground */
-        FreezingRain = 3,
-        /** Rain + hail; ice pellets; small translucent frozen raindrops */
-        Sleet = 4,
-        /** Larger ice pellets; falling separately or in irregular clumps */
-        Hail = 5,
-        /** Hail with smaller grains of ice; mini-snowballs */
-        SmallHail = 6,
-        /** Snow... */
-        Snow = 7,
-        /** Frozen drizzle; very small snow crystals */
-        SnowGrains = 8,
-        /** Needles; columns or plates of ice. Sometimes described as "diamond dust". In very cold regions */
-        IceCrystals = 9,
-        /** It's raining down ash, e.g. from a volcano */
-        Ash = 10,
-        Length
-      };
-
-      /**
-       * These are special events that can "enhance" the "experience" of existing weather events
-       */
-      enum class specialtype {
-        /** Strong wind with a sudden onset that lasts at least a minute */
-        Squall = 0,
-        /** Series of waves in a water body caused by the displacement of a large volume of water */
-        Tsunami = 1,
-        /** Violent; rotating column of air */
-        Tornado = 2,
-        /** Unplanned; unwanted; uncontrolled fire in an area */
-        Fire = 3,
-        /** Thunder and/or lightning */
-        Thunder = 4,
-        Length
-      };
-
-      /**
-       * These are used for weather timeline manipulation
-       * that isn't just adding to the stack of weather events
-       */
-      enum class controlcodes {
-        /** How much is stored already */
-        GetLength = 0,
-        /** This wipes the entire timeline */
-        DelTimeline = 1,
-        /** There's a currently valid timeline event with the given type */
-        HasValidEvent = 3,
-        Length
-      };
-
-      /**
-       * Events have types
-       * then they're easier to parse after sending them over the air
-       */
-      enum class eventtype : uint8_t {
-        /** @see obscuration */
-        Obscuration = 0,
-        /** @see precipitation */
-        Precipitation = 1,
-        /** @see wind */
-        Wind = 2,
-        /** @see temperature */
-        Temperature = 3,
-        /** @see airquality */
-        AirQuality = 4,
-        /** @see special */
-        Special = 5,
-        /** @see pressure */
-        Pressure = 6,
-        /** @see location */
-        Location = 7,
-        /** @see cloud */
-        Clouds = 8,
-        /** @see humidity */
-        Humidity = 9,
-        Length
-      };
-
-      /**
-       * Valid event query
-       *
-       * NOTE: Not currently available, until needs are better known
-       */
-      class ValidEventQuery {
-      public:
-        static constexpr controlcodes code = controlcodes::HasValidEvent;
-        eventtype eventType;
-      };
-
-      /** The header used for further parsing */
-      class TimelineHeader {
-      public:
-        /**
-         * UNIX timestamp
-         * TODO: This is currently WITH A TIMEZONE OFFSET!
-         * Please send events with the timestamp offset by the timezone.
-         **/
-        uint64_t timestamp;
-        /**
-         * Time in seconds until the event expires
-         *
-         * 32 bits ought to be enough for everyone
-         *
-         * If there's a newer event of the same type then it overrides this one, even if it hasn't expired
-         */
-        uint32_t expires;
-        /**
-         * What type of weather-related event
-         */
-        eventtype eventType;
-      };
-
-      /** Specifies how cloudiness is stored */
-      class Clouds : public TimelineHeader {
-      public:
-        /** Cloud coverage in percentage, 0-100% */
-        uint8_t amount;
-      };
-
-      /** Specifies how obscuration is stored */
-      class Obscuration : public TimelineHeader {
-      public:
-        /** Type of precipitation */
-        obscurationtype type;
-        /**
-         * Visibility distance in meters
-         * 65535 is reserved for unspecified
-         */
-        uint16_t amount;
-      };
-
-      /** Specifies how precipitation is stored */
-      class Precipitation : public TimelineHeader {
-      public:
-        /** Type of precipitation */
-        precipitationtype type;
-        /**
-         * How much is it going to rain? In millimeters
-         * 255 is reserved for unspecified
-         **/
-        uint8_t amount;
-      };
-
-      /**
-       * How wind speed is stored
-       *
-       * In order to represent bursts of wind instead of constant wind,
-       * you have minimum and maximum speeds.
-       *
-       * As direction can fluctuate wildly and some watch faces might wish to display it nicely,
-       * we're following the aerospace industry weather report option of specifying a range.
-       */
-      class Wind : public TimelineHeader {
-      public:
-        /** Meters per second */
-        uint8_t speedMin;
-        /** Meters per second */
-        uint8_t speedMax;
-        /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
-        uint8_t directionMin;
-        /** Unitless direction between 0-255; approximately 1 unit per 0.71 degrees */
-        uint8_t directionMax;
-      };
-
-      /**
-       * How temperature is stored
-       *
-       * As it's annoying to figure out the dewpoint on the watch,
-       * please send it from the companion
-       *
-       * We don't do floats, picodegrees are not useful. Make sure to multiply.
-       */
-      class Temperature : public TimelineHeader {
-      public:
-        /**
-         * Temperature °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
-         * -32768 is reserved for "no data"
-         */
-        int16_t temperature;
-        /**
-         * Dewpoint °C but multiplied by 100 (e.g. -12.50°C becomes -1250)
-         * -32768 is reserved for "no data"
-         */
-        int16_t dewPoint;
-      };
-
-      /**
-       * How location info is stored
-       *
-       * This can be mostly static with long expiration,
-       * as it usually is, but it could change during a trip for ex.
-       * so we allow changing it dynamically.
-       *
-       * Location info can be for some kind of map watch face
-       * or daylight calculations, should those be required.
-       *
-       */
-      class Location : public TimelineHeader {
-      public:
-        /** Location name */
-        std::string location;
-        /** Altitude relative to sea level in meters */
-        int16_t altitude;
-        /** Latitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
-        int32_t latitude;
-        /** Longitude, EPSG:3857 (Google Maps, Openstreetmaps datum) */
-        int32_t longitude;
-      };
-
-      /**
-       * How humidity is stored
-       */
-      class Humidity : public TimelineHeader {
-      public:
-        /** Relative humidity, 0-100% */
-        uint8_t humidity;
-      };
-
-      /**
-       * How air pressure is stored
-       */
-      class Pressure : public TimelineHeader {
-      public:
-        /** Air pressure in hectopascals (hPa) */
-        int16_t pressure;
-      };
-
-      /**
-       * How special events are stored
-       */
-      class Special : public TimelineHeader {
-      public:
-        /** Special event's type */
-        specialtype type;
-      };
-
-      /**
-       * How air quality is stored
-       *
-       * These events are a bit more complex because the topic is not simple,
-       * the intention is to heavy-lift the annoying preprocessing from the watch
-       * this allows watch face or watchapp makers to generate accurate alerts and graphics
-       *
-       * If this needs further enforced standardization, pull requests are welcome
-       */
-      class AirQuality : public TimelineHeader {
-      public:
-        /**
-         * The name of the pollution
-         *
-         * for the sake of better compatibility with watchapps
-         * that might want to use this data for say visuals
-         * don't localize the name.
-         *
-         * Ideally watchapp itself localizes the name, if it's at all needed.
-         *
-         * E.g.
-         * For generic ones use "PM0.1", "PM5", "PM10"
-         * For chemical compounds use the molecular formula e.g. "NO2", "CO2", "O3"
-         * For pollen use the genus, e.g. "Betula" for birch or "Alternaria" for that mold's spores
-         */
-        std::string polluter;
-        /**
-         * Amount of the pollution in SI units,
-         * otherwise it's going to be difficult to create UI, alerts
-         * and so on and for.
-         *
-         * See more:
-         * https://ec.europa.eu/environment/air/quality/standards.htm
-         * http://www.ourair.org/wp-content/uploads/2012-aaqs2.pdf
-         *
-         * Example units:
-         * count/m³ for pollen
-         * µgC/m³ for micrograms of organic carbon
-         * µg/m³ sulfates, PM0.1, PM1, PM2, PM10 and so on, dust
-         * mg/m³ CO2, CO
-         * ng/m³ for heavy metals
-         *
-         * List is not comprehensive, should be improved.
-         * The current ones are what watchapps assume!
-         *
-         * Note: ppb and ppm to concentration should be calculated on the companion, using
-         * the correct formula (taking into account temperature and air pressure)
-         *
-         * Note2: The amount is off by times 100, for two decimal places of precision.
-         * E.g. 54.32µg/m³ is 5432
-         *
-         */
-        uint32_t amount;
-      };
-    };
-  }
-}




diff --git a/src/components/ble/weather/WeatherService.cpp b/src/components/ble/weather/WeatherService.cpp
deleted file mode 100644
index 513bb2a16ba81d1e36546a2ff9d9ffca2434f443..0000000000000000000000000000000000000000
--- a/src/components/ble/weather/WeatherService.cpp
+++ /dev/null
@@ -1,605 +0,0 @@
-/*  Copyright (C) 2021 Avamander
-
-    This file is part of InfiniTime.
-
-    InfiniTime is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published
-    by the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    InfiniTime is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-#include <algorithm>
-#include <qcbor/qcbor_spiffy_decode.h>
-#include "WeatherService.h"
-#include "libs/QCBOR/inc/qcbor/qcbor.h"
-
-int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble_gatt_access_ctxt* ctxt, void* arg) {
-  return static_cast<Pinetime::Controllers::WeatherService*>(arg)->OnCommand(ctxt);
-}
-
-namespace Pinetime {
-  namespace Controllers {
-    WeatherService::WeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
-      nullHeader = &nullTimelineheader;
-      nullTimelineheader->timestamp = 0;
-    }
-
-    void WeatherService::Init() {
-      uint8_t res = 0;
-      res = ble_gatts_count_cfg(serviceDefinition);
-      ASSERT(res == 0);
-
-      res = ble_gatts_add_svcs(serviceDefinition);
-      ASSERT(res == 0);
-    }
-
-    int WeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
-      if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
-        const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
-        if (packetLen <= 0) {
-          return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-        }
-        // Decode
-        QCBORDecodeContext decodeContext;
-        UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
-
-        QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
-        // KINDLY provide us a fixed-length map
-        QCBORDecode_EnterMap(&decodeContext, nullptr);
-        // Always encodes to the smallest number of bytes based on the value
-        int64_t tmpTimestamp = 0;
-        QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
-        if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
-          CleanUpQcbor(&decodeContext);
-          return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-        }
-        int64_t tmpExpires = 0;
-        QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
-        if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
-          CleanUpQcbor(&decodeContext);
-          return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-        }
-        int64_t tmpEventType = 0;
-        QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
-        if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
-            tmpEventType >= static_cast<int64_t>(WeatherData::eventtype::Length)) {
-          CleanUpQcbor(&decodeContext);
-          return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-        }
-
-        switch (static_cast<WeatherData::eventtype>(tmpEventType)) {
-          case WeatherData::eventtype::AirQuality: {
-            std::unique_ptr<WeatherData::AirQuality> airquality = std::make_unique<WeatherData::AirQuality>();
-            airquality->timestamp = tmpTimestamp;
-            airquality->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            airquality->expires = tmpExpires;
-
-            UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
-            QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
-            if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            airquality->polluter = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
-
-            int64_t tmpAmount = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
-            if (tmpAmount < 0 || tmpAmount > 4294967295) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(airquality))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Obscuration: {
-            std::unique_ptr<WeatherData::Obscuration> obscuration = std::make_unique<WeatherData::Obscuration>();
-            obscuration->timestamp = tmpTimestamp;
-            obscuration->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            obscuration->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::obscurationtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            obscuration->type = static_cast<WeatherData::obscurationtype>(tmpType);
-
-            int64_t tmpAmount = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
-            if (tmpAmount < 0 || tmpAmount > 65535) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(obscuration))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Precipitation: {
-            std::unique_ptr<WeatherData::Precipitation> precipitation = std::make_unique<WeatherData::Precipitation>();
-            precipitation->timestamp = tmpTimestamp;
-            precipitation->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            precipitation->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::precipitationtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            precipitation->type = static_cast<WeatherData::precipitationtype>(tmpType);
-
-            int64_t tmpAmount = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
-            if (tmpAmount < 0 || tmpAmount > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(precipitation))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Wind: {
-            std::unique_ptr<WeatherData::Wind> wind = std::make_unique<WeatherData::Wind>();
-            wind->timestamp = tmpTimestamp;
-            wind->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            wind->expires = tmpExpires;
-
-            int64_t tmpMin = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
-            if (tmpMin < 0 || tmpMin > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            int64_t tmpMax = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
-            if (tmpMax < 0 || tmpMax > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            int64_t tmpDMin = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
-            if (tmpDMin < 0 || tmpDMin > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            int64_t tmpDMax = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
-            if (tmpDMax < 0 || tmpDMax > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(wind))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Temperature: {
-            std::unique_ptr<WeatherData::Temperature> temperature = std::make_unique<WeatherData::Temperature>();
-            temperature->timestamp = tmpTimestamp;
-            temperature->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            temperature->expires = tmpExpires;
-
-            int64_t tmpTemperature = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
-            if (tmpTemperature < -32768 || tmpTemperature > 32767) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            temperature->temperature =
-              static_cast<int16_t>(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            int64_t tmpDewPoint = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
-            if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            temperature->dewPoint =
-              static_cast<int16_t>(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(temperature))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Special: {
-            std::unique_ptr<WeatherData::Special> special = std::make_unique<WeatherData::Special>();
-            special->timestamp = tmpTimestamp;
-            special->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            special->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
-            if (tmpType < 0 || tmpType >= static_cast<int64_t>(WeatherData::specialtype::Length)) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            special->type = static_cast<WeatherData::specialtype>(tmpType);
-
-            if (!AddEventToTimeline(std::move(special))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Pressure: {
-            std::unique_ptr<WeatherData::Pressure> pressure = std::make_unique<WeatherData::Pressure>();
-            pressure->timestamp = tmpTimestamp;
-            pressure->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            pressure->expires = tmpExpires;
-
-            int64_t tmpPressure = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
-            if (tmpPressure < 0 || tmpPressure >= 65535) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-
-            if (!AddEventToTimeline(std::move(pressure))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Location: {
-            std::unique_ptr<WeatherData::Location> location = std::make_unique<WeatherData::Location>();
-            location->timestamp = tmpTimestamp;
-            location->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            location->expires = tmpExpires;
-
-            UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
-            QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
-            if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            location->location = std::string(static_cast<const char*>(stringBuf.ptr), stringBuf.len);
-
-            int64_t tmpAltitude = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
-            if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            location->altitude = static_cast<int16_t>(tmpAltitude);
-
-            int64_t tmpLatitude = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
-            if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            location->latitude = static_cast<int32_t>(tmpLatitude);
-
-            int64_t tmpLongitude = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
-            if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            location->latitude = static_cast<int32_t>(tmpLongitude);
-
-            if (!AddEventToTimeline(std::move(location))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Clouds: {
-            std::unique_ptr<WeatherData::Clouds> clouds = std::make_unique<WeatherData::Clouds>();
-            clouds->timestamp = tmpTimestamp;
-            clouds->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            clouds->expires = tmpExpires;
-
-            int64_t tmpAmount = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
-            if (tmpAmount < 0 || tmpAmount > 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            clouds->amount = static_cast<uint8_t>(tmpAmount);
-
-            if (!AddEventToTimeline(std::move(clouds))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          case WeatherData::eventtype::Humidity: {
-            std::unique_ptr<WeatherData::Humidity> humidity = std::make_unique<WeatherData::Humidity>();
-            humidity->timestamp = tmpTimestamp;
-            humidity->eventType = static_cast<WeatherData::eventtype>(tmpEventType);
-            humidity->expires = tmpExpires;
-
-            int64_t tmpType = 0;
-            QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
-            if (tmpType < 0 || tmpType >= 255) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            humidity->humidity = static_cast<uint8_t>(tmpType);
-
-            if (!AddEventToTimeline(std::move(humidity))) {
-              CleanUpQcbor(&decodeContext);
-              return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-            }
-            break;
-          }
-          default: {
-            CleanUpQcbor(&decodeContext);
-            return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
-          }
-        }
-
-        QCBORDecode_ExitMap(&decodeContext);
-        GetTimelineLength();
-        TidyTimeline();
-
-        if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
-          return BLE_ATT_ERR_INSUFFICIENT_RES;
-        }
-      } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
-        // Encode
-        uint8_t buffer[64];
-        QCBOREncodeContext encodeContext;
-        /* TODO: This is very much still a test endpoint
-         *  it needs a characteristic UUID check
-         *  and actual implementations that show
-         *  what actually has to be read.
-         *  WARN: Consider commands not part of the API for now!
-         */
-        QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
-        QCBOREncode_OpenMap(&encodeContext);
-        QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
-        QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
-        QCBOREncode_CloseMap(&encodeContext);
-
-        UsefulBufC encodedEvent;
-        auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
-        if (uErr != 0) {
-          return BLE_ATT_ERR_INSUFFICIENT_RES;
-        }
-        auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
-        if (res == 0) {
-          return BLE_ATT_ERR_INSUFFICIENT_RES;
-        }
-
-        return 0;
-      }
-      return 0;
-    }
-
-    std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(*this->nullHeader);
-    }
-
-    std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
-          return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
-        }
-      }
-
-      return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(*this->nullHeader);
-    }
-
-    size_t WeatherService::GetTimelineLength() const {
-      return timeline.size();
-    }
-
-    bool WeatherService::AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event) {
-      if (timeline.size() == timeline.max_size()) {
-        return false;
-      }
-
-      timeline.push_back(std::move(event));
-      return true;
-    }
-
-    bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      for (auto&& header : timeline) {
-        if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) {
-          return true;
-        }
-      }
-      return false;
-    }
-
-    void WeatherService::TidyTimeline() {
-      uint64_t timeCurrent = GetCurrentUnixTimestamp();
-      timeline.erase(std::remove_if(std::begin(timeline),
-                                    std::end(timeline),
-                                    [&](std::unique_ptr<WeatherData::TimelineHeader> const& header) {
-                                      return !IsEventStillValid(header, timeCurrent);
-                                    }),
-                     std::end(timeline));
-
-      std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents);
-    }
-
-    bool WeatherService::CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
-                                               const std::unique_ptr<WeatherData::TimelineHeader>& second) {
-      return first->timestamp > second->timestamp;
-    }
-
-    bool WeatherService::IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp) {
-      // Not getting timestamp in isEventStillValid for more speed
-      return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
-    }
-
-    uint64_t WeatherService::GetCurrentUnixTimestamp() const {
-      return std::chrono::duration_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime().time_since_epoch()).count();
-    }
-
-    int16_t WeatherService::GetTodayMinTemp() const {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
-                               ((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
-      uint64_t currentDayStart = currentDayEnd - 86400;
-      int16_t result = -32768;
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
-            header->timestamp < currentDayEnd &&
-            reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
-          int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
-          if (result == -32768) {
-            result = temperature;
-          } else if (result > temperature) {
-            result = temperature;
-          } else {
-            // The temperature in this item is higher than the lowest we've found
-          }
-        }
-      }
-
-      return result;
-    }
-
-    int16_t WeatherService::GetTodayMaxTemp() const {
-      uint64_t currentTimestamp = GetCurrentUnixTimestamp();
-      uint64_t currentDayEnd = currentTimestamp + ((24 - dateTimeController.Hours()) * 60 * 60) +
-                               ((60 - dateTimeController.Minutes()) * 60) + (60 - dateTimeController.Seconds());
-      uint64_t currentDayStart = currentDayEnd - 86400;
-      int16_t result = -32768;
-      for (auto&& header : this->timeline) {
-        if (header->eventType == WeatherData::eventtype::Temperature && header->timestamp >= currentDayStart &&
-            header->timestamp < currentDayEnd &&
-            reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature != -32768) {
-          int16_t temperature = reinterpret_cast<const std::unique_ptr<WeatherData::Temperature>&>(header)->temperature;
-          if (result == -32768) {
-            result = temperature;
-          } else if (result < temperature) {
-            result = temperature;
-          } else {
-            // The temperature in this item is lower than the highest we've found
-          }
-        }
-      }
-
-      return result;
-    }
-
-    void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
-      QCBORDecode_ExitMap(decodeContext);
-      QCBORDecode_Finish(decodeContext);
-    }
-  }
-}




diff --git a/src/components/ble/weather/WeatherService.h b/src/components/ble/weather/WeatherService.h
deleted file mode 100644
index 609e8760d60021c564a9489430f67191272e5380..0000000000000000000000000000000000000000
--- a/src/components/ble/weather/WeatherService.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/*  Copyright (C) 2021 Avamander
-
-    This file is part of InfiniTime.
-
-    InfiniTime is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published
-    by the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    InfiniTime is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-#pragma once
-
-#include <cstdint>
-#include <string>
-#include <vector>
-#include <memory>
-
-#define min // workaround: nimble's min/max macros conflict with libstdc++
-#define max
-#include <host/ble_gap.h>
-#include <host/ble_uuid.h>
-#undef max
-#undef min
-
-#include "WeatherData.h"
-#include "libs/QCBOR/inc/qcbor/qcbor.h"
-#include "components/datetime/DateTimeController.h"
-
-int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
-
-namespace Pinetime {
-  namespace Controllers {
-
-    class WeatherService {
-    public:
-      explicit WeatherService(const DateTime& dateTimeController);
-
-      void Init();
-
-      int OnCommand(struct ble_gatt_access_ctxt* ctxt);
-
-      /*
-       * Helper functions for quick access to currently valid data
-       */
-      std::unique_ptr<WeatherData::Location>& GetCurrentLocation();
-      std::unique_ptr<WeatherData::Clouds>& GetCurrentClouds();
-      std::unique_ptr<WeatherData::Obscuration>& GetCurrentObscuration();
-      std::unique_ptr<WeatherData::Precipitation>& GetCurrentPrecipitation();
-      std::unique_ptr<WeatherData::Wind>& GetCurrentWind();
-      std::unique_ptr<WeatherData::Temperature>& GetCurrentTemperature();
-      std::unique_ptr<WeatherData::Humidity>& GetCurrentHumidity();
-      std::unique_ptr<WeatherData::Pressure>& GetCurrentPressure();
-      std::unique_ptr<WeatherData::AirQuality>& GetCurrentQuality();
-
-      /**
-       * Searches for the current day's maximum temperature
-       * @return -32768 if there's no data, degrees Celsius times 100 otherwise
-       */
-      int16_t GetTodayMaxTemp() const;
-      /**
-       * Searches for the current day's minimum temperature
-       * @return -32768 if there's no data, degrees Celsius times 100 otherwise
-       */
-      int16_t GetTodayMinTemp() const;
-
-      /*
-       * Management functions
-       */
-      /**
-       * Adds an event to the timeline
-       * @return
-       */
-      bool AddEventToTimeline(std::unique_ptr<WeatherData::TimelineHeader> event);
-      /**
-       * Gets the current timeline length
-       */
-      size_t GetTimelineLength() const;
-      /**
-       * Checks if an event of a certain type exists in the timeline
-       */
-      bool HasTimelineEventOfType(WeatherData::eventtype type) const;
-
-    private:
-      // 00040000-78fc-48fe-8e23-433b3a1942d0
-      static constexpr ble_uuid128_t BaseUuid() {
-        return CharUuid(0x00, 0x00);
-      }
-
-      // 0004yyxx-78fc-48fe-8e23-433b3a1942d0
-      static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
-        return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
-                              .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
-      }
-
-      ble_uuid128_t weatherUuid {BaseUuid()};
-
-      /**
-       * Just write timeline data here.
-       *
-       * See {@link WeatherData.h} for more information.
-       */
-      ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
-      /**
-       * This doesn't take timeline data, provides some control over it.
-       *
-       * NOTE: Currently not supported. Companion app implementer feedback required.
-       * There's very little point in solidifying an API before we know the needs.
-       */
-      ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
-
-      const struct ble_gatt_chr_def characteristicDefinition[3] = {
-        {.uuid = &weatherDataCharUuid.u,
-         .access_cb = WeatherCallback,
-         .arg = this,
-         .flags = BLE_GATT_CHR_F_WRITE,
-         .val_handle = &eventHandle},
-        {.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
-        {nullptr}};
-      const struct ble_gatt_svc_def serviceDefinition[2] = {
-        {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition},
-        {0}};
-
-      uint16_t eventHandle {};
-
-      const Pinetime::Controllers::DateTime& dateTimeController;
-
-      std::vector<std::unique_ptr<WeatherData::TimelineHeader>> timeline;
-      std::unique_ptr<WeatherData::TimelineHeader> nullTimelineheader = std::make_unique<WeatherData::TimelineHeader>();
-      std::unique_ptr<WeatherData::TimelineHeader>* nullHeader;
-
-      /**
-       * Cleans up the timeline of expired events
-       */
-      void TidyTimeline();
-
-      /**
-       * Compares two timeline events
-       */
-      static bool CompareTimelineEvents(const std::unique_ptr<WeatherData::TimelineHeader>& first,
-                                        const std::unique_ptr<WeatherData::TimelineHeader>& second);
-
-      /**
-       * Returns current UNIX timestamp
-       */
-      uint64_t GetCurrentUnixTimestamp() const;
-
-      /**
-       * Checks if the event hasn't gone past and expired
-       *
-       * @param header timeline event to check
-       * @param currentTimestamp what's the time right now
-       * @return if the event is valid
-       */
-      static bool IsEventStillValid(const std::unique_ptr<WeatherData::TimelineHeader>& uniquePtr, const uint64_t timestamp);
-
-      /**
-       * This is a helper function that closes a QCBOR map and decoding context cleanly
-       */
-      void CleanUpQcbor(QCBORDecodeContext* decodeContext);
-    };
-  }
-}




diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp
index 24af4f2c2041020a75fc3eb005c6ae5d8769afba..1aed496364542254e6811dd1f90db196764d500c 100644
--- a/src/components/datetime/DateTimeController.cpp
+++ b/src/components/datetime/DateTimeController.cpp
@@ -177,9 +177,9 @@     } else {
       hour12 = (hour == 12) ? 12 : hour - 12;
       amPmStr = "PM";
     }
-    sprintf(buff, "%i:%02i %s", hour12, minute, amPmStr);
+    snprintf(buff, sizeof(buff), "%i:%02i %s", hour12, minute, amPmStr);
   } else {
-    sprintf(buff, "%02i:%02i", hour, minute);
+    snprintf(buff, sizeof(buff), "%02i:%02i", hour, minute);
   }
   return std::string(buff);
 }




diff --git a/src/components/gfx/Gfx.cpp b/src/components/gfx/Gfx.cpp
index 3eaaa3fecc1a5c709acdeb799dfe439cc4906f6d..baa6486a4a11b6dd5bd2e0d825df935e1a95c64f 100644
--- a/src/components/gfx/Gfx.cpp
+++ b/src/components/gfx/Gfx.cpp
@@ -141,7 +141,7 @@
 bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
   if (!state.busy)
     return false;
-  state.remainingIterations--;
+  state.remainingIterations = state.remainingIterations - 1;
   if (state.remainingIterations == 0) {
     state.busy = false;
     NotifyEndOfTransfer(state.taskToNotify);
@@ -170,7 +170,7 @@     *data = reinterpret_cast(buffer);
     size = bytes_in_line * 8 * 2;
   }
 
-  state.currentIteration++;
+  state.currentIteration = state.currentIteration + 1;
 
   return true;
 }




diff --git a/src/components/motion/MotionController.cpp b/src/components/motion/MotionController.cpp
index ef3cf811889e95aafa6f5bd4f1e344301cdf2803..d28378d5422f89b2e75e8933a4bd12817bbe4891 100644
--- a/src/components/motion/MotionController.cpp
+++ b/src/components/motion/MotionController.cpp
@@ -2,14 +2,45 @@ #include "components/motion/MotionController.h"
 
 #include <task.h>
 
+#include "utility/Math.h"
+
 using namespace Pinetime::Controllers;
+
+namespace {
+  constexpr inline int32_t Clamp(int32_t val, int32_t min, int32_t max) {
+    return val < min ? min : (val > max ? max : val);
+  }
+
+  // only returns meaningful values if inputs are acceleration due to gravity
+  int16_t DegreesRolled(int16_t y, int16_t z, int16_t prevY, int16_t prevZ) {
+    int16_t prevYAngle = Pinetime::Utility::Asin(Clamp(prevY * 32, -32767, 32767));
+    int16_t yAngle = Pinetime::Utility::Asin(Clamp(y * 32, -32767, 32767));
+
+    if (z < 0 && prevZ < 0) {
+      return yAngle - prevYAngle;
+    }
+    if (prevZ < 0) {
+      if (y < 0) {
+        return -prevYAngle - yAngle - 180;
+      }
+      return -prevYAngle - yAngle + 180;
+    }
+    if (z < 0) {
+      if (y < 0) {
+        return prevYAngle + yAngle + 180;
+      }
+      return prevYAngle + yAngle - 180;
+    }
+    return prevYAngle - yAngle;
+  }
+}
 
 void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) {
   if (this->nbSteps != nbSteps && service != nullptr) {
     service->OnNewStepCountValue(nbSteps);
   }
 
-  if (service != nullptr && (this->x != x || this->y != y || this->z != z)) {
+  if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
     service->OnNewMotionValues(x, y, z);
   }
 
@@ -18,10 +49,12 @@   time = xTaskGetTickCount();
 
   lastX = this->x;
   this->x = x;
-  lastY = this->y;
-  this->y = y;
-  lastZ = this->z;
-  this->z = z;
+  yHistory++;
+  yHistory[0] = y;
+  zHistory++;
+  zHistory[0] = z;
+
+  stats = GetAccelStats();
 
   int32_t deltaSteps = nbSteps - this->nbSteps;
   if (deltaSteps > 0) {
@@ -30,36 +63,70 @@   }
   this->nbSteps = nbSteps;
 }
 
-bool MotionController::ShouldRaiseWake(bool isSleeping) {
-  if ((x + 335) <= 670 && z < 0) {
-    if (!isSleeping) {
-      if (y <= 0) {
-        return false;
-      }
-      lastYForRaiseWake = 0;
-      return false;
-    }
+MotionController::AccelStats MotionController::GetAccelStats() const {
+  AccelStats stats;
+
+  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
+    stats.yMean += yHistory[histSize - i];
+    stats.zMean += zHistory[histSize - i];
+    stats.prevYMean += yHistory[1 + i];
+    stats.prevZMean += zHistory[1 + i];
+  }
+  stats.yMean /= AccelStats::numHistory;
+  stats.zMean /= AccelStats::numHistory;
+  stats.prevYMean /= AccelStats::numHistory;
+  stats.prevZMean /= AccelStats::numHistory;
+
+  for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
+    stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
+    stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
+  }
+  stats.yVariance /= AccelStats::numHistory;
+  stats.zVariance /= AccelStats::numHistory;
 
-    if (y >= 0) {
-      lastYForRaiseWake = 0;
-      return false;
-    }
-    if (y + 230 < lastYForRaiseWake) {
-      lastYForRaiseWake = y;
-      return true;
-    }
+  return stats;
+}
+
+bool MotionController::ShouldRaiseWake() const {
+  constexpr uint32_t varianceThresh = 56 * 56;
+  constexpr int16_t xThresh = 384;
+  constexpr int16_t yThresh = -64;
+  constexpr int16_t rollDegreesThresh = -45;
+
+  if (x < -xThresh || x > xThresh) {
+    return false;
   }
-  return false;
+
+  // if the variance is below the threshold, the accelerometer values can be considered to be from acceleration due to gravity
+  if (stats.yVariance > varianceThresh || (stats.yMean < -724 && stats.zVariance > varianceThresh) || stats.yMean > yThresh) {
+    return false;
+  }
+
+  return DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < rollDegreesThresh;
 }
 
 bool MotionController::ShouldShakeWake(uint16_t thresh) {
   /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
-  int32_t speed = std::abs(z - lastZ + (y / 2) - (lastY / 2) + (x / 4) - (lastX / 4)) / (time - lastTime) * 100;
-  //(.2 * speed) + ((1 - .2) * accumulatedSpeed);
-  // implemented without floats as .25Alpha
-  accumulatedSpeed = (speed / 5) + ((accumulatedSpeed / 5) * 4);
+  int32_t speed =
+    std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
+  // (.2 * speed) + ((1 - .2) * accumulatedSpeed);
+  accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
 
   return accumulatedSpeed > thresh;
+}
+
+bool MotionController::ShouldLowerSleep() const {
+  if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
+    return false;
+  }
+
+  for (uint8_t i = AccelStats::numHistory + 1; i < yHistory.Size(); i++) {
+    if (yHistory[i] < 265) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 void MotionController::Init(Pinetime::Drivers::Bma421::DeviceTypes types) {




diff --git a/src/components/motion/MotionController.h b/src/components/motion/MotionController.h
index c524fef33fd4757f8316a0fa0887734802295ded..0aa7823e05e6d2b6702385b42a60d5eff0013e4d 100644
--- a/src/components/motion/MotionController.h
+++ b/src/components/motion/MotionController.h
@@ -6,6 +6,7 @@ #include 
 
 #include "drivers/Bma421.h"
 #include "components/ble/MotionService.h"
+#include "utility/CircularBuffer.h"
 
 namespace Pinetime {
   namespace Controllers {
@@ -24,11 +25,11 @@         return x;
       }
 
       int16_t Y() const {
-        return y;
+        return yHistory[0];
       }
 
       int16_t Z() const {
-        return z;
+        return zHistory[0];
       }
 
       uint32_t NbSteps() const {
@@ -44,7 +45,8 @@         return currentTripSteps;
       }
 
       bool ShouldShakeWake(uint16_t thresh);
-      bool ShouldRaiseWake(bool isSleeping);
+      bool ShouldRaiseWake() const;
+      bool ShouldLowerSleep() const;
 
       int32_t CurrentShakeSpeed() const {
         return accumulatedSpeed;
@@ -67,13 +69,27 @@
       TickType_t lastTime = 0;
       TickType_t time = 0;
 
+      struct AccelStats {
+        static constexpr uint8_t numHistory = 2;
+
+        int16_t yMean = 0;
+        int16_t zMean = 0;
+        int16_t prevYMean = 0;
+        int16_t prevZMean = 0;
+
+        uint32_t yVariance = 0;
+        uint32_t zVariance = 0;
+      };
+
+      AccelStats GetAccelStats() const;
+
+      AccelStats stats = {};
+
       int16_t lastX = 0;
       int16_t x = 0;
-      int16_t lastYForRaiseWake = 0;
-      int16_t lastY = 0;
-      int16_t y = 0;
-      int16_t lastZ = 0;
-      int16_t z = 0;
+      static constexpr uint8_t histSize = 8;
+      Utility::CircularBuffer<int16_t, histSize> yHistory = {};
+      Utility::CircularBuffer<int16_t, histSize> zHistory = {};
       int32_t accumulatedSpeed = 0;
 
       DeviceTypes deviceType = DeviceTypes::Unknown;




diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h
index efa44fdee46e33315d590cfcf6813d75e2c4ea99..063120774a8548024da3935a03a5c84532f111a6 100644
--- a/src/components/settings/Settings.h
+++ b/src/components/settings/Settings.h
@@ -3,21 +3,17 @@ #include 
 #include <bitset>
 #include "components/brightness/BrightnessController.h"
 #include "components/fs/FS.h"
-#include "displayapp/WatchFaces.h"
+#include "displayapp/apps/Apps.h"
 
 namespace Pinetime {
   namespace Controllers {
     class Settings {
     public:
       enum class ClockType : uint8_t { H24, H12 };
+      enum class WeatherFormat : uint8_t { Metric, Imperial };
       enum class Notification : uint8_t { On, Off, Sleep };
       enum class ChimesOption : uint8_t { None, Hours, HalfHours };
-      enum class WakeUpMode : uint8_t {
-        SingleTap = 0,
-        DoubleTap = 1,
-        RaiseWrist = 2,
-        Shake = 3,
-      };
+      enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
       enum class Colors : uint8_t {
         White,
         Silver,
@@ -185,6 +181,17 @@       ClockType GetClockType() const {
         return settings.clockType;
       };
 
+      void SetWeatherFormat(WeatherFormat weatherFormat) {
+        if (weatherFormat != settings.weatherFormat) {
+          settingsChanged = true;
+        }
+        settings.weatherFormat = weatherFormat;
+      };
+
+      WeatherFormat GetWeatherFormat() const {
+        return settings.weatherFormat;
+      };
+
       void SetNotificationStatus(Notification status) {
         if (status != settings.notificationStatus) {
           settingsChanged = true;
@@ -238,7 +245,7 @@           }
         }
       };
 
-      std::bitset<4> getWakeUpModes() const {
+      std::bitset<5> getWakeUpModes() const {
         return settings.wakeUpMode;
       }
 
@@ -279,7 +286,7 @@
     private:
       Pinetime::Controllers::FS& fs;
 
-      static constexpr uint32_t settingsVersion = 0x0005;
+      static constexpr uint32_t settingsVersion = 0x0007;
 
       struct SettingsData {
         uint32_t version = settingsVersion;
@@ -287,6 +294,7 @@         uint32_t stepsGoal = 10000;
         uint32_t screenTimeOut = 15000;
 
         ClockType clockType = ClockType::H24;
+        WeatherFormat weatherFormat = WeatherFormat::Metric;
         Notification notificationStatus = Notification::On;
 
         Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
@@ -296,8 +304,9 @@         PineTimeStyle PTS;
 
         WatchFaceInfineat watchFaceInfineat;
 
-        std::bitset<4> wakeUpMode {0};
+        std::bitset<5> wakeUpMode {0};
         uint16_t shakeWakeThreshold = 150;
+
         Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
       };
 




diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h
deleted file mode 100644
index f253bc0387b5d477b826e6d8f96c81631105cc98..0000000000000000000000000000000000000000
--- a/src/displayapp/Apps.h
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma once
-
-namespace Pinetime {
-  namespace Applications {
-    enum class Apps {
-      None,
-      Launcher,
-      Clock,
-      SysInfo,
-      FirmwareUpdate,
-      FirmwareValidation,
-      NotificationsPreview,
-      Notifications,
-      Timer,
-      Alarm,
-      FlashLight,
-      BatteryInfo,
-      Music,
-      Paint,
-      Paddle,
-      Twos,
-      HeartRate,
-      Navigation,
-      StopWatch,
-      Metronome,
-      Motion,
-      Steps,
-      PassKey,
-      QuickSettings,
-      Settings,
-      SettingWatchFace,
-      SettingTimeFormat,
-      SettingDisplay,
-      SettingWakeUp,
-      SettingSteps,
-      SettingSetDateTime,
-      SettingChimes,
-      SettingShakeThreshold,
-      SettingBluetooth,
-      Error
-    };
-  }
-}




diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h
new file mode 100644
index 0000000000000000000000000000000000000000..9992426c5dbb5d4aa6f5bd282dc8a985c0fe9212
--- /dev/null
+++ b/src/displayapp/Controllers.h
@@ -0,0 +1,56 @@
+#pragma once
+
+namespace Pinetime {
+  namespace Applications {
+    class DisplayApp;
+  }
+
+  namespace Components {
+    class LittleVgl;
+  }
+
+  namespace Controllers {
+    class Battery;
+    class Ble;
+    class DateTime;
+    class NotificationManager;
+    class HeartRateController;
+    class Settings;
+    class MotorController;
+    class MotionController;
+    class AlarmController;
+    class BrightnessController;
+    class SimpleWeatherService;
+    class FS;
+    class Timer;
+    class MusicService;
+    class NavigationService;
+  }
+
+  namespace System {
+    class SystemTask;
+  }
+
+  namespace Applications {
+    struct AppControllers {
+      const Pinetime::Controllers::Battery& batteryController;
+      const Pinetime::Controllers::Ble& bleController;
+      Pinetime::Controllers::DateTime& dateTimeController;
+      Pinetime::Controllers::NotificationManager& notificationManager;
+      Pinetime::Controllers::HeartRateController& heartRateController;
+      Pinetime::Controllers::Settings& settingsController;
+      Pinetime::Controllers::MotorController& motorController;
+      Pinetime::Controllers::MotionController& motionController;
+      Pinetime::Controllers::AlarmController& alarmController;
+      Pinetime::Controllers::BrightnessController& brightnessController;
+      Pinetime::Controllers::SimpleWeatherService* weatherController;
+      Pinetime::Controllers::FS& filesystem;
+      Pinetime::Controllers::Timer& timer;
+      Pinetime::System::SystemTask* systemTask;
+      Pinetime::Applications::DisplayApp* displayApp;
+      Pinetime::Components::LittleVgl& lvgl;
+      Pinetime::Controllers::MusicService* musicService;
+      Pinetime::Controllers::NavigationService* navigationService;
+    };
+  }
+}




diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index a930fe961cd2492039b52c33094cee2de09bcbe8..938d1179a8dd225ff87e4a93e6f0ab4c29bbc384 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -11,7 +11,6 @@ #include "components/ble/NotificationManager.h"
 #include "components/motion/MotionController.h"
 #include "components/motor/MotorController.h"
 #include "displayapp/screens/ApplicationList.h"
-#include "displayapp/screens/Clock.h"
 #include "displayapp/screens/FirmwareUpdate.h"
 #include "displayapp/screens/FirmwareValidation.h"
 #include "displayapp/screens/InfiniPaint.h"
@@ -29,7 +28,6 @@ #include "displayapp/screens/BatteryInfo.h"
 #include "displayapp/screens/Steps.h"
 #include "displayapp/screens/PassKey.h"
 #include "displayapp/screens/Error.h"
-#include "displayapp/screens/Weather.h"
 
 #include "drivers/Cst816s.h"
 #include "drivers/St7789.h"
@@ -41,6 +39,7 @@ #include "displayapp/screens/settings/QuickSettings.h"
 #include "displayapp/screens/settings/Settings.h"
 #include "displayapp/screens/settings/SettingWatchFace.h"
 #include "displayapp/screens/settings/SettingTimeFormat.h"
+#include "displayapp/screens/settings/SettingWeatherFormat.h"
 #include "displayapp/screens/settings/SettingWakeUp.h"
 #include "displayapp/screens/settings/SettingDisplay.h"
 #include "displayapp/screens/settings/SettingSteps.h"
@@ -50,6 +49,7 @@ #include "displayapp/screens/settings/SettingShakeThreshold.h"
 #include "displayapp/screens/settings/SettingBluetooth.h"
 
 #include "libs/lv_conf.h"
+#include "UserApps.h"
 
 using namespace Pinetime::Applications;
 using namespace Pinetime::Applications::Display;
@@ -96,7 +96,25 @@     brightnessController {brightnessController},
     touchHandler {touchHandler},
     filesystem {filesystem},
     lvgl {lcd, filesystem},
-    timer(this, TimerCallback) {
+    timer(this, TimerCallback),
+    controllers {batteryController,
+                 bleController,
+                 dateTimeController,
+                 notificationManager,
+                 heartRateController,
+                 settingsController,
+                 motorController,
+                 motionController,
+                 alarmController,
+                 brightnessController,
+                 nullptr,
+                 filesystem,
+                 timer,
+                 nullptr,
+                 this,
+                 lvgl,
+                 nullptr,
+                 nullptr} {
 }
 
 void DisplayApp::Start(System::BootErrors error) {
@@ -402,26 +420,31 @@   currentScreen.reset(nullptr);
   SetFullRefresh(direction);
 
   switch (app) {
-    case Apps::Launcher:
-      currentScreen =
-        std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController);
-      break;
-    case Apps::Motion:
-      // currentScreen = std::make_unique<Screens::Motion>(motionController);
-      // break;
-    case Apps::None:
-    case Apps::Clock:
-      currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
-                                                       batteryController,
-                                                       bleController,
-                                                       notificationManager,
-                                                       settingsController,
-                                                       heartRateController,
-                                                       motionController,
-                                                       systemTask->nimble().weather(),
-                                                       filesystem);
-      break;
-
+    case Apps::Launcher: {
+      std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
+      int i = 0;
+      for (const auto& userApp : userApps) {
+        apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
+      }
+      currentScreen = std::make_unique<Screens::ApplicationList>(this,
+                                                                 settingsController,
+                                                                 batteryController,
+                                                                 bleController,
+                                                                 dateTimeController,
+                                                                 filesystem,
+                                                                 std::move(apps));
+    } break;
+    case Apps::Clock: {
+      const auto* watchFace =
+        std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
+          return watchfaceDescription.watchFace == settingsController.GetWatchFace();
+        });
+      if (watchFace != userWatchFaces.end())
+        currentScreen.reset(watchFace->create(controllers));
+      else {
+        currentScreen.reset(userWatchFaces[0].create(controllers));
+      }
+    } break;
     case Apps::Error:
       currentScreen = std::make_unique<Screens::Error>(bootError);
       break;
@@ -453,14 +476,6 @@                                                                motorController,
                                                                *systemTask,
                                                                Screens::Notifications::Modes::Preview);
       break;
-    case Apps::Timer:
-      currentScreen = std::make_unique<Screens::Timer>(timer);
-      break;
-    case Apps::Alarm:
-      currentScreen = std::make_unique<Screens::Alarm>(alarmController, settingsController.GetClockType(), *systemTask, motorController);
-      break;
-
-    // Settings
     case Apps::QuickSettings:
       currentScreen = std::make_unique<Screens::QuickSettings>(this,
                                                                batteryController,
@@ -473,12 +488,21 @@       break;
     case Apps::Settings:
       currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
       break;
-    case Apps::SettingWatchFace:
-      currentScreen = std::make_unique<Screens::SettingWatchFace>(this, settingsController, filesystem);
-      break;
+    case Apps::SettingWatchFace: {
+      std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
+      int i = 0;
+      for (const auto& userWatchFace : userWatchFaces) {
+        items[i++] =
+          Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
+      }
+      currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
+    } break;
     case Apps::SettingTimeFormat:
       currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
       break;
+    case Apps::SettingWeatherFormat:
+      currentScreen = std::make_unique<Screens::SettingWeatherFormat>(settingsController);
+      break;
     case Apps::SettingWakeUp:
       currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
       break;
@@ -516,38 +540,17 @@       break;
     case Apps::FlashLight:
       currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
       break;
-    case Apps::StopWatch:
-      currentScreen = std::make_unique<Screens::StopWatch>(*systemTask);
-      break;
-    case Apps::Twos:
-      currentScreen = std::make_unique<Screens::Twos>();
-      break;
-    case Apps::Paint:
-      currentScreen = std::make_unique<Screens::InfiniPaint>(lvgl, motorController);
-      break;
-    case Apps::Paddle:
-      currentScreen = std::make_unique<Screens::Paddle>(lvgl);
-      break;
-    case Apps::Music:
-      currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music());
-      break;
-    case Apps::Navigation:
-      currentScreen = std::make_unique<Screens::Navigation>(systemTask->nimble().navigation());
-      break;
-    case Apps::HeartRate:
-      currentScreen = std::make_unique<Screens::HeartRate>(heartRateController, *systemTask);
-      break;
-    case Apps::Metronome:
-      currentScreen = std::make_unique<Screens::Metronome>(motorController, *systemTask);
-      break;
-    /* Weather debug app
-    case Apps::Weather:
-      currentScreen = std::make_unique<Screens::Weather>(this, systemTask->nimble().weather());
-      break;
-    */
-    case Apps::Steps:
-      currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
+    default: {
+      const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
+        return appDescription.app == app;
+      });
+      if (d != userApps.end()) {
+        currentScreen.reset(d->create(controllers));
+      } else {
+        currentScreen.reset(userWatchFaces[0].create(controllers));
+      }
       break;
+    }
   }
   currentApp = app;
 }
@@ -560,7 +563,15 @@     if (xHigherPriorityTaskWoken == pdTRUE) {
       portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
     }
   } else {
-    xQueueSend(msgQueue, &msg, portMAX_DELAY);
+    TickType_t timeout = portMAX_DELAY;
+    // Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
+    // deadlock between SystemTask and DisplayApp when their respective message queues are getting full
+    // when a lot of notifications are received on a very short time span.
+    if (msg == Messages::NewNotification) {
+      timeout = static_cast<TickType_t>(0);
+    }
+
+    xQueueSend(msgQueue, &msg, timeout);
   }
 }
 
@@ -597,6 +608,19 @@ }
 
 void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
   this->systemTask = systemTask;
+  this->controllers.systemTask = systemTask;
+}
+
+void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {
+  this->controllers.weatherController = weatherService;
+}
+
+void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) {
+  this->controllers.musicService = musicService;
+}
+
+void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) {
+  this->controllers.navigationService = NavigationService;
 }
 
 void DisplayApp::ApplyBrightness() {




diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h
index f537651dbfc7e9b36a904abea83453a6c5a20dca..96bce4dd1df629aa5f322f229c4bf12e4f4530fa 100644
--- a/src/displayapp/DisplayApp.h
+++ b/src/displayapp/DisplayApp.h
@@ -4,7 +4,7 @@ #include 
 #include <task.h>
 #include <memory>
 #include <systemtask/Messages.h>
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "displayapp/LittleVgl.h"
 #include "displayapp/TouchEvents.h"
 #include "components/brightness/BrightnessController.h"
@@ -20,6 +20,7 @@ #include "displayapp/Messages.h"
 #include "BootErrors.h"
 
 #include "utility/StaticStack.h"
+#include "displayapp/Controllers.h"
 
 namespace Pinetime {
 
@@ -38,6 +39,7 @@     class NotificationManager;
     class HeartRateController;
     class MotionController;
     class TouchHandler;
+    class SimpleWeatherService;
   }
 
   namespace System {
@@ -73,6 +75,9 @@
       void SetFullRefresh(FullRefreshDirections direction);
 
       void Register(Pinetime::System::SystemTask* systemTask);
+      void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
+      void Register(Pinetime::Controllers::MusicService* musicService);
+      void Register(Pinetime::Controllers::NavigationService* NavigationService);
 
     private:
       Pinetime::Drivers::St7789& lcd;
@@ -96,6 +101,7 @@       Pinetime::Controllers::FirmwareValidator validator;
       Pinetime::Components::LittleVgl lvgl;
       Pinetime::Controllers::Timer timer;
 
+      AppControllers controllers;
       TaskHandle_t taskHandle;
 
       States state = States::Running;




diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp
index de165c2922b48d5acbc19bb5b794f2607371dbcb..c4bd57669b482f9b68273b6b36527a753ca5d018 100644
--- a/src/displayapp/DisplayAppRecovery.cpp
+++ b/src/displayapp/DisplayAppRecovery.cpp
@@ -121,3 +121,12 @@ }
 
 void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
 }
+
+void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* /*weatherService*/) {
+}
+
+void DisplayApp::Register(Pinetime::Controllers::MusicService* /*musicService*/) {
+}
+
+void DisplayApp::Register(Pinetime::Controllers::NavigationService* /*NavigationService*/) {
+}




diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h
index 3ce9518773f083a409ea7c604e79f1c9420aa068..3a5c78d9095638dd3f6658d705c1b4909e509585 100644
--- a/src/displayapp/DisplayAppRecovery.h
+++ b/src/displayapp/DisplayAppRecovery.h
@@ -11,7 +11,7 @@ #include 
 #include <components/motor/MotorController.h>
 #include "BootErrors.h"
 #include "displayapp/TouchEvents.h"
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "displayapp/Messages.h"
 
 namespace Pinetime {
@@ -34,6 +34,9 @@     class MotorController;
     class AlarmController;
     class BrightnessController;
     class FS;
+    class SimpleWeatherService;
+    class MusicService;
+    class NavigationService;
   }
 
   namespace System {
@@ -66,6 +69,9 @@       };
 
       void PushMessage(Pinetime::Applications::Display::Messages msg);
       void Register(Pinetime::System::SystemTask* systemTask);
+      void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
+      void Register(Pinetime::Controllers::MusicService* musicService);
+      void Register(Pinetime::Controllers::NavigationService* NavigationService);
 
     private:
       TaskHandle_t taskHandle;




diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h
new file mode 100644
index 0000000000000000000000000000000000000000..f18e2efb0a7c9d8d67d5f10f957596a91ea6fcb6
--- /dev/null
+++ b/src/displayapp/UserApps.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "displayapp/apps/Apps.h"
+#include "Controllers.h"
+
+#include "displayapp/screens/Alarm.h"
+#include "displayapp/screens/Timer.h"
+#include "displayapp/screens/Twos.h"
+#include "displayapp/screens/Tile.h"
+#include "displayapp/screens/ApplicationList.h"
+#include "displayapp/screens/WatchFaceDigital.h"
+#include "displayapp/screens/WatchFaceMine.h"
+#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
+#include "displayapp/screens/WatchFaceInfineat.h"
+#include "displayapp/screens/WatchFaceTerminal.h"
+
+namespace Pinetime {
+  namespace Applications {
+    namespace Screens {
+      class Screen;
+    }
+
+    struct AppDescription {
+      Apps app;
+      const char* icon;
+      Screens::Screen* (*create)(AppControllers& controllers);
+    };
+
+    struct WatchFaceDescription {
+      WatchFace watchFace;
+      const char* name;
+      Screens::Screen* (*create)(AppControllers& controllers);
+      bool (*isAvailable)(Controllers::FS& fileSystem);
+    };
+
+    template <Apps t>
+    consteval AppDescription CreateAppDescription() {
+      return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
+    }
+
+    template <WatchFace t>
+    consteval WatchFaceDescription CreateWatchFaceDescription() {
+      return {WatchFaceTraits<t>::watchFace, WatchFaceTraits<t>::name, &WatchFaceTraits<t>::Create, &WatchFaceTraits<t>::IsAvailable};
+    }
+
+    template <template <Apps...> typename T, Apps... ts>
+    consteval std::array<AppDescription, sizeof...(ts)> CreateAppDescriptions(T<ts...>) {
+      return {CreateAppDescription<ts>()...};
+    }
+
+    template <template <WatchFace...> typename T, WatchFace... ts>
+    consteval std::array<WatchFaceDescription, sizeof...(ts)> CreateWatchFaceDescriptions(T<ts...>) {
+      return {CreateWatchFaceDescription<ts>()...};
+    }
+
+    constexpr auto userApps = CreateAppDescriptions(UserAppTypes {});
+    constexpr auto userWatchFaces = CreateWatchFaceDescriptions(UserWatchFaceTypes {});
+  }
+}




diff --git a/src/displayapp/WatchFaces.h b/src/displayapp/WatchFaces.h
deleted file mode 100644
index d0f8ca158bf89a0d0bae707d6fb96b041d7285d6..0000000000000000000000000000000000000000
--- a/src/displayapp/WatchFaces.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-namespace Pinetime {
-  namespace Applications {
-    enum class WatchFace : uint8_t {
-      Digital = 0,
-      Mine = 1,
-      PineTimeStyle = 2,
-      Terminal = 3,
-      Infineat = 4,
-      CasioStyleG7710 = 5,
-    };
-  }
-}




diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in
new file mode 100644
index 0000000000000000000000000000000000000000..a267628b122e23efd5a9cd3f3c71d921e58db0da
--- /dev/null
+++ b/src/displayapp/apps/Apps.h.in
@@ -0,0 +1,77 @@
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+namespace Pinetime {
+  namespace Applications {
+    enum class Apps : uint8_t {
+      None,
+      Launcher,
+      Clock,
+      SysInfo,
+      FirmwareUpdate,
+      FirmwareValidation,
+      NotificationsPreview,
+      Notifications,
+      Timer,
+      Alarm,
+      FlashLight,
+      BatteryInfo,
+      Music,
+      Paint,
+      Paddle,
+      Twos,
+      HeartRate,
+      Navigation,
+      StopWatch,
+      Metronome,
+      Motion,
+      Steps,
+      PassKey,
+      QuickSettings,
+      Settings,
+      SettingWatchFace,
+      SettingTimeFormat,
+      SettingWeatherFormat,
+      SettingDisplay,
+      SettingWakeUp,
+      SettingSteps,
+      SettingSetDateTime,
+      SettingChimes,
+      SettingShakeThreshold,
+      SettingBluetooth,
+      Error,
+      Weather
+    };
+
+    enum class WatchFace : uint8_t {
+      Digital,
+      Mine,
+      Terminal,
+      Infineat,
+      CasioStyleG7710,
+    };
+
+    template <Apps>
+    struct AppTraits {};
+
+    template <WatchFace>
+    struct WatchFaceTraits {};
+
+    template <Apps... As>
+    struct TypeList {
+      static constexpr size_t Count = sizeof...(As);
+    };
+
+    using UserAppTypes = TypeList<@USERAPP_TYPES@>;
+
+    template <WatchFace... Ws>
+    struct WatchFaceTypeList {
+      static constexpr size_t Count = sizeof...(Ws);
+    };
+
+    using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
+
+    static_assert(UserWatchFaceTypes::Count >= 1);
+  }
+}




diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..52118c085843163a17f0a95aee2b3cb0447ab17f
--- /dev/null
+++ b/src/displayapp/apps/CMakeLists.txt
@@ -0,0 +1,36 @@
+if(DEFINED ENABLE_USERAPPS)
+    set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
+else ()
+    set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Alarm")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
+    #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
+    #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion")
+    set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
+endif ()
+
+if(DEFINED ENABLE_WATCHFACES)
+    set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
+else()
+    set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
+    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Mine")
+    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal")
+    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat")
+    set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
+    set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
+endif()
+
+add_library(infinitime_apps INTERFACE)
+target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
+target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
+
+# Generate the list of user apps to be compiled into the firmware
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Apps.h.in ${CMAKE_CURRENT_BINARY_DIR}/Apps.h)




diff --git a/src/displayapp/fonts/CMakeLists.txt b/src/displayapp/fonts/CMakeLists.txt
index 5a32151ec4a86ec1956e1a1ae8464eaefa2d7e72..22627efcadbdf2148149edec854f7a3a8d285c81 100644
--- a/src/displayapp/fonts/CMakeLists.txt
+++ b/src/displayapp/fonts/CMakeLists.txt
@@ -1,5 +1,5 @@
 set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20
-   jetbrains_mono_extrabold_compressed lv_font_navi_80 lv_font_sys_48
+   jetbrains_mono_extrabold_compressed lv_font_sys_48
    open_sans_light fontawesome_weathericons)
 find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
    HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")




diff --git a/src/displayapp/fonts/README.md b/src/displayapp/fonts/README.md
index b2669a788cc035ffd63bd80315765b4887f05c2b..bcc2d49c1c9cecdc188ce1960f63057b72d2870e 100644
--- a/src/displayapp/fonts/README.md
+++ b/src/displayapp/fonts/README.md
@@ -33,3 +33,17 @@
 ### Navigation font
 
 `navigtion.ttf` is created with the web app [icomoon](https://icomoon.io/app) by importing the svg files from `src/displayapp/icons/navigation/unique` and generating the font. `lv_font_navi_80.json` is a project file for the site, which you can import to add or remove icons.
+
+To save space in the internal flash memory, the navigation icons are now moved into the external flash memory. To do this, the TTF font is converted into pictures (1 for each symbol). Those pictures are then concatenated into 2 big pictures (we need two files since LVGL supports maximum 2048px width/height). At runtime, a map is used to locate the desired icon in the corresponding file at a specific offset. 
+
+Here is the command to convert the TTF font in PNG picture:
+
+```shell
+convert -background none -fill white -font navigation.ttf -pointsize 80 -gravity center label:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"  navigation0.png
+
+convert -background none -fill white -font navigation.ttf -pointsize 80 -gravity center label:"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"  navigation1.png
+```
+
+*Please note that the characters after `label:` are UTF-8 characters and might not be displayed correctly in this document.*
+
+The characters in the TTF font range from `0xEEA480` to `0xEEA4A9`. Characters from `0xEEA480` to `0xEEA498` are stored in `navigation0.png` and the others in `navigation1.png`. Each character is 80px height so displaying a specific character consists in multiplying its index in the file by -80 and use this value as the offset when calling `lv_img_set_offset_y()`.




diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json
index a2f8efc83011aaa71813b8a63be63034618e396e..b10a95a3a66fe1622258baee7003017c3d3848f0 100644
--- a/src/displayapp/fonts/fonts.json
+++ b/src/displayapp/fonts/fonts.json
@@ -7,7 +7,7 @@             "range": "0x20-0x7e, 0x3b4, 0x3bc, 0x3c0, 0x3c4, 0x104, 0x105, 0x106, 0x107, 0x118, 0x119, 0x141, 0x142, 0x143, 0x144, 0xd3, 0xf3, 0x15a, 0x15b, 0x179, 0x17a, 0x17b, 0x17c, 0x410-0x44f, 0xB0"
          },
          {
             "file": "FontAwesome5-Solid+Brands+Regular.woff",
-            "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c"
+            "range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
          }
       ],
       "bpp": 1,
@@ -18,7 +18,7 @@    "jetbrains_mono_42": {
       "sources": [
          {
             "file": "JetBrainsMono-Regular.ttf",
-            "range": "0x25, 0x2b, 0x2d, 0x30-0x3a"
+            "range": "0x25, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74"
          }
       ],
       "bpp": 1,
@@ -64,22 +64,11 @@       ],
       "bpp": 1,
       "size": 48
    },
-   "lv_font_navi_80": {
-      "sources": [
-         {
-            "file": "navigation.ttf",
-            "range": "0xe900-0xe929"
-         }
-      ],
-      "bpp": 2,
-      "size": 80,
-      "compress": true
-   },
    "fontawesome_weathericons": {
       "sources": [
          {
             "file": "FontAwesome5-Solid+Brands+Regular.woff",
-            "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e"
+            "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc"
          }
       ],
       "bpp": 1,




diff --git a/src/displayapp/screens/Alarm.cpp b/src/displayapp/screens/Alarm.cpp
index 4e6ce797477d7dac4e22cd4ec77ad5bb820bc638..cbc702f3124b222facf9b7ca92c59f2b0596f789 100644
--- a/src/displayapp/screens/Alarm.cpp
+++ b/src/displayapp/screens/Alarm.cpp
@@ -19,6 +19,10 @@ #include "displayapp/screens/Alarm.h"
 #include "displayapp/screens/Screen.h"
 #include "displayapp/screens/Symbols.h"
 #include "displayapp/InfiniTimeTheme.h"
+#include "components/settings/Settings.h"
+#include "components/alarm/AlarmController.h"
+#include "components/motor/MotorController.h"
+#include "systemtask/SystemTask.h"
 
 using namespace Pinetime::Applications::Screens;
 using Pinetime::Controllers::AlarmController;




diff --git a/src/displayapp/screens/Alarm.h b/src/displayapp/screens/Alarm.h
index 911773662acebd9b2ff1b5cc9704fc5c8100d556..993d65d171b93675a284603ebdbaf31c50c8520f 100644
--- a/src/displayapp/screens/Alarm.h
+++ b/src/displayapp/screens/Alarm.h
@@ -17,21 +17,22 @@     along with this program.  If not, see .
 */
 #pragma once
 
+#include "displayapp/apps/Apps.h"
+#include "components/settings/Settings.h"
 #include "displayapp/screens/Screen.h"
-#include "systemtask/SystemTask.h"
-#include "displayapp/LittleVgl.h"
-#include "components/alarm/AlarmController.h"
 #include "displayapp/widgets/Counter.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
   namespace Applications {
     namespace Screens {
       class Alarm : public Screen {
       public:
-        Alarm(Controllers::AlarmController& alarmController,
-              Controllers::Settings::ClockType clockType,
-              System::SystemTask& systemTask,
-              Controllers::MotorController& motorController);
+        explicit Alarm(Controllers::AlarmController& alarmController,
+                       Controllers::Settings::ClockType clockType,
+                       System::SystemTask& systemTask,
+                       Controllers::MotorController& motorController);
         ~Alarm() override;
         void SetAlerting();
         void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
@@ -63,6 +64,19 @@         void UpdateAlarmTime();
         Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
         Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
       };
+    }
+
+    template <>
+    struct AppTraits<Apps::Alarm> {
+      static constexpr Apps app = Apps::Alarm;
+      static constexpr const char* icon = Screens::Symbols::clock;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Alarm(controllers.alarmController,
+                                  controllers.settingsController.GetClockType(),
+                                  *controllers.systemTask,
+                                  controllers.motorController);
+      };
     };
-  };
+  }
 }




diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp
index 0a65a5d472452ded0cd6d970da7a885143e7c112..41735349da8322d27b248cdb59ae1ecb7b0193c3 100644
--- a/src/displayapp/screens/ApplicationList.cpp
+++ b/src/displayapp/screens/ApplicationList.cpp
@@ -1,12 +1,11 @@
 #include "displayapp/screens/ApplicationList.h"
+#include "displayapp/screens/Tile.h"
 #include <lvgl/lvgl.h>
 #include <functional>
-#include "displayapp/Apps.h"
-#include "displayapp/DisplayApp.h"
+#include <algorithm>
+#include "components/settings/Settings.h"
 
 using namespace Pinetime::Applications::Screens;
-
-constexpr std::array<Tile::Applications, ApplicationList::applications.size()> ApplicationList::applications;
 
 auto ApplicationList::CreateScreenList() const {
   std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
@@ -18,16 +17,20 @@   }
   return screens;
 }
 
-ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app,
+ApplicationList::ApplicationList(DisplayApp* app,
                                  Pinetime::Controllers::Settings& settingsController,
                                  const Pinetime::Controllers::Battery& batteryController,
                                  const Pinetime::Controllers::Ble& bleController,
-                                 Controllers::DateTime& dateTimeController)
+                                 Controllers::DateTime& dateTimeController,
+                                 Pinetime::Controllers::FS& filesystem,
+                                 std::array<Tile::Applications, UserAppTypes::Count>&& apps)
   : app {app},
     settingsController {settingsController},
     batteryController {batteryController},
     bleController {bleController},
     dateTimeController {dateTimeController},
+    filesystem {filesystem},
+    apps {std::move(apps)},
     screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
 }
 
@@ -40,9 +43,14 @@   return screens.OnTouchEvent(event);
 }
 
 std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
-  std::array<Tile::Applications, appsPerScreen> apps;
+  std::array<Tile::Applications, appsPerScreen> pageApps;
+
   for (int i = 0; i < appsPerScreen; i++) {
-    apps[i] = applications[screenNum * appsPerScreen + i];
+    if (i + (screenNum * appsPerScreen) >= apps.size()) {
+      pageApps[i] = {"", Pinetime::Applications::Apps::None, false};
+    } else {
+      pageApps[i] = apps[i + (screenNum * appsPerScreen)];
+    }
   }
 
   return std::make_unique<Screens::Tile>(screenNum,
@@ -52,5 +60,5 @@                                          settingsController,
                                          batteryController,
                                          bleController,
                                          dateTimeController,
-                                         apps);
+                                         pageApps);
 }




diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h
index 7bdd1154081bb3e00ec721bec77a2dbac1b3bb13..41a413af1a44a37674a65a9a89f92a24a21a6762 100644
--- a/src/displayapp/screens/ApplicationList.h
+++ b/src/displayapp/screens/ApplicationList.h
@@ -2,14 +2,12 @@ #pragma once
 
 #include <array>
 #include <memory>
-
-#include "displayapp/screens/Screen.h"
-#include "displayapp/screens/ScreenList.h"
-#include "components/datetime/DateTimeController.h"
-#include "components/settings/Settings.h"
-#include "components/battery/BatteryController.h"
-#include "displayapp/screens/Symbols.h"
-#include "displayapp/screens/Tile.h"
+#include "displayapp/apps/Apps.h"
+#include "Screen.h"
+#include "ScreenList.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
+#include "Tile.h"
 
 namespace Pinetime {
   namespace Applications {
@@ -20,7 +18,9 @@         explicit ApplicationList(DisplayApp* app,
                                  Pinetime::Controllers::Settings& settingsController,
                                  const Pinetime::Controllers::Battery& batteryController,
                                  const Pinetime::Controllers::Ble& bleController,
-                                 Controllers::DateTime& dateTimeController);
+                                 Controllers::DateTime& dateTimeController,
+                                 Pinetime::Controllers::FS& filesystem,
+                                 std::array<Tile::Applications, UserAppTypes::Count>&& apps);
         ~ApplicationList() override;
         bool OnTouchEvent(TouchEvents event) override;
 
@@ -33,29 +33,13 @@         Controllers::Settings& settingsController;
         const Pinetime::Controllers::Battery& batteryController;
         const Pinetime::Controllers::Ble& bleController;
         Controllers::DateTime& dateTimeController;
+        Pinetime::Controllers::FS& filesystem;
+        std::array<Tile::Applications, UserAppTypes::Count> apps;
 
         static constexpr int appsPerScreen = 6;
 
-        // Increment this when more space is needed
-        static constexpr int nScreens = 2;
-
-        static constexpr std::array<Tile::Applications, appsPerScreen * nScreens> applications {{
-          {Symbols::stopWatch, Apps::StopWatch},
-          {Symbols::clock, Apps::Alarm},
-          {Symbols::hourGlass, Apps::Timer},
-          {Symbols::shoe, Apps::Steps},
-          {Symbols::heartBeat, Apps::HeartRate},
-          {Symbols::music, Apps::Music},
-
-          {Symbols::paintbrush, Apps::Paint},
-          {Symbols::paddle, Apps::Paddle},
-          {"2", Apps::Twos},
-          {Symbols::drum, Apps::Metronome},
-          {Symbols::map, Apps::Navigation},
-          {Symbols::none, Apps::None},
+        static constexpr int nScreens = UserAppTypes::Count > 0 ? (UserAppTypes::Count - 1) / appsPerScreen + 1 : 1;
 
-          // {"M", Apps::Motion},
-        }};
         ScreenList<nScreens> screens;
       };
     }




diff --git a/src/displayapp/screens/CheckboxList.h b/src/displayapp/screens/CheckboxList.h
index c208bc489d6f8a7c8757285ded2259b436c6d8b9..c6119970a14f0eb11646393205fd6cb83d91b491 100644
--- a/src/displayapp/screens/CheckboxList.h
+++ b/src/displayapp/screens/CheckboxList.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "displayapp/screens/Screen.h"
 #include <array>
 #include <cstdint>




diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp
deleted file mode 100644
index 17b4f092af2898540a2f20fc50e23b3b51e9b226..0000000000000000000000000000000000000000
--- a/src/displayapp/screens/Clock.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-#include "displayapp/screens/Clock.h"
-
-#include <lvgl/lvgl.h>
-#include "components/battery/BatteryController.h"
-#include "components/motion/MotionController.h"
-#include "components/ble/BleController.h"
-#include "components/ble/NotificationManager.h"
-#include "components/settings/Settings.h"
-#include "displayapp/DisplayApp.h"
-#include "displayapp/screens/WatchFaceDigital.h"
-#include "displayapp/screens/WatchFaceTerminal.h"
-#include "displayapp/screens/WatchFaceInfineat.h"
-#include "displayapp/screens/WatchFaceMine.h"
-#include "displayapp/screens/WatchFacePineTimeStyle.h"
-#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
-
-using namespace Pinetime::Applications::Screens;
-using namespace Pinetime::Applications;
-
-Clock::Clock(Controllers::DateTime& dateTimeController,
-             const Controllers::Battery& batteryController,
-             const Controllers::Ble& bleController,
-             Controllers::NotificationManager& notificationManager,
-             Controllers::Settings& settingsController,
-             Controllers::HeartRateController& heartRateController,
-             Controllers::MotionController& motionController,
-             Controllers::WeatherService& weatherService,
-             Controllers::FS& filesystem)
-  : dateTimeController {dateTimeController},
-    batteryController {batteryController},
-    bleController {bleController},
-    notificationManager {notificationManager},
-    settingsController {settingsController},
-    heartRateController {heartRateController},
-    motionController {motionController},
-    weatherService {weatherService},
-    filesystem {filesystem},
-    screen {[this, &settingsController]() {
-      switch (settingsController.GetWatchFace()) {
-        case WatchFace::Digital:
-          return WatchFaceDigitalScreen();
-          break;
-        case WatchFace::Mine:
-          return WatchFaceMineScreen();
-          break;
-        case WatchFace::PineTimeStyle:
-          return WatchFacePineTimeStyleScreen();
-          break;
-        case WatchFace::Terminal:
-          return WatchFaceTerminalScreen();
-          break;
-        case WatchFace::Infineat:
-          return WatchFaceInfineatScreen();
-          break;
-        case WatchFace::CasioStyleG7710:
-          return WatchFaceCasioStyleG7710();
-          break;
-      }
-      return WatchFaceDigitalScreen();
-    }()} {
-  settingsController.SetAppMenu(0);
-}
-
-Clock::~Clock() {
-  lv_obj_clean(lv_scr_act());
-}
-
-bool Clock::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
-  return screen->OnTouchEvent(event);
-}
-
-bool Clock::OnButtonPushed() {
-  return screen->OnButtonPushed();
-}
-
-std::unique_ptr<Screen> Clock::WatchFaceDigitalScreen() {
-  return std::make_unique<Screens::WatchFaceDigital>(dateTimeController,
-                                                     batteryController,
-                                                     bleController,
-                                                     notificationManager,
-                                                     settingsController,
-                                                     heartRateController,
-                                                     motionController);
-}
-
-std::unique_ptr<Screen> Clock::WatchFacePineTimeStyleScreen() {
-  return std::make_unique<Screens::WatchFacePineTimeStyle>(dateTimeController,
-                                                           batteryController,
-                                                           bleController,
-                                                           notificationManager,
-                                                           settingsController,
-                                                           motionController,
-                                                           weatherService);
-}
-
-std::unique_ptr<Screen> Clock::WatchFaceTerminalScreen() {
-  return std::make_unique<Screens::WatchFaceTerminal>(dateTimeController,
-                                                      batteryController,
-                                                      bleController,
-                                                      notificationManager,
-                                                      settingsController,
-                                                      heartRateController,
-                                                      motionController);
-}
-
-std::unique_ptr<Screen> Clock::WatchFaceInfineatScreen() {
-  return std::make_unique<Screens::WatchFaceInfineat>(dateTimeController,
-                                                      batteryController,
-                                                      bleController,
-                                                      notificationManager,
-                                                      settingsController,
-                                                      motionController,
-                                                      filesystem);
-}
-
-std::unique_ptr<Screen> Clock::WatchFaceCasioStyleG7710() {
-  return std::make_unique<Screens::WatchFaceCasioStyleG7710>(dateTimeController,
-                                                             batteryController,
-                                                             bleController,
-                                                             notificationManager,
-                                                             settingsController,
-                                                             heartRateController,
-                                                             motionController,
-                                                             filesystem);
-}
-
-std::unique_ptr<Screen> Clock::WatchFaceMineScreen() {
-  return std::make_unique<Screens::WatchFaceMine>(dateTimeController,
-                                                  batteryController,
-                                                  bleController,
-                                                  notificationManager,
-                                                  settingsController,
-                                                  motionController,
-                                                  weatherService);
-}




diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h
deleted file mode 100644
index b15cea3d7f72e611106d18042a6b7679a4df637a..0000000000000000000000000000000000000000
--- a/src/displayapp/screens/Clock.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#pragma once
-
-#include <lvgl/src/lv_core/lv_obj.h>
-#include <chrono>
-#include <cstdint>
-#include <memory>
-#include <components/heartrate/HeartRateController.h>
-#include "displayapp/screens/Screen.h"
-#include "components/datetime/DateTimeController.h"
-#include "components/ble/weather/WeatherService.h"
-
-namespace Pinetime {
-  namespace Controllers {
-    class Settings;
-    class Battery;
-    class Ble;
-    class NotificationManager;
-    class MotionController;
-  }
-
-  namespace Applications {
-    namespace Screens {
-      class Clock : public Screen {
-      public:
-        Clock(Controllers::DateTime& dateTimeController,
-              const Controllers::Battery& batteryController,
-              const Controllers::Ble& bleController,
-              Controllers::NotificationManager& notificationManager,
-              Controllers::Settings& settingsController,
-              Controllers::HeartRateController& heartRateController,
-              Controllers::MotionController& motionController,
-              Controllers::WeatherService& weatherService,
-              Controllers::FS& filesystem);
-        ~Clock() override;
-
-        bool OnTouchEvent(TouchEvents event) override;
-        bool OnButtonPushed() override;
-
-      private:
-        Controllers::DateTime& dateTimeController;
-        const Controllers::Battery& batteryController;
-        const Controllers::Ble& bleController;
-        Controllers::NotificationManager& notificationManager;
-        Controllers::Settings& settingsController;
-        Controllers::HeartRateController& heartRateController;
-        Controllers::MotionController& motionController;
-        Controllers::WeatherService& weatherService;
-        Controllers::FS& filesystem;
-
-        std::unique_ptr<Screen> screen;
-        std::unique_ptr<Screen> WatchFaceDigitalScreen();
-        std::unique_ptr<Screen> WatchFacePineTimeStyleScreen();
-        std::unique_ptr<Screen> WatchFaceTerminalScreen();
-        std::unique_ptr<Screen> WatchFaceInfineatScreen();
-        std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
-        std::unique_ptr<Screen> WatchFaceMineScreen();
-      };
-    }
-  }
-}




diff --git a/src/displayapp/screens/HeartRate.h b/src/displayapp/screens/HeartRate.h
index 78ae63db5f4b9700499c1f95e291af55dad0cf04..bf39209a4f9dbf81ae363de45fa0ee814726e9a5 100644
--- a/src/displayapp/screens/HeartRate.h
+++ b/src/displayapp/screens/HeartRate.h
@@ -4,6 +4,7 @@ #include 
 #include <chrono>
 #include "displayapp/screens/Screen.h"
 #include "systemtask/SystemTask.h"
+#include "Symbols.h"
 #include <lvgl/src/lv_core/lv_style.h>
 #include <lvgl/src/lv_core/lv_obj.h>
 
@@ -37,5 +38,15 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::HeartRate> {
+      static constexpr Apps app = Apps::HeartRate;
+      static constexpr const char* icon = Screens::Symbols::heartBeat;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/InfiniPaint.h b/src/displayapp/screens/InfiniPaint.h
index ec184c44c5cc1184c2525dec78f603b8e38af0fc..b1f9741a02d8e4cf9cfcd7ffa275e6520d02101e 100644
--- a/src/displayapp/screens/InfiniPaint.h
+++ b/src/displayapp/screens/InfiniPaint.h
@@ -5,6 +5,9 @@ #include 
 #include <algorithm> // std::fill
 #include "displayapp/screens/Screen.h"
 #include "components/motor/MotorController.h"
+#include "Symbols.h"
+#include "displayapp/apps/Apps.h"
+#include <displayapp/Controllers.h>
 
 namespace Pinetime {
   namespace Components {
@@ -35,5 +38,15 @@         lv_color_t selectColor = LV_COLOR_WHITE;
         uint8_t color = 2;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Paint> {
+      static constexpr Apps app = Apps::Paint;
+      static constexpr const char* icon = Screens::Symbols::paintbrush;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/List.h b/src/displayapp/screens/List.h
index 564229e69495d1dfe7c107ef15b751a039210d2d..17a25f8209ecdd29efcf553e8aa629f0d80f14d2 100644
--- a/src/displayapp/screens/List.h
+++ b/src/displayapp/screens/List.h
@@ -5,7 +5,7 @@ #include 
 #include <array>
 #include "displayapp/screens/Screen.h"
 #include "displayapp/widgets/PageIndicator.h"
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "components/settings/Settings.h"
 
 #define MAXLISTITEMS 4




diff --git a/src/displayapp/screens/Metronome.h b/src/displayapp/screens/Metronome.h
index cc037766f556901b1770b0cdfd43dc0a70ce21e8..ec26742ac935223e7091dc701d5d0a16822904a2 100644
--- a/src/displayapp/screens/Metronome.h
+++ b/src/displayapp/screens/Metronome.h
@@ -3,6 +3,7 @@
 #include "systemtask/SystemTask.h"
 #include "components/motor/MotorController.h"
 #include "displayapp/screens/Screen.h"
+#include "Symbols.h"
 
 static constexpr uint8_t BPM_WINDOW_SIZE = 3;
 
@@ -43,5 +44,15 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Metronome> {
+      static constexpr Apps app = Apps::Metronome;
+      static constexpr const char* icon = Screens::Symbols::drum;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Metronome(controllers.motorController, *controllers.systemTask);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/Motion.h b/src/displayapp/screens/Motion.h
index e4cbe48393ca58e0efab10547c2fa7de195a9471..e13e068cd92810ec114377eae43bd5b3cf312357 100644
--- a/src/displayapp/screens/Motion.h
+++ b/src/displayapp/screens/Motion.h
@@ -6,6 +6,8 @@ #include "displayapp/screens/Screen.h"
 #include <lvgl/src/lv_core/lv_style.h>
 #include <lvgl/src/lv_core/lv_obj.h>
 #include <components/motion/MotionController.h>
+#include "displayapp/Controllers.h"
+#include "displayapp/apps/Apps.h"
 
 namespace Pinetime {
   namespace Applications {
@@ -30,5 +32,15 @@         lv_obj_t* labelStep;
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Motion> {
+      static constexpr Apps app = Apps::Motion;
+      static constexpr const char* icon = "M";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Motion(controllers.motionController);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/Music.h b/src/displayapp/screens/Music.h
index 847c6e7444d64c97d3e4d824724603ecde619ed2..522533215b2eea725da214c7a61fee5923cdd42f 100644
--- a/src/displayapp/screens/Music.h
+++ b/src/displayapp/screens/Music.h
@@ -21,6 +21,9 @@ #include 
 #include <lvgl/src/lv_core/lv_obj.h>
 #include <string>
 #include "displayapp/screens/Screen.h"
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
   namespace Controllers {
@@ -82,5 +85,15 @@
         /** Watchapp */
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Music> {
+      static constexpr Apps app = Apps::Music;
+      static constexpr const char* icon = Screens::Symbols::music;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Music(*controllers.musicService);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/Navigation.cpp b/src/displayapp/screens/Navigation.cpp
index e28191014cad759d574b9b9de5f3c9227b1ac3e7..ee9f2a0002f63b4a639bea1c3274c28e01083404 100644
--- a/src/displayapp/screens/Navigation.cpp
+++ b/src/displayapp/screens/Navigation.cpp
@@ -23,105 +23,166 @@ #include "displayapp/InfiniTimeTheme.h"
 
 using namespace Pinetime::Applications::Screens;
 
-LV_FONT_DECLARE(lv_font_navi_80)
+/* Notes about the navigation icons :
+ *  - Icons are generated from a TTF font converted in PNG images. Those images are all appended
+ *    vertically into a single PNG images. Since LVGL support images width and height up to
+ *    2048 px, the icons needs to be split into 2 separate PNG pictures. More info in
+ *    src/displayapp/fonts/README.md
+ *  - To make the handling of those icons easier, they must all have the same width and height
+ *  - Those PNG are then converted into BINARY format using the classical image generator
+ *    (in src/resources/generate-img.py)
+ *  - The array `iconMap` maps each icon with an index. This index corresponds to the position of
+ *    the icon in the file. All index lower than 25 (`maxIconsPerFile`) represent icons located
+ *    in the first file (navigation0.bin). All the other icons are located in the second file
+ *    (navigation1.bin). Since all icons have the same height, this index must be multiplied by
+ *    80px (`iconHeight`) to get the actual position (in pixels) of the icon in the image.
+ * - This is how the images are laid out in the PNG files :
+ *  *---------------*
+ *  | ICON 0        |
+ *  | FILE 0        |
+ *  | INDEX = 0     |
+ *  | PIXEL# = 0    |
+ *  *---------------*
+ *  | ICON 1        |
+ *  | FILE 0        |
+ *  | INDEX = 1     |
+ *  | PIXEL# = -80  |
+ *  *---------------*
+ *  | ICON 2        |
+ *  | FILE 0        |
+ *  | INDEX = 2     |
+ *  | PIXEL# = -160 |
+ *  *---------------*
+ *  |     ...       |
+ *  *---------------*
+ *  | ICON 25       |
+ *  | FILE 1        |
+ *  | INDEX = 25    |
+ *  | PIXEL# = 0    |
+ *  *---------------*
+ *  | ICON 26       |
+ *  | FILE 1        |
+ *  | INDEX = 26    |
+ *  | PIXEL# = -80  |
+ *  *---------------*
+ *   - The source images are located in `src/resources/navigation0.png` and `src/resources/navigation1.png`
+ */
 
 namespace {
-  constexpr std::array<std::pair<const char*, const char*>, 86> m_iconMap = {{
-    {"arrive-left", "\xEE\xA4\x81"},
-    {"arrive-right", "\xEE\xA4\x82"},
-    {"arrive-straight", "\xEE\xA4\x80"},
-    {"arrive", "\xEE\xA4\x80"},
-    {"close", "\xEE\xA4\x83"},
-    {"continue-left", "\xEE\xA4\x85"},
-    {"continue-right", "\xEE\xA4\x86"},
-    {"continue-slight-left", "\xEE\xA4\x87"},
-    {"continue-slight-right", "\xEE\xA4\x88"},
-    {"continue-straight", "\xEE\xA4\x84"},
-    {"continue-uturn", "\xEE\xA4\x89"},
-    {"continue", "\xEE\xA4\x84"},
-    {"depart-left", "\xEE\xA4\x8B"},
-    {"depart-right", "\xEE\xA4\x8C"},
-    {"depart-straight", "\xEE\xA4\x8A"},
-    {"end-of-road-left", "\xEE\xA4\x8D"},
-    {"end-of-road-right", "\xEE\xA4\x8E"},
-    {"ferry", "\xEE\xA4\x8F"},
-    {"flag", "\xEE\xA4\x90"},
-    {"fork-left", "\xEE\xA4\x92"},
-    {"fork-right", "\xEE\xA4\x93"},
-    {"fork-slight-left", "\xEE\xA4\x94"},
-    {"fork-slight-right", "\xEE\xA4\x95"},
-    {"fork-straight", "\xEE\xA4\x96"},
-    {"invalid", "\xEE\xA4\x84"},
-    {"invalid-left", "\xEE\xA4\x85"},
-    {"invalid-right", "\xEE\xA4\x86"},
-    {"invalid-slight-left", "\xEE\xA4\x87"},
-    {"invalid-slight-right", "\xEE\xA4\x88"},
-    {"invalid-straight", "\xEE\xA4\x84"},
-    {"invalid-uturn", "\xEE\xA4\x89"},
-    {"merge-left", "\xEE\xA4\x97"},
-    {"merge-right", "\xEE\xA4\x98"},
-    {"merge-slight-left", "\xEE\xA4\x99"},
-    {"merge-slight-right", "\xEE\xA4\x9A"},
-    {"merge-straight", "\xEE\xA4\x84"},
-    {"new-name-left", "\xEE\xA4\x85"},
-    {"new-name-right", "\xEE\xA4\x86"},
-    {"new-name-sharp-left", "\xEE\xA4\x9B"},
-    {"new-name-sharp-right", "\xEE\xA4\x9C"},
-    {"new-name-slight-left", "\xEE\xA4\x87"},
-    {"new-name-slight-right", "\xEE\xA4\x88"},
-    {"new-name-straight", "\xEE\xA4\x84"},
-    {"notification-left", "\xEE\xA4\x85"},
-    {"notification-right", "\xEE\xA4\x86"},
-    {"notification-sharp-left", "\xEE\xA4\x9B"},
-    {"notification-sharp-right", "\xEE\xA4\xA5"},
-    {"notification-slight-left", "\xEE\xA4\x87"},
-    {"notification-slight-right", "\xEE\xA4\x88"},
-    {"notification-straight", "\xEE\xA4\x84"},
-    {"off-ramp-left", "\xEE\xA4\x9D"},
-    {"off-ramp-right", "\xEE\xA4\x9E"},
-    {"off-ramp-slight-left", "\xEE\xA4\x9F"},
-    {"off-ramp-slight-right", "\xEE\xA4\xA0"},
-    {"on-ramp-left", "\xEE\xA4\x85"},
-    {"on-ramp-right", "\xEE\xA4\x86"},
-    {"on-ramp-sharp-left", "\xEE\xA4\x9B"},
-    {"on-ramp-sharp-right", "\xEE\xA4\xA5"},
-    {"on-ramp-slight-left", "\xEE\xA4\x87"},
-    {"on-ramp-slight-right", "\xEE\xA4\x88"},
-    {"on-ramp-straight", "\xEE\xA4\x84"},
-    {"rotary", "\xEE\xA4\xA1"},
-    {"rotary-left", "\xEE\xA4\xA2"},
-    {"rotary-right", "\xEE\xA4\xA3"},
-    {"rotary-sharp-left", "\xEE\xA4\xA4"},
-    {"rotary-sharp-right", "\xEE\xA4\xA5"},
-    {"rotary-slight-left", "\xEE\xA4\xA6"},
-    {"rotary-slight-right", "\xEE\xA4\xA7"},
-    {"rotary-straight", "\xEE\xA4\xA8"},
-    {"roundabout", "\xEE\xA4\xA1"},
-    {"roundabout-left", "\xEE\xA4\xA2"},
-    {"roundabout-right", "\xEE\xA4\xA3"},
-    {"roundabout-sharp-left", "\xEE\xA4\xA4"},
-    {"roundabout-sharp-right", "\xEE\xA4\xA5"},
-    {"roundabout-slight-left", "\xEE\xA4\xA6"},
-    {"roundabout-slight-right", "\xEE\xA4\xA7"},
-    {"roundabout-straight", "\xEE\xA4\xA8"},
-    {"turn-left", "\xEE\xA4\x85"},
-    {"turn-right", "\xEE\xA4\x86"},
-    {"turn-sharp-left", "\xEE\xA4\x9B"},
-    {"turn-sharp-right", "\xEE\xA4\xA5"},
-    {"turn-slight-left", "\xEE\xA4\x87"},
-    {"turn-slight-right", "\xEE\xA4\x88"},
-    {"turn-straight", "\xEE\xA4\x84"},
-    {"updown", "\xEE\xA4\xA9"},
-    {"uturn", "\xEE\xA4\x89"},
+  struct Icon {
+    const char* fileName;
+    int16_t offset;
+  };
+
+  constexpr uint16_t iconHeight = -80;
+  constexpr uint8_t flagIndex = 18;
+  constexpr uint8_t maxIconsPerFile = 25;
+  const char* iconsFile0 = "F:/images/navigation0.bin";
+  const char* iconsFile1 = "F:/images/navigation1.bin";
+
+  constexpr std::array<std::pair<const char*, uint8_t>, 86> iconMap = {{
+    {"arrive-left", 1},
+    {"arrive-right", 2},
+    {"arrive-straight", 0},
+    {"arrive", 0},
+    {"close", 3},
+    {"continue-left", 5},
+    {"continue-right", 6},
+    {"continue-slight-left", 7},
+    {"continue-slight-right", 8},
+    {"continue-straight", 4},
+    {"continue-uturn", 9},
+    {"continue", 4},
+    {"depart-left", 11},
+    {"depart-right", 12},
+    {"depart-straight", 10},
+    {"end-of-road-left", 13},
+    {"end-of-road-right", 14},
+    {"ferry", 15},
+    {"flag", 16},
+    {"fork-left", 18},
+    {"fork-right", 19},
+    {"fork-slight-left", 20},
+    {"fork-slight-right", 21},
+    {"fork-straight", 22},
+    {"invalid", 4},
+    {"invalid-left", 5},
+    {"invalid-right", 6},
+    {"invalid-slight-left", 7},
+    {"invalid-slight-right", 8},
+    {"invalid-straight", 4},
+    {"invalid-uturn", 9},
+    {"merge-left", 23},
+    {"merge-right", 24},
+    {"merge-slight-left", 25},
+    {"merge-slight-right", 26},
+    {"merge-straight", 4},
+    {"new-name-left", 5},
+    {"new-name-right", 6},
+    {"new-name-sharp-left", 27},
+    {"new-name-sharp-right", 28},
+    {"new-name-slight-left", 7},
+    {"new-name-slight-right", 8},
+    {"new-name-straight", 4},
+    {"notification-left", 5},
+    {"notification-right", 6},
+    {"notification-sharp-left", 27},
+    {"notification-sharp-right", 37},
+    {"notification-slight-left", 7},
+    {"notification-slight-right", 8},
+    {"notification-straight", 4},
+    {"off-ramp-left", 29},
+    {"off-ramp-right", 30},
+    {"off-ramp-slight-left", 31},
+    {"off-ramp-slight-right", 32},
+    {"on-ramp-left", 5},
+    {"on-ramp-right", 6},
+    {"on-ramp-sharp-left", 27},
+    {"on-ramp-sharp-right", 37},
+    {"on-ramp-slight-left", 7},
+    {"on-ramp-slight-right", 8},
+    {"on-ramp-straight", 4},
+    {"rotary", 33},
+    {"rotary-left", 34},
+    {"rotary-right", 35},
+    {"rotary-sharp-left", 36},
+    {"rotary-sharp-right", 37},
+    {"rotary-slight-left", 38},
+    {"rotary-slight-right", 39},
+    {"rotary-straight", 40},
+    {"roundabout", 33},
+    {"roundabout-left", 34},
+    {"roundabout-right", 35},
+    {"roundabout-sharp-left", 36},
+    {"roundabout-sharp-right", 37},
+    {"roundabout-slight-left", 38},
+    {"roundabout-slight-right", 39},
+    {"roundabout-straight", 40},
+    {"turn-left", 5},
+    {"turn-right", 6},
+    {"turn-sharp-left", 27},
+    {"turn-sharp-right", 37},
+    {"turn-slight-left", 7},
+    {"turn-slight-right", 8},
+    {"turn-straight", 4},
+    {"updown", 41},
+    {"uturn", 9},
   }};
 
-  const char* iconForName(const std::string& icon) {
-    for (auto iter : m_iconMap) {
+  Icon GetIcon(uint8_t index) {
+    if (index < maxIconsPerFile) {
+      return {iconsFile0, static_cast<int16_t>(iconHeight * index)};
+    }
+    return {iconsFile1, static_cast<int16_t>(iconHeight * (index - maxIconsPerFile))};
+  }
+
+  Icon GetIcon(const std::string& icon) {
+    for (const auto& iter : iconMap) {
       if (iter.first == icon) {
-        return iter.second;
+        return GetIcon(iter.second);
       }
     }
-    return "\xEE\xA4\x90";
+    return GetIcon(flagIndex);
   }
 }
 
@@ -130,27 +191,33 @@  * Navigation watchapp
  *
  */
 Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navService(nav) {
-
-  imgFlag = lv_label_create(lv_scr_act(), nullptr);
-  lv_obj_set_style_local_text_font(imgFlag, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_navi_80);
-  lv_obj_set_style_local_text_color(imgFlag, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN);
-  lv_label_set_text_static(imgFlag, iconForName("flag"));
-  lv_obj_align(imgFlag, nullptr, LV_ALIGN_IN_TOP_MID, 0, 2);
+  const auto& image = GetIcon("flag");
+  imgFlag = lv_img_create(lv_scr_act(), nullptr);
+  lv_img_set_auto_size(imgFlag, false);
+  lv_obj_set_size(imgFlag, 80, 80);
+  lv_img_set_src(imgFlag, image.fileName);
+  lv_img_set_offset_x(imgFlag, 0);
+  lv_img_set_offset_y(imgFlag, image.offset);
+  lv_obj_set_style_local_image_recolor_opa(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER);
+  lv_obj_set_style_local_image_recolor(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN);
+  lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
 
   txtNarrative = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK);
+  lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT);
   lv_obj_set_width(txtNarrative, LV_HOR_RES);
-  lv_label_set_text(txtNarrative, "Navigation\n\nwatiting for\ninstructions ...");
+  lv_obj_set_height(txtNarrative, 80);
+  lv_label_set_text_static(txtNarrative, "Navigation");
   lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 32);
+  lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
 
   txtManDist = lv_label_create(lv_scr_act(), nullptr);
   lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
   lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
+  lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
   lv_obj_set_width(txtManDist, LV_HOR_RES);
   lv_label_set_text_static(txtManDist, "--M");
   lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(txtManDist, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+  lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
 
   // Route Progress
   barProgress = lv_bar_create(lv_scr_act(), nullptr);
@@ -173,7 +240,11 @@
 void Navigation::Refresh() {
   if (flag != navService.getFlag()) {
     flag = navService.getFlag();
-    lv_label_set_text_static(imgFlag, iconForName(flag));
+    const auto& image = GetIcon(flag);
+    lv_img_set_src(imgFlag, image.fileName);
+    lv_obj_set_style_local_image_recolor_opa(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_COVER);
+    lv_obj_set_style_local_image_recolor(imgFlag, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_CYAN);
+    lv_img_set_offset_y(imgFlag, image.offset);
   }
 
   if (narrative != navService.getNarrative()) {
@@ -196,3 +267,19 @@       lv_obj_set_style_local_bg_color(barProgress, LV_BAR_PART_INDIC, LV_STATE_DEFAULT, Colors::orange);
     }
   }
 }
+
+bool Navigation::IsAvailable(Pinetime::Controllers::FS& filesystem) {
+  lfs_file file = {};
+
+  if (filesystem.FileOpen(&file, "/images/navigation0.bin", LFS_O_RDONLY) < 0) {
+    return false;
+  }
+  filesystem.FileClose(&file);
+
+  if (filesystem.FileOpen(&file, "/images/navigation1.bin", LFS_O_RDONLY) < 0) {
+    return false;
+  }
+  filesystem.FileClose(&file);
+
+  return true;
+}




diff --git a/src/displayapp/screens/Navigation.h b/src/displayapp/screens/Navigation.h
index 6495edb2c3bdf748e393c483ba4d9cc63b6d5f9d..5c7a04299f996e2064a84db25a3c8cc92cadd13d 100644
--- a/src/displayapp/screens/Navigation.h
+++ b/src/displayapp/screens/Navigation.h
@@ -22,20 +22,25 @@ #include 
 #include <string>
 #include "displayapp/screens/Screen.h"
 #include <array>
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
   namespace Controllers {
     class NavigationService;
+    class FS;
   }
 
   namespace Applications {
     namespace Screens {
       class Navigation : public Screen {
       public:
-        Navigation(Pinetime::Controllers::NavigationService& nav);
+        explicit Navigation(Pinetime::Controllers::NavigationService& nav);
         ~Navigation() override;
 
         void Refresh() override;
+        static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
 
       private:
         lv_obj_t* imgFlag;
@@ -48,10 +53,20 @@
         std::string flag;
         std::string narrative;
         std::string manDist;
-        int progress;
+        int progress = 0;
 
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Navigation> {
+      static constexpr Apps app = Apps::Navigation;
+      static constexpr const char* icon = Screens::Symbols::map;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Navigation(*controllers.navigationService);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/Paddle.h b/src/displayapp/screens/Paddle.h
index 33dac191b59378e6e33f41221d9fbb53c220d11d..586cccf4cf478d3a6f9032b4e639405709cfaa52 100644
--- a/src/displayapp/screens/Paddle.h
+++ b/src/displayapp/screens/Paddle.h
@@ -3,6 +3,9 @@
 #include <lvgl/lvgl.h>
 #include <cstdint>
 #include "displayapp/screens/Screen.h"
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
   namespace Components {
@@ -45,5 +48,15 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Paddle> {
+      static constexpr Apps app = Apps::Paddle;
+      static constexpr const char* icon = Screens::Symbols::paddle;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Paddle(controllers.lvgl);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/Steps.h b/src/displayapp/screens/Steps.h
index 5dc07eff947d7e8e4bc4cd94ac6f7f6d32f05be9..6443582f5efe3147d85703c49ef859da4865a3b8 100644
--- a/src/displayapp/screens/Steps.h
+++ b/src/displayapp/screens/Steps.h
@@ -4,6 +4,9 @@ #include 
 #include <lvgl/lvgl.h>
 #include "displayapp/screens/Screen.h"
 #include <components/motion/MotionController.h>
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
 namespace Pinetime {
 
@@ -39,5 +42,15 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Steps> {
+      static constexpr Apps app = Apps::Steps;
+      static constexpr const char* icon = Screens::Symbols::shoe;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Steps(controllers.motionController, controllers.settingsController);
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/StopWatch.cpp b/src/displayapp/screens/StopWatch.cpp
index 239ebe393486d30af26b10369224c2edfe8399e4..f0359da4d468003dc4b072268c7674aeed7c06a6 100644
--- a/src/displayapp/screens/StopWatch.cpp
+++ b/src/displayapp/screens/StopWatch.cpp
@@ -198,11 +198,11 @@         lv_label_ins_text(lapText, LV_LABEL_POS_LAST, "\n");
         continue;
       }
       TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
-      char buffer[16];
+      char buffer[17];
       if (times.hours == 0) {
-        sprintf(buffer, "#%2d   %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
+        snprintf(buffer, sizeof(buffer), "#%2d    %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
       } else {
-        sprintf(buffer, "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths);
+        snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths);
       }
       lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer);
     }




diff --git a/src/displayapp/screens/StopWatch.h b/src/displayapp/screens/StopWatch.h
index d1acd1624e63f657c62d898ccfb21c4b710d3d3c..3386d042b8b6808afbff261a9e311be3d7216517 100644
--- a/src/displayapp/screens/StopWatch.h
+++ b/src/displayapp/screens/StopWatch.h
@@ -7,50 +7,67 @@ #include 
 #include "portmacro_cmsis.h"
 
 #include "systemtask/SystemTask.h"
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
 
-namespace Pinetime::Applications::Screens {
+namespace Pinetime {
+  namespace Applications {
+    namespace Screens {
 
-  enum class States { Init, Running, Halted };
+      enum class States { Init, Running, Halted };
 
-  struct TimeSeparated_t {
-    int hours;
-    int mins;
-    int secs;
-    int hundredths;
-  };
+      struct TimeSeparated_t {
+        int hours;
+        int mins;
+        int secs;
+        int hundredths;
+      };
 
-  class StopWatch : public Screen {
-  public:
-    explicit StopWatch(System::SystemTask& systemTask);
-    ~StopWatch() override;
-    void Refresh() override;
+      class StopWatch : public Screen {
+      public:
+        explicit StopWatch(System::SystemTask& systemTask);
+        ~StopWatch() override;
+        void Refresh() override;
+
+        void playPauseBtnEventHandler();
+        void stopLapBtnEventHandler();
+        bool OnButtonPushed() override;
+
+      private:
+        void SetInterfacePaused();
+        void SetInterfaceRunning();
+        void SetInterfaceStopped();
 
-    void playPauseBtnEventHandler();
-    void stopLapBtnEventHandler();
-    bool OnButtonPushed() override;
+        void Reset();
+        void Start();
+        void Pause();
 
-  private:
-    void SetInterfacePaused();
-    void SetInterfaceRunning();
-    void SetInterfaceStopped();
+        Pinetime::System::SystemTask& systemTask;
+        States currentState = States::Init;
+        TickType_t startTime;
+        TickType_t oldTimeElapsed = 0;
+        TickType_t blinkTime = 0;
+        static constexpr int maxLapCount = 20;
+        TickType_t laps[maxLapCount + 1];
+        static constexpr int displayedLaps = 2;
+        int lapsDone = 0;
+        lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap;
+        lv_obj_t* lapText;
+        bool isHoursLabelUpdated = false;
 
-    void Reset();
-    void Start();
-    void Pause();
+        lv_task_t* taskRefresh;
+      };
+    }
 
-    Pinetime::System::SystemTask& systemTask;
-    States currentState = States::Init;
-    TickType_t startTime;
-    TickType_t oldTimeElapsed = 0;
-    TickType_t blinkTime = 0;
-    static constexpr int maxLapCount = 20;
-    TickType_t laps[maxLapCount + 1];
-    static constexpr int displayedLaps = 2;
-    int lapsDone = 0;
-    lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap;
-    lv_obj_t* lapText;
-    bool isHoursLabelUpdated = false;
+    template <>
+    struct AppTraits<Apps::StopWatch> {
+      static constexpr Apps app = Apps::StopWatch;
+      static constexpr const char* icon = Screens::Symbols::stopWatch;
 
-    lv_task_t* taskRefresh;
-  };
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::StopWatch(*controllers.systemTask);
+      };
+    };
+  }
 }




diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h
index 7154ff44c1c86ab06e905e85b1c1bd866b866b60..549ea04ff0882b9bb3d390d94214a065fa6a2d1a 100644
--- a/src/displayapp/screens/Symbols.h
+++ b/src/displayapp/screens/Symbols.h
@@ -45,6 +45,9 @@         static constexpr const char* cloudSunRain = "\xEF\x9D\x83";
         static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
         static constexpr const char* smog = "\xEF\x9D\x9F";
         static constexpr const char* cloud = "\xEF\x83\x82";
+        static constexpr const char* cloudMeatball = "\xEF\x9C\xBB";
+        static constexpr const char* bolt = "\xEF\x83\xA7";
+        static constexpr const char* snowflake = "\xEF\x8B\x9C";
         static constexpr const char* ban = "\xEF\x81\x9E";
 
         // lv_font_sys_48.c




diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp
index eea7593800e752c59bdb69b82bccc9cf5a6ef7b9..18fb7ad2083446fd392a928c3e5e4965df051b7b 100644
--- a/src/displayapp/screens/SystemInfo.cpp
+++ b/src/displayapp/screens/SystemInfo.cpp
@@ -236,9 +236,9 @@
   auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
   std::sort(tasksStatus, tasksStatus + nb, sortById);
   for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
-    char buffer[7] = {0};
+    char buffer[11] = {0};
 
-    sprintf(buffer, "%lu", tasksStatus[i].xTaskNumber);
+    snprintf(buffer, sizeof(buffer), "%lu", tasksStatus[i].xTaskNumber);
     lv_table_set_cell_value(infoTask, i + 1, 0, buffer);
     switch (tasksStatus[i].eCurrentState) {
       case eReady:
@@ -262,9 +262,9 @@     buffer[1] = '\0';
     lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
     lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
     if (tasksStatus[i].usStackHighWaterMark < 20) {
-      sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark);
+      snprintf(buffer, sizeof(buffer), "%" PRIu16 " low", tasksStatus[i].usStackHighWaterMark);
     } else {
-      sprintf(buffer, "%d", tasksStatus[i].usStackHighWaterMark);
+      snprintf(buffer, sizeof(buffer), "%" PRIu16, tasksStatus[i].usStackHighWaterMark);
     }
     lv_table_set_cell_value(infoTask, i + 1, 3, buffer);
   }




diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp
index 1266f379f80d4fb228583da2fbe9d7cd2a1d1049..7c392c59e56b37245c29f070db83dba8c5c2654c 100644
--- a/src/displayapp/screens/Tile.cpp
+++ b/src/displayapp/screens/Tile.cpp
@@ -1,5 +1,4 @@
 #include "displayapp/screens/Tile.h"
-#include "displayapp/DisplayApp.h"
 #include "displayapp/screens/BatteryIcon.h"
 #include "components/ble/BleController.h"
 #include "displayapp/InfiniTimeTheme.h"
@@ -76,7 +75,7 @@   lv_obj_set_style_local_pad_inner(btnm1, LV_BTNMATRIX_PART_BG, LV_STATE_DEFAULT, 10);
 
   for (uint8_t i = 0; i < 6; i++) {
     lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_CLICK_TRIG);
-    if (applications[i].application == Apps::None) {
+    if (applications[i].application == Apps::None || !applications[i].enabled) {
       lv_btnmatrix_set_btn_ctrl(btnm1, i, LV_BTNMATRIX_CTRL_DISABLED);
     }
   }




diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h
index 91acb26c7b0458bf18f19f4275292f9d7360b0e9..f1b86246cec4c5b9497cc3aa422b0537d96e4e72 100644
--- a/src/displayapp/screens/Tile.h
+++ b/src/displayapp/screens/Tile.h
@@ -4,7 +4,7 @@ #include 
 #include <cstdint>
 #include <memory>
 #include "displayapp/screens/Screen.h"
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "components/datetime/DateTimeController.h"
 #include "components/settings/Settings.h"
 #include "components/battery/BatteryController.h"
@@ -19,6 +19,7 @@       public:
         struct Applications {
           const char* icon;
           Pinetime::Applications::Apps application;
+          bool enabled;
         };
 
         explicit Tile(uint8_t screenID,




diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp
index d94887404e81955677c2c22630b03da03b34141c..a1ede6bed6d2a3c34c5d579e88f57d76f2bd2260 100644
--- a/src/displayapp/screens/Timer.cpp
+++ b/src/displayapp/screens/Timer.cpp
@@ -62,7 +62,7 @@
   txtPlayPause = lv_label_create(lv_scr_act(), nullptr);
   lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
 
-  if (timerController.IsRunning()) {
+  if (timer.IsRunning()) {
     SetTimerRunning();
   } else {
     SetTimerStopped();




diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h
index e452a9d9dcfc75d725e3f7e8aaaf1c9d3e9a64a6..409cae1cd488e15896d0a0c180bcf96835a36e21 100644
--- a/src/displayapp/screens/Timer.h
+++ b/src/displayapp/screens/Timer.h
@@ -8,38 +8,51 @@ #include "displayapp/widgets/Counter.h"
 #include <lvgl/lvgl.h>
 
 #include "components/timer/Timer.h"
+#include "Symbols.h"
 
-namespace Pinetime::Applications::Screens {
-  class Timer : public Screen {
-  public:
-    Timer(Controllers::Timer& timerController);
-    ~Timer() override;
-    void Refresh() override;
-    void Reset();
-    void ToggleRunning();
-    void ButtonPressed();
-    void MaskReset();
+namespace Pinetime::Applications {
+  namespace Screens {
+    class Timer : public Screen {
+    public:
+      Timer(Controllers::Timer& timerController);
+      ~Timer() override;
+      void Refresh() override;
+      void Reset();
+      void ToggleRunning();
+      void ButtonPressed();
+      void MaskReset();
 
-  private:
-    void SetTimerRunning();
-    void SetTimerStopped();
-    void UpdateMask();
-    Controllers::Timer& timer;
+    private:
+      void SetTimerRunning();
+      void SetTimerStopped();
+      void UpdateMask();
+      Pinetime::Controllers::Timer& timer;
+
+      lv_obj_t* btnPlayPause;
+      lv_obj_t* txtPlayPause;
+
+      lv_obj_t* btnObjectMask;
+      lv_obj_t* highlightObjectMask;
+      lv_objmask_mask_t* btnMask;
+      lv_objmask_mask_t* highlightMask;
 
-    lv_obj_t* btnPlayPause;
-    lv_obj_t* txtPlayPause;
+      lv_task_t* taskRefresh;
+      Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
+      Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
 
-    lv_obj_t* btnObjectMask;
-    lv_obj_t* highlightObjectMask;
-    lv_objmask_mask_t* btnMask;
-    lv_objmask_mask_t* highlightMask;
+      bool buttonPressing = false;
+      lv_coord_t maskPosition = 0;
+      TickType_t pressTime = 0;
+    };
+  }
 
-    lv_task_t* taskRefresh;
-    Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
-    Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
+  template <>
+  struct AppTraits<Apps::Timer> {
+    static constexpr Apps app = Apps::Timer;
+    static constexpr const char* icon = Screens::Symbols::hourGlass;
 
-    bool buttonPressing = false;
-    lv_coord_t maskPosition = 0;
-    TickType_t pressTime = 0;
+    static Screens::Screen* Create(AppControllers& controllers) {
+      return new Screens::Timer(controllers.timer);
+    };
   };
 }




diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp
index 8157a160b9987a45a3014ac42128b4d56afa93ce..6f2eff40c4d7129366a65209405995b6909f1698 100644
--- a/src/displayapp/screens/Twos.cpp
+++ b/src/displayapp/screens/Twos.cpp
@@ -242,7 +242,7 @@     const unsigned int row = i / nCols;
     const unsigned int col = i % nCols;
     if (grid[row][col].value > 0) {
       char buffer[7];
-      sprintf(buffer, "%d", grid[row][col].value);
+      snprintf(buffer, sizeof(buffer), "%u", grid[row][col].value);
       lv_table_set_cell_value(gridDisplay, row, col, buffer);
     } else {
       lv_table_set_cell_value(gridDisplay, row, col, "");




diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h
index e731eae62f591e85b1a8eb0ad68b7b2929d317c3..52449fd36c0a043d7e3fdd39d943047ab62d2b07 100644
--- a/src/displayapp/screens/Twos.h
+++ b/src/displayapp/screens/Twos.h
@@ -1,7 +1,8 @@
 #pragma once
 
-#include <lvgl/src/lv_core/lv_obj.h>
+#include "displayapp/apps/Apps.h"
 #include "displayapp/screens/Screen.h"
+#include "displayapp/Controllers.h"
 
 namespace Pinetime {
   namespace Applications {
@@ -35,5 +36,15 @@         bool tryMove(int newRow, int newCol, int oldRow, int oldCol);
         bool placeNewTile();
       };
     }
+
+    template <>
+    struct AppTraits<Apps::Twos> {
+      static constexpr Apps app = Apps::Twos;
+      static constexpr const char* icon = "2";
+
+      static Screens::Screen* Create(AppControllers& /*controllers*/) {
+        return new Screens::Twos();
+      };
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFaceCasioStyleG7710.h b/src/displayapp/screens/WatchFaceCasioStyleG7710.h
index f84db7b548bb184fa6afe663922e649f26dd2be4..f10e931ce4607a58398e4a0ce595e487df6de248 100644
--- a/src/displayapp/screens/WatchFaceCasioStyleG7710.h
+++ b/src/displayapp/screens/WatchFaceCasioStyleG7710.h
@@ -5,10 +5,12 @@ #include 
 #include <chrono>
 #include <cstdint>
 #include <memory>
+#include <displayapp/Controllers.h>
 #include "displayapp/screens/Screen.h"
 #include "components/datetime/DateTimeController.h"
 #include "components/ble/BleController.h"
 #include "utility/DirtyValue.h"
+#include "displayapp/apps/Apps.h"
 
 namespace Pinetime {
   namespace Controllers {
@@ -100,5 +102,26 @@         lv_font_t* font_segment40 = nullptr;
         lv_font_t* font_segment115 = nullptr;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::CasioStyleG7710> {
+      static constexpr WatchFace watchFace = WatchFace::CasioStyleG7710;
+      static constexpr const char* name = "Casio G7710";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController,
+                                                     controllers.batteryController,
+                                                     controllers.bleController,
+                                                     controllers.notificationManager,
+                                                     controllers.settingsController,
+                                                     controllers.heartRateController,
+                                                     controllers.motionController,
+                                                     controllers.filesystem);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
+        return Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem);
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h
index f2409880e981b4eeb17b816c713fe51c466d370a..a4664792c050a34d5d7ce16070cdb4313e427ba4 100644
--- a/src/displayapp/screens/WatchFaceDigital.h
+++ b/src/displayapp/screens/WatchFaceDigital.h
@@ -9,6 +9,7 @@ #include "components/datetime/DateTimeController.h"
 #include "components/ble/BleController.h"
 #include "displayapp/widgets/StatusIcons.h"
 #include "utility/DirtyValue.h"
+#include "displayapp/apps/Apps.h"
 
 namespace Pinetime {
   namespace Controllers {
@@ -71,5 +72,25 @@         lv_task_t* taskRefresh;
         Widgets::StatusIcons statusIcons;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::Digital> {
+      static constexpr WatchFace watchFace = WatchFace::Digital;
+      static constexpr const char* name = "Digital face";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceDigital(controllers.dateTimeController,
+                                             controllers.batteryController,
+                                             controllers.bleController,
+                                             controllers.notificationManager,
+                                             controllers.settingsController,
+                                             controllers.heartRateController,
+                                             controllers.motionController);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
+        return true;
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFaceInfineat.h b/src/displayapp/screens/WatchFaceInfineat.h
index 33532abb8e910da8d66a833fb8c9a4124e662165..32c08f1887089565bda31105c0b4477f70ad4eac 100644
--- a/src/displayapp/screens/WatchFaceInfineat.h
+++ b/src/displayapp/screens/WatchFaceInfineat.h
@@ -4,9 +4,11 @@ #include 
 #include <chrono>
 #include <cstdint>
 #include <memory>
+#include <displayapp/Controllers.h>
 #include "displayapp/screens/Screen.h"
 #include "components/datetime/DateTimeController.h"
 #include "utility/DirtyValue.h"
+#include "displayapp/apps/Apps.h"
 
 namespace Pinetime {
   namespace Controllers {
@@ -98,5 +100,25 @@         lv_font_t* font_teko = nullptr;
         lv_font_t* font_bebas = nullptr;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::Infineat> {
+      static constexpr WatchFace watchFace = WatchFace::Infineat;
+      static constexpr const char* name = "Infineat face";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceInfineat(controllers.dateTimeController,
+                                              controllers.batteryController,
+                                              controllers.bleController,
+                                              controllers.notificationManager,
+                                              controllers.settingsController,
+                                              controllers.motionController,
+                                              controllers.filesystem);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
+        return Screens::WatchFaceInfineat::IsAvailable(filesystem);
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFaceMine.cpp b/src/displayapp/screens/WatchFaceMine.cpp
index b0b6cf5ce318ba8916d83e2f13f37eb66ef8ca65..6ccc4ac00e368cf532dca2e556d8f53474dbae25 100644
--- a/src/displayapp/screens/WatchFaceMine.cpp
+++ b/src/displayapp/screens/WatchFaceMine.cpp
@@ -7,7 +7,8 @@ #include "displayapp/screens/BleIcon.h"
 #include "displayapp/screens/Symbols.h"
 #include "displayapp/screens/NotificationIcon.h"
 #include "components/settings/Settings.h"
-#include "components/ble/weather/WeatherService.h"
+#include "displayapp/screens/WeatherSymbols.h"
+#include "components/ble/SimpleWeatherService.h"
 #include "displayapp/InfiniTimeTheme.h"
 
 using namespace Pinetime::Applications::Screens;
@@ -50,7 +51,7 @@                                  const Controllers::Ble& bleController,
                                  Controllers::NotificationManager& notificationManager,
                                  Controllers::Settings& settingsController,
                                  Controllers::MotionController& motionController,
-                                 Controllers::WeatherService& weatherService)
+                                 Controllers::SimpleWeatherService& weatherService)
   : currentDateTime {{}},
     dateTimeController {dateTimeController},
     batteryController {batteryController},
@@ -319,25 +320,14 @@   }
 }
 
 void WatchFaceMine::DrawWeather() {  // from https://github.com/InfiniTimeOrg/InfiniTime/pull/1856
-  if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 &&
-      weatherService.GetCurrentPrecipitation()->timestamp != 0) {
-    nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
-    clouds = (weatherService.GetCurrentClouds()->amount);
-    precip = (weatherService.GetCurrentPrecipitation()->amount);
-    if (nowTemp.IsUpdated()) {
-      std::string hawaii = decimalToDozenal(1.8 * nowTemp.Get() - 36);
+  currentWeather = weatherService.Current();
+  if (currentWeather.IsUpdated()) {
+    auto optCurrentWeather = currentWeather.Get();
+    if (optCurrentWeather) {
+      auto temp = optCurrentWeather->temperature / 100;
+      std::string hawaii = decimalToDozenal(1.8 * temp - 36);
       lv_label_set_text_fmt(temperature, "%s", hawaii.c_str());
-      if ((clouds <= 30) && (precip == 0)) {
-        lv_label_set_text(weatherIcon, Symbols::sun);
-      } else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) {
-        lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
-      } else if ((clouds > 90) && (precip == 0)) {
-        lv_label_set_text(weatherIcon, Symbols::cloud);
-      } else if ((clouds > 70) && (precip >= 2)) {
-        lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy);
-      } else {
-        lv_label_set_text(weatherIcon, Symbols::cloudSun);
-      };
+      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
       lv_obj_realign(temperature);
       lv_obj_realign(weatherIcon);
     }




diff --git a/src/displayapp/screens/WatchFaceMine.h b/src/displayapp/screens/WatchFaceMine.h
index 986e930b41d66e7c93753ea541e124c761f49c30..34170882eff02e869833556d2df07fc490264e77 100644
--- a/src/displayapp/screens/WatchFaceMine.h
+++ b/src/displayapp/screens/WatchFaceMine.h
@@ -8,7 +8,7 @@ #include "components/datetime/DateTimeController.h"
 #include "components/battery/BatteryController.h"
 #include "components/ble/BleController.h"
 #include "components/ble/NotificationManager.h"
-#include "components/ble/weather/WeatherService.h"
+#include "components/ble/SimpleWeatherService.h"
 #include "displayapp/screens/BatteryIcon.h"
 #include "utility/DirtyValue.h"
 
@@ -31,7 +31,7 @@                         const Controllers::Ble& bleController,
                         Controllers::NotificationManager& notificationManager,
                         Controllers::Settings& settingsController,
                         Controllers::MotionController& motionController,
-                        Controllers::WeatherService& weather);
+                        Controllers::SimpleWeatherService& weather);
 
         ~WatchFaceMine() override;
 
@@ -56,6 +56,8 @@         Utility::DirtyValue stepCount {};
         Utility::DirtyValue<uint32_t> stepGoal {};
 
         Utility::DirtyValue<int8_t> worldClockOffset {};
+
+        Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
 
         lv_obj_t* tick[24];
         lv_point_t tick_point[24][2];
@@ -110,7 +112,7 @@         const Controllers::Ble& bleController;
         Controllers::NotificationManager& notificationManager;
         Controllers::Settings& settingsController;
         Controllers::MotionController& motionController;
-        Controllers::WeatherService& weatherService;
+        Controllers::SimpleWeatherService& weatherService;
 
         void DrawFace();
         void DrawOffsetHands(uint8_t, uint8_t, uint8_t, uint8_t);
@@ -126,5 +128,25 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::Mine> {
+      static constexpr WatchFace watchFace = WatchFace::Mine;
+      static constexpr const char* name = "mine";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceMine(controllers.dateTimeController,
+                                                   controllers.batteryController,
+                                                   controllers.bleController,
+                                                   controllers.notificationManager,
+                                                   controllers.settingsController,
+                                                   controllers.motionController,
+                                                   *controllers.weatherController);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
+        return true;
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp
index 250a745c1551f4cb64dbb47dd595ebf95ff919c8..296323d38eb087123687de1d8973c6c4e14f9ea5 100644
--- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp
+++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp
@@ -27,13 +27,14 @@ #include "displayapp/screens/BatteryIcon.h"
 #include "displayapp/screens/BleIcon.h"
 #include "displayapp/screens/NotificationIcon.h"
 #include "displayapp/screens/Symbols.h"
+#include "displayapp/screens/WeatherSymbols.h"
 #include "components/battery/BatteryController.h"
 #include "components/ble/BleController.h"
 #include "components/ble/NotificationManager.h"
 #include "components/motion/MotionController.h"
 #include "components/settings/Settings.h"
 #include "displayapp/DisplayApp.h"
-#include "components/ble/weather/WeatherService.h"
+#include "components/ble/SimpleWeatherService.h"
 
 using namespace Pinetime::Applications::Screens;
 
@@ -50,7 +51,7 @@                                                const Controllers::Ble& bleController,
                                                Controllers::NotificationManager& notificationManager,
                                                Controllers::Settings& settingsController,
                                                Controllers::MotionController& motionController,
-                                               Controllers::WeatherService& weatherService)
+                                               Controllers::SimpleWeatherService& weatherService)
   : currentDateTime {{}},
     batteryIcon(false),
     dateTimeController {dateTimeController},
@@ -115,7 +116,7 @@
   weatherIcon = lv_label_create(lv_scr_act(), nullptr);
   lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
   lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
-  lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
+  lv_label_set_text(weatherIcon, Symbols::ban);
   lv_obj_align(weatherIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 35);
   lv_obj_set_auto_realign(weatherIcon, true);
   if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
@@ -126,6 +127,7 @@   }
 
   temperature = lv_label_create(lv_scr_act(), nullptr);
   lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
+  lv_label_set_text(temperature, "--");
   lv_obj_align(temperature, sidebar, LV_ALIGN_IN_TOP_MID, 0, 65);
   if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
     lv_obj_set_hidden(temperature, false);
@@ -537,29 +539,23 @@       lv_obj_set_style_local_scale_grad_color(stepGauge, LV_GAUGE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
     }
   }
 
-  if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 &&
-      weatherService.GetCurrentPrecipitation()->timestamp != 0) {
-    nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
-    clouds = (weatherService.GetCurrentClouds()->amount);
-    precip = (weatherService.GetCurrentPrecipitation()->amount);
-    if (nowTemp.IsUpdated()) {
-      lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get());
-      if ((clouds <= 30) && (precip == 0)) {
-        lv_label_set_text(weatherIcon, Symbols::sun);
-      } else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) {
-        lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
-      } else if ((clouds > 90) && (precip == 0)) {
-        lv_label_set_text(weatherIcon, Symbols::cloud);
-      } else if ((clouds > 70) && (precip >= 2)) {
-        lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy);
-      } else {
-        lv_label_set_text(weatherIcon, Symbols::cloudSun);
-      };
+  currentWeather = weatherService.Current();
+
+  if (currentWeather.IsUpdated()) {
+    auto optCurrentWeather = currentWeather.Get();
+    if (optCurrentWeather) {
+      int16_t temp = optCurrentWeather->temperature;
+      if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
+        temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
+      }
+      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
+      lv_label_set_text_fmt(temperature, "%d°", temp);
+      lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
       lv_obj_realign(temperature);
       lv_obj_realign(weatherIcon);
     }
   } else {
-    lv_label_set_text_static(temperature, "--");
+    lv_label_set_text(temperature, "--");
     lv_label_set_text(weatherIcon, Symbols::ban);
     lv_obj_realign(temperature);
     lv_obj_realign(weatherIcon);




diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.h b/src/displayapp/screens/WatchFacePineTimeStyle.h
index e157bb2cd2ce682f42b45d35fa3233c41710be75..72537095f9a5fb11cc48c5ee0f2b55e01ac7510d 100644
--- a/src/displayapp/screens/WatchFacePineTimeStyle.h
+++ b/src/displayapp/screens/WatchFacePineTimeStyle.h
@@ -4,11 +4,12 @@ #include 
 #include <chrono>
 #include <cstdint>
 #include <memory>
+#include <displayapp/Controllers.h>
 #include "displayapp/screens/Screen.h"
 #include "displayapp/screens/BatteryIcon.h"
 #include "displayapp/Colors.h"
 #include "components/datetime/DateTimeController.h"
-#include "components/ble/weather/WeatherService.h"
+#include "components/ble/SimpleWeatherService.h"
 #include "components/ble/BleController.h"
 #include "utility/DirtyValue.h"
 
@@ -32,7 +33,7 @@                                const Controllers::Ble& bleController,
                                Controllers::NotificationManager& notificationManager,
                                Controllers::Settings& settingsController,
                                Controllers::MotionController& motionController,
-                               Controllers::WeatherService& weather);
+                               Controllers::SimpleWeatherService& weather);
         ~WatchFacePineTimeStyle() override;
 
         bool OnTouchEvent(TouchEvents event) override;
@@ -60,9 +61,7 @@         Utility::DirtyValue bleRadioEnabled {};
         Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
         Utility::DirtyValue<uint32_t> stepCount {};
         Utility::DirtyValue<bool> notificationState {};
-        Utility::DirtyValue<int16_t> nowTemp {};
-        int16_t clouds = 0;
-        int16_t precip = 0;
+        Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
 
         static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
         static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
@@ -113,7 +112,7 @@         const Controllers::Ble& bleController;
         Controllers::NotificationManager& notificationManager;
         Controllers::Settings& settingsController;
         Controllers::MotionController& motionController;
-        Controllers::WeatherService& weatherService;
+        Controllers::SimpleWeatherService& weatherService;
 
         void SetBatteryIcon();
         void CloseMenu();
@@ -121,5 +120,25 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::PineTimeStyle> {
+      static constexpr WatchFace watchFace = WatchFace::PineTimeStyle;
+      static constexpr const char* name = "PineTimeStyle";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFacePineTimeStyle(controllers.dateTimeController,
+                                                   controllers.batteryController,
+                                                   controllers.bleController,
+                                                   controllers.notificationManager,
+                                                   controllers.settingsController,
+                                                   controllers.motionController,
+                                                   *controllers.weatherController);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
+        return true;
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/WatchFaceTerminal.h b/src/displayapp/screens/WatchFaceTerminal.h
index d08a146b065d634a8772e1ad1f08e8aeb43de25a..ce22005fb9619df2d35d8097553fa7c6489c803f 100644
--- a/src/displayapp/screens/WatchFaceTerminal.h
+++ b/src/displayapp/screens/WatchFaceTerminal.h
@@ -4,6 +4,7 @@ #include 
 #include <chrono>
 #include <cstdint>
 #include <memory>
+#include <displayapp/Controllers.h>
 #include "displayapp/screens/Screen.h"
 #include "components/datetime/DateTimeController.h"
 #include "utility/DirtyValue.h"
@@ -68,5 +69,25 @@
         lv_task_t* taskRefresh;
       };
     }
+
+    template <>
+    struct WatchFaceTraits<WatchFace::Terminal> {
+      static constexpr WatchFace watchFace = WatchFace::Terminal;
+      static constexpr const char* name = "Terminal";
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::WatchFaceTerminal(controllers.dateTimeController,
+                                              controllers.batteryController,
+                                              controllers.bleController,
+                                              controllers.notificationManager,
+                                              controllers.settingsController,
+                                              controllers.heartRateController,
+                                              controllers.motionController);
+      };
+
+      static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
+        return true;
+      }
+    };
   }
 }




diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
deleted file mode 100644
index 4921174c7a840a28407cd612676ba5081f634207..0000000000000000000000000000000000000000
--- a/src/displayapp/screens/Weather.cpp
+++ /dev/null
@@ -1,221 +0,0 @@
-/*  Copyright (C) 2021 Avamander
-
-    This file is part of InfiniTime.
-
-    InfiniTime is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published
-    by the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    InfiniTime is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-#include "Weather.h"
-#include <lvgl/lvgl.h>
-#include <components/ble/weather/WeatherService.h>
-#include "Label.h"
-#include "components/battery/BatteryController.h"
-#include "components/ble/BleController.h"
-#include "components/ble/weather/WeatherData.h"
-
-using namespace Pinetime::Applications::Screens;
-
-Weather::Weather(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::WeatherService& weather)
-  : app {app},
-    weatherService(weather),
-    screens {app,
-             0,
-             {[this]() -> std::unique_ptr<Screen> {
-                return CreateScreenTemperature();
-              },
-              [this]() -> std::unique_ptr<Screen> {
-                return CreateScreenAir();
-              },
-              [this]() -> std::unique_ptr<Screen> {
-                return CreateScreenClouds();
-              },
-              [this]() -> std::unique_ptr<Screen> {
-                return CreateScreenPrecipitation();
-              },
-              [this]() -> std::unique_ptr<Screen> {
-                return CreateScreenHumidity();
-              }},
-             Screens::ScreenListModes::UpDown} {
-}
-
-Weather::~Weather() {
-  lv_obj_clean(lv_scr_act());
-}
-
-void Weather::Refresh() {
-  if (running) {
-    // screens.Refresh();
-  }
-}
-
-bool Weather::OnButtonPushed() {
-  running = false;
-  return true;
-}
-
-bool Weather::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
-  return screens.OnTouchEvent(event);
-}
-
-std::unique_ptr<Screen> Weather::CreateScreenTemperature() {
-  lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_recolor(label, true);
-  std::unique_ptr<Controllers::WeatherData::Temperature>& current = weatherService.GetCurrentTemperature();
-  if (current->timestamp == 0) {
-    // Do not use the data, it's invalid
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Temperature#\n\n"
-                          "#444444 %d#°C \n\n"
-                          "#444444 %d#\n\n"
-                          "%d\n"
-                          "%d\n",
-                          0,
-                          0,
-                          0,
-                          0);
-  } else {
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Temperature#\n\n"
-                          "#444444 %d#°C \n\n"
-                          "#444444 %hd#\n\n"
-                          "%llu\n"
-                          "%lu\n",
-                          current->temperature / 100,
-                          current->dewPoint,
-                          current->timestamp,
-                          current->expires);
-  }
-  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
-  return std::unique_ptr<Screen>(new Screens::Label(0, 5, label));
-}
-
-std::unique_ptr<Screen> Weather::CreateScreenAir() {
-  lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_recolor(label, true);
-  std::unique_ptr<Controllers::WeatherData::AirQuality>& current = weatherService.GetCurrentQuality();
-  if (current->timestamp == 0) {
-    // Do not use the data, it's invalid
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Air quality#\n\n"
-                          "#444444 %s#\n"
-                          "#444444 %d#\n\n"
-                          "%d\n"
-                          "%d\n",
-                          "",
-                          0,
-                          0,
-                          0);
-  } else {
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Air quality#\n\n"
-                          "#444444 %s#\n"
-                          "#444444 %lu#\n\n"
-                          "%llu\n"
-                          "%lu\n",
-                          current->polluter.c_str(),
-                          (current->amount / 100),
-                          current->timestamp,
-                          current->expires);
-  }
-  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
-  return std::unique_ptr<Screen>(new Screens::Label(0, 5, label));
-}
-
-std::unique_ptr<Screen> Weather::CreateScreenClouds() {
-  lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_recolor(label, true);
-  std::unique_ptr<Controllers::WeatherData::Clouds>& current = weatherService.GetCurrentClouds();
-  if (current->timestamp == 0) {
-    // Do not use the data, it's invalid
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Clouds#\n\n"
-                          "#444444 %d%%#\n\n"
-                          "%d\n"
-                          "%d\n",
-                          0,
-                          0,
-                          0);
-  } else {
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Clouds#\n\n"
-                          "#444444 %hhu%%#\n\n"
-                          "%llu\n"
-                          "%lu\n",
-                          current->amount,
-                          current->timestamp,
-                          current->expires);
-  }
-  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
-  return std::unique_ptr<Screen>(new Screens::Label(0, 5, label));
-}
-
-std::unique_ptr<Screen> Weather::CreateScreenPrecipitation() {
-  lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_recolor(label, true);
-  std::unique_ptr<Controllers::WeatherData::Precipitation>& current = weatherService.GetCurrentPrecipitation();
-  if (current->timestamp == 0) {
-    // Do not use the data, it's invalid
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Precipitation#\n\n"
-                          "#444444 %d%%#\n\n"
-                          "%d\n"
-                          "%d\n",
-                          0,
-                          0,
-                          0);
-  } else {
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Precipitation#\n\n"
-                          "#444444 %hhu%%#\n\n"
-                          "%llu\n"
-                          "%lu\n",
-                          current->amount,
-                          current->timestamp,
-                          current->expires);
-  }
-  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
-  return std::unique_ptr<Screen>(new Screens::Label(0, 5, label));
-}
-
-std::unique_ptr<Screen> Weather::CreateScreenHumidity() {
-  lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
-  lv_label_set_recolor(label, true);
-  std::unique_ptr<Controllers::WeatherData::Humidity>& current = weatherService.GetCurrentHumidity();
-  if (current->timestamp == 0) {
-    // Do not use the data, it's invalid
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Humidity#\n\n"
-                          "#444444 %d%%#\n\n"
-                          "%d\n"
-                          "%d\n",
-                          0,
-                          0,
-                          0);
-  } else {
-    lv_label_set_text_fmt(label,
-                          "#FFFF00 Humidity#\n\n"
-                          "#444444 %hhu%%#\n\n"
-                          "%llu\n"
-                          "%lu\n",
-                          current->humidity,
-                          current->timestamp,
-                          current->expires);
-  }
-  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
-  lv_obj_align(label, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
-  return std::unique_ptr<Screen>(new Screens::Label(0, 5, label));
-}




diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h
deleted file mode 100644
index 459534aa2a145188b58bf5c4f456e246145f46e7..0000000000000000000000000000000000000000
--- a/src/displayapp/screens/Weather.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <components/ble/weather/WeatherService.h>
-#include "Screen.h"
-#include "ScreenList.h"
-
-namespace Pinetime {
-  namespace Applications {
-    class DisplayApp;
-
-    namespace Screens {
-      class Weather : public Screen {
-      public:
-        explicit Weather(DisplayApp* app, Pinetime::Controllers::WeatherService& weather);
-
-        ~Weather() override;
-
-        void Refresh() override;
-
-        bool OnButtonPushed() override;
-
-        bool OnTouchEvent(TouchEvents event) override;
-
-      private:
-        DisplayApp* app;
-        bool running = true;
-
-        Controllers::WeatherService& weatherService;
-
-        ScreenList<5> screens;
-
-        std::unique_ptr<Screen> CreateScreenTemperature();
-
-        std::unique_ptr<Screen> CreateScreenAir();
-
-        std::unique_ptr<Screen> CreateScreenClouds();
-
-        std::unique_ptr<Screen> CreateScreenPrecipitation();
-
-        std::unique_ptr<Screen> CreateScreenHumidity();
-      };
-    }
-  }
-}




diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h
new file mode 100644
index 0000000000000000000000000000000000000000..99ce3887ac16747a88c6647b40ac09df22942841
--- /dev/null
+++ b/src/displayapp/screens/WeatherSymbols.h
@@ -0,0 +1,46 @@
+#pragma once
+#include "components/ble/SimpleWeatherService.h"
+#include "displayapp/screens/Symbols.h"
+
+namespace Pinetime {
+  namespace Applications {
+    namespace Screens {
+      namespace Symbols {
+        const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
+          switch (icon) {
+            case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
+              return Symbols::sun;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
+              return Symbols::cloudSun;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
+              return Symbols::cloud;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
+              return Symbols::cloudMeatball;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
+              return Symbols::bolt;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
+              return Symbols::snowflake;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
+              return Symbols::cloudShowersHeavy;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
+              return Symbols::cloudSunRain;
+              break;
+            case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
+              return Symbols::smog;
+              break;
+            default:
+              return Symbols::ban;
+              break;
+          }
+        }
+      }
+    }
+  }
+}




diff --git a/src/displayapp/screens/settings/SettingDisplay.cpp b/src/displayapp/screens/settings/SettingDisplay.cpp
index 91f4d5902f50fedc45f6ff84b6251d5e61933b95..bd533e675b7de2405257f8857f0af197cf1bd101 100644
--- a/src/displayapp/screens/settings/SettingDisplay.cpp
+++ b/src/displayapp/screens/settings/SettingDisplay.cpp
@@ -43,10 +43,10 @@   lv_label_set_text_static(icon, Symbols::sun);
   lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
   lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
 
-  char buffer[12];
+  char buffer[4];
   for (unsigned int i = 0; i < options.size(); i++) {
     cbOption[i] = lv_checkbox_create(container1, nullptr);
-    sprintf(buffer, "%2ds", options[i] / 1000);
+    snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
     lv_checkbox_set_text(cbOption[i], buffer);
     cbOption[i]->user_data = this;
     lv_obj_set_event_cb(cbOption[i], event_handler);




diff --git a/src/displayapp/screens/settings/SettingWakeUp.cpp b/src/displayapp/screens/settings/SettingWakeUp.cpp
index 8df34c208452054bebc8141e04bf180220dbb88f..4649dc82e2cc8f54869d06e48330c67873d87599 100644
--- a/src/displayapp/screens/settings/SettingWakeUp.cpp
+++ b/src/displayapp/screens/settings/SettingWakeUp.cpp
@@ -8,7 +8,7 @@ #include "displayapp/screens/Styles.h"
 
 using namespace Pinetime::Applications::Screens;
 
-constexpr std::array<SettingWakeUp::Option, 4> SettingWakeUp::options;
+constexpr std::array<SettingWakeUp::Option, 5> SettingWakeUp::options;
 
 namespace {
   void event_handler(lv_obj_t* obj, lv_event_t event) {
@@ -27,9 +27,9 @@   lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10);
   lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);
   lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0);
 
-  lv_obj_set_pos(container1, 10, 60);
+  lv_obj_set_pos(container1, 10, 35);
   lv_obj_set_width(container1, LV_HOR_RES - 20);
-  lv_obj_set_height(container1, LV_VER_RES - 50);
+  lv_obj_set_height(container1, LV_VER_RES - 20);
   lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT);
 
   lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);




diff --git a/src/displayapp/screens/settings/SettingWakeUp.h b/src/displayapp/screens/settings/SettingWakeUp.h
index 28219ca1c407fd5300300e92cb36f6e9a0f854e3..61edabce34fc64e2705b7e00eb793a739121f013 100644
--- a/src/displayapp/screens/settings/SettingWakeUp.h
+++ b/src/displayapp/screens/settings/SettingWakeUp.h
@@ -25,11 +25,12 @@           const char* name;
         };
 
         Controllers::Settings& settingsController;
-        static constexpr std::array<Option, 4> options = {{
+        static constexpr std::array<Option, 5> options = {{
           {Controllers::Settings::WakeUpMode::SingleTap, "Single Tap"},
           {Controllers::Settings::WakeUpMode::DoubleTap, "Double Tap"},
           {Controllers::Settings::WakeUpMode::RaiseWrist, "Raise Wrist"},
           {Controllers::Settings::WakeUpMode::Shake, "Shake Wake"},
+          {Controllers::Settings::WakeUpMode::LowerWrist, "Lower Wrist"},
         }};
 
         lv_obj_t* cbOption[options.size()];




diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp
index 5498d06792363ab5281d82c30d60ce19c1bbd08c..e01e9f847d673e7be8676fa704ee55f9491f227c 100644
--- a/src/displayapp/screens/settings/SettingWatchFace.cpp
+++ b/src/displayapp/screens/settings/SettingWatchFace.cpp
@@ -3,13 +3,43 @@ #include 
 #include "displayapp/DisplayApp.h"
 #include "displayapp/screens/Screen.h"
 #include "components/settings/Settings.h"
-#include "displayapp/WatchFaces.h"
 
 using namespace Pinetime::Applications::Screens;
 
 constexpr const char* SettingWatchFace::title;
 constexpr const char* SettingWatchFace::symbol;
 
+namespace {
+  uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
+                                    Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
+                   Pinetime::Applications::WatchFace watchface) {
+    size_t index = 0;
+    auto found = std::find_if(watchfaces.begin(),
+                              watchfaces.end(),
+                              [&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
+                                const bool result = item.watchface == watchface;
+                                if (!result) {
+                                  index++;
+                                }
+                                return result;
+                              });
+    if (found == watchfaces.end()) {
+      index = 0;
+    }
+
+    return index;
+  }
+
+  Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
+                                                                      Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
+                                                     size_t index) {
+    if (index >= watchfaces.size()) {
+      return watchfaces[0].watchface;
+    }
+    return watchfaces[index].watchface;
+  }
+}
+
 auto SettingWatchFace::CreateScreenList() const {
   std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
   for (size_t i = 0; i < screens.size(); i++) {
@@ -21,9 +51,11 @@   return screens;
 }
 
 SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
+                                   std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
                                    Pinetime::Controllers::Settings& settingsController,
                                    Pinetime::Controllers::FS& filesystem)
   : app {app},
+    watchfaceItems {std::move(watchfaceItems)},
     settingsController {settingsController},
     filesystem {filesystem},
     screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {
@@ -40,7 +72,12 @@
 std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) const {
   std::array<Screens::CheckboxList::Item, settingsPerScreen> watchfacesOnThisScreen;
   for (int i = 0; i < settingsPerScreen; i++) {
-    watchfacesOnThisScreen[i] = watchfaces[screenNum * settingsPerScreen + i];
+    if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
+      watchfacesOnThisScreen[i] = {"", false};
+    } else {
+      auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)];
+      watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled};
+    }
   }
 
   return std::make_unique<Screens::CheckboxList>(
@@ -48,9 +85,9 @@     screenNum,
     nScreens,
     title,
     symbol,
-    static_cast<uint32_t>(settingsController.GetWatchFace()),
-    [&settings = settingsController](uint32_t index) {
-      settings.SetWatchFace(static_cast<WatchFace>(index));
+    static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
+    [this, &settings = settingsController](uint32_t index) {
+      settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
       settings.SaveSettings();
     },
     watchfacesOnThisScreen);




diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h
index 33dbedbdf7320de85767fea7f9997d59809e75e0..4c75b0ab21b58c760e71d8f6133a2743a5cc41ae 100644
--- a/src/displayapp/screens/settings/SettingWatchFace.h
+++ b/src/displayapp/screens/settings/SettingWatchFace.h
@@ -19,7 +19,16 @@     namespace Screens {
 
       class SettingWatchFace : public Screen {
       public:
-        SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem);
+        struct Item {
+          const char* name;
+          WatchFace watchface;
+          bool enabled;
+        };
+
+        SettingWatchFace(DisplayApp* app,
+                         std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
+                         Pinetime::Controllers::Settings& settingsController,
+                         Pinetime::Controllers::FS& filesystem);
         ~SettingWatchFace() override;
 
         bool OnTouchEvent(TouchEvents event) override;
@@ -28,6 +37,10 @@       private:
         DisplayApp* app;
         auto CreateScreenList() const;
         std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
+
+        static constexpr int settingsPerScreen = 4;
+        std::array<Item, UserWatchFaceTypes::Count> watchfaceItems;
+        static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
 
         Controllers::Settings& settingsController;
         Pinetime::Controllers::FS& filesystem;
@@ -35,20 +48,6 @@
         static constexpr const char* title = "Watch face";
         static constexpr const char* symbol = Symbols::home;
 
-        static constexpr int settingsPerScreen = 4;
-
-        // Increment this when more space is needed
-        static constexpr int nScreens = 2;
-
-        std::array<Screens::CheckboxList::Item, settingsPerScreen * nScreens> watchfaces {
-          {{"Digital face", true},
-           {"mine", true},
-           {"PineTimeStyle", true},
-           {"Terminal", true},
-           {"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)},
-           {"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)},
-           {"mine", false},
-           {"", false}}};
         ScreenList<nScreens> screens;
       };
     }




diff --git a/src/displayapp/screens/settings/SettingWeatherFormat.cpp b/src/displayapp/screens/settings/SettingWeatherFormat.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..22d281b2ad41848f0e1646103e897927f4e94d75
--- /dev/null
+++ b/src/displayapp/screens/settings/SettingWeatherFormat.cpp
@@ -0,0 +1,63 @@
+#include "displayapp/screens/settings/SettingWeatherFormat.h"
+
+#include <lvgl/lvgl.h>
+
+#include "displayapp/DisplayApp.h"
+#include "displayapp/screens/Styles.h"
+#include "displayapp/screens/Screen.h"
+#include "displayapp/screens/Symbols.h"
+
+using namespace Pinetime::Applications::Screens;
+
+namespace {
+  struct Option {
+    Pinetime::Controllers::Settings::WeatherFormat weatherFormat;
+    const char* name;
+  };
+
+  constexpr std::array<Option, 2> options = {{
+    {Pinetime::Controllers::Settings::WeatherFormat::Metric, "Metric"},
+    {Pinetime::Controllers::Settings::WeatherFormat::Imperial, "Imperial"},
+  }};
+
+  std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
+    std::array<Pinetime::Applications::Screens::CheckboxList::Item, CheckboxList::MaxItems> optionArray;
+    for (size_t i = 0; i < CheckboxList::MaxItems; i++) {
+      if (i >= options.size()) {
+        optionArray[i].name = "";
+        optionArray[i].enabled = false;
+      } else {
+        optionArray[i].name = options[i].name;
+        optionArray[i].enabled = true;
+      }
+    }
+    return optionArray;
+  }
+
+  uint32_t GetDefaultOption(Pinetime::Controllers::Settings::WeatherFormat currentOption) {
+    for (size_t i = 0; i < options.size(); i++) {
+      if (options[i].weatherFormat == currentOption) {
+        return i;
+      }
+    }
+    return 0;
+  }
+}
+
+SettingWeatherFormat::SettingWeatherFormat(Pinetime::Controllers::Settings& settingsController)
+  : checkboxList(
+      0,
+      1,
+      "Weather format",
+      Symbols::cloudSunRain,
+      GetDefaultOption(settingsController.GetWeatherFormat()),
+      [&settings = settingsController](uint32_t index) {
+        settings.SetWeatherFormat(options[index].weatherFormat);
+        settings.SaveSettings();
+      },
+      CreateOptionArray()) {
+}
+
+SettingWeatherFormat::~SettingWeatherFormat() {
+  lv_obj_clean(lv_scr_act());
+}




diff --git a/src/displayapp/screens/settings/SettingWeatherFormat.h b/src/displayapp/screens/settings/SettingWeatherFormat.h
new file mode 100644
index 0000000000000000000000000000000000000000..a3d2bf4b661699fc5c44f59ab2c2c01ec8a49c3c
--- /dev/null
+++ b/src/displayapp/screens/settings/SettingWeatherFormat.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <array>
+#include <cstdint>
+#include <lvgl/lvgl.h>
+
+#include "components/settings/Settings.h"
+#include "displayapp/screens/Screen.h"
+#include "displayapp/screens/CheckboxList.h"
+
+namespace Pinetime {
+
+  namespace Applications {
+    namespace Screens {
+
+      class SettingWeatherFormat : public Screen {
+      public:
+        explicit SettingWeatherFormat(Pinetime::Controllers::Settings& settingsController);
+        ~SettingWeatherFormat() override;
+
+      private:
+        CheckboxList checkboxList;
+      };
+    }
+  }
+}




diff --git a/src/displayapp/screens/settings/Settings.cpp b/src/displayapp/screens/settings/Settings.cpp
index 065417fa7c3f99d83b15a647edbab5a7afd0ae44..cb5ba413e8ccd893e4e5acc8dced1bfc72df326e 100644
--- a/src/displayapp/screens/settings/Settings.cpp
+++ b/src/displayapp/screens/settings/Settings.cpp
@@ -1,7 +1,7 @@
 #include "displayapp/screens/settings/Settings.h"
 #include <lvgl/lvgl.h>
 #include <functional>
-#include "displayapp/Apps.h"
+#include "displayapp/apps/Apps.h"
 #include "displayapp/DisplayApp.h"
 
 using namespace Pinetime::Applications::Screens;




diff --git a/src/displayapp/screens/settings/Settings.h b/src/displayapp/screens/settings/Settings.h
index 3f8097538e5d021993664488b87e33ae49aeb944..a21b4ccd858a66163a629a1ac95c27c3350a85a9 100644
--- a/src/displayapp/screens/settings/Settings.h
+++ b/src/displayapp/screens/settings/Settings.h
@@ -29,7 +29,7 @@
         static constexpr int entriesPerScreen = 4;
 
         // Increment this when more space is needed
-        static constexpr int nScreens = 3;
+        static constexpr int nScreens = 4;
 
         static constexpr std::array<List::Applications, entriesPerScreen * nScreens> entries {{
           {Symbols::sun, "Display", Apps::SettingDisplay},
@@ -39,12 +39,14 @@           {Symbols::home, "Watch face", Apps::SettingWatchFace},
 
           {Symbols::shoe, "Steps", Apps::SettingSteps},
           {Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
+          {Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
           {Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
-          {Symbols::clock, "Chimes", Apps::SettingChimes},
 
+          {Symbols::clock, "Chimes", Apps::SettingChimes},
           {Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold},
           {Symbols::check, "Firmware", Apps::FirmwareValidation},
           {Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
+
           {Symbols::list, "About", Apps::SysInfo},
 
           // {Symbols::none, "None", Apps::None},




diff --git a/src/drivers/SpiMaster.cpp b/src/drivers/SpiMaster.cpp
index 1a2fab9c27a721cdcbf15c31f86f1943a6c89282..3446d639fa9a7f2765dcbc746fecaa45e30d49f2 100644
--- a/src/drivers/SpiMaster.cpp
+++ b/src/drivers/SpiMaster.cpp
@@ -131,8 +131,8 @@   auto s = currentBufferSize;
   if (s > 0) {
     auto currentSize = std::min((size_t) 255, s);
     PrepareTx(currentBufferAddr, currentSize);
-    currentBufferAddr += currentSize;
-    currentBufferSize -= currentSize;
+    currentBufferAddr = currentBufferAddr + currentSize;
+    currentBufferSize = currentBufferSize - currentSize;
 
     spiBaseAddress->TASKS_START = 1;
   } else {
@@ -153,7 +153,7 @@
 void SpiMaster::OnStartedEvent() {
 }
 
-void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size) {
+void SpiMaster::PrepareTx(const uint32_t bufferAddress, const size_t size) {
   spiBaseAddress->TXD.PTR = bufferAddress;
   spiBaseAddress->TXD.MAXCNT = size;
   spiBaseAddress->TXD.LIST = 0;
@@ -163,7 +163,7 @@   spiBaseAddress->RXD.LIST = 0;
   spiBaseAddress->EVENTS_END = 0;
 }
 
-void SpiMaster::PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size) {
+void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
   spiBaseAddress->TXD.PTR = 0;
   spiBaseAddress->TXD.MAXCNT = 0;
   spiBaseAddress->TXD.LIST = 0;
@@ -195,8 +195,8 @@   currentBufferSize = size;
 
   auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize);
   PrepareTx(currentBufferAddr, currentSize);
-  currentBufferSize -= currentSize;
-  currentBufferAddr += currentSize;
+  currentBufferSize = currentBufferSize - currentSize;
+  currentBufferAddr = currentBufferAddr + currentSize;
   spiBaseAddress->TASKS_START = 1;
 
   if (size == 1) {




diff --git a/src/drivers/Watchdog.cpp b/src/drivers/Watchdog.cpp
index 6c2c963be375c40b6e993a27774ea8870c362885..eeeb6cfdf53ad6c4af8913661ef90630f3605dcf 100644
--- a/src/drivers/Watchdog.cpp
+++ b/src/drivers/Watchdog.cpp
@@ -41,7 +41,7 @@   void EnableFirstReloadRegister() {
     // RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent
     // one of the eight reload registers available.
     // In this case, we enable only the first one.
-    NRF_WDT->RREN |= 1;
+    NRF_WDT->RREN = NRF_WDT->RREN | 1;
   }
 
   /// Returns the reset reason provided by the POWER subsystem




diff --git a/src/resources/CMakeLists.txt b/src/resources/CMakeLists.txt
index 0983aaffe6face66b5a603d218f8a353a3b4d7c0..3834e854fe569b7c3651b92b6b142dbc427f2966 100644
--- a/src/resources/CMakeLists.txt
+++ b/src/resources/CMakeLists.txt
@@ -3,8 +3,8 @@ find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
     HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
 message(STATUS "Using ${LV_FONT_CONV} to generate font files")
 
-find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED
-    HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
+find_program(LV_IMG_CONV "lv_img_conv.py" NO_CACHE REQUIRED
+    HINTS "${CMAKE_CURRENT_SOURCE_DIR}")
 message(STATUS "Using ${LV_IMG_CONV} to generate font files")
 
 if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)




diff --git a/src/resources/generate-img.py b/src/resources/generate-img.py
index cdbfc030486bab0996001f70808046acc8f0b9a8..518d22062e7a727884d601b388977bb7d080a460 100755
--- a/src/resources/generate-img.py
+++ b/src/resources/generate-img.py
@@ -11,6 +11,9 @@ import subprocess
 
 def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str):
     args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format]
+    if lv_img_conv.endswith(".py"):
+        # lv_img_conv is a python script, call with current python executable
+        args = [sys.executable] + args
 
     return args
 




diff --git a/src/resources/images/navigation0.png b/src/resources/images/navigation0.png
new file mode 100644
index 0000000000000000000000000000000000000000..37d7abd842da080551e7c01ef1121b22cdd00f54
Binary files /dev/null and b/src/resources/images/navigation0.png differ




diff --git a/src/resources/images/navigation1.png b/src/resources/images/navigation1.png
new file mode 100644
index 0000000000000000000000000000000000000000..f1714a713865c8de9e085309fb022fac5f0ff4c6
Binary files /dev/null and b/src/resources/images/navigation1.png differ




diff --git a/src/resources/images.json b/src/resources/images.json
index db2ccab088a76e096c04e1474387034ed51963e5..e424718808f237165e4f382546ce55c7f1a02671 100644
--- a/src/resources/images.json
+++ b/src/resources/images.json
@@ -5,5 +5,19 @@       "color_format": "CF_TRUE_COLOR_ALPHA",
       "output_format": "bin",
       "binary_format": "ARGB8565_RBSWAP",
       "target_path": "/images/"
+   },
+   "navigation0" : {
+      "sources": "images/navigation0.png",
+      "color_format": "CF_INDEXED_1_BIT",
+      "output_format": "bin",
+      "binary_format": "ARGB8565_RBSWAP",
+      "target_path": "/images/"
+   },
+   "navigation1" : {
+      "sources": "images/navigation1.png",
+      "color_format": "CF_INDEXED_1_BIT",
+      "output_format": "bin",
+      "binary_format": "ARGB8565_RBSWAP",
+      "target_path": "/images/"
    }
 }




diff --git a/src/resources/lv_img_conv.py b/src/resources/lv_img_conv.py
new file mode 100755
index 0000000000000000000000000000000000000000..04765462ee9d10ec121dedf2e7f99daa465d577a
--- /dev/null
+++ b/src/resources/lv_img_conv.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+import argparse
+import pathlib
+import sys
+import decimal
+from PIL import Image
+
+
+def classify_pixel(value, bits):
+    def round_half_up(v):
+        """python3 implements "propper" "banker's rounding" by rounding to the nearest
+        even number. Javascript rounds to the nearest integer.
+        To have the same output as the original JavaScript implementation add a custom
+        rounding function, which does "school" rounding (to the nearest integer).
+
+        see: https://stackoverflow.com/questions/43851273/how-to-round-float-0-5-up-to-1-0-while-still-rounding-0-45-to-0-0-as-the-usual
+        """
+        return int(decimal.Decimal(v).quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP))
+    tmp = 1 << (8 - bits)
+    val = round_half_up(value / tmp) * tmp
+    if val < 0:
+        val = 0
+    return val
+
+
+def test_classify_pixel():
+    # test difference between round() and round_half_up()
+    assert classify_pixel(18, 5) == 16
+    # school rounding 4.5 to 5, but banker's rounding 4.5 to 4
+    assert classify_pixel(18, 6) == 20
+
+
+def main():
+    parser = argparse.ArgumentParser()
+
+    parser.add_argument("img",
+        help="Path to image to convert to C header file")
+    parser.add_argument("-o", "--output-file",
+        help="output file path (for single-image conversion)",
+        required=True)
+    parser.add_argument("-f", "--force",
+        help="allow overwriting the output file",
+        action="store_true")
+    parser.add_argument("-i", "--image-name",
+        help="name of image structure (not implemented)")
+    parser.add_argument("-c", "--color-format",
+        help="color format of image",
+        default="CF_TRUE_COLOR_ALPHA",
+        choices=[
+            "CF_ALPHA_1_BIT", "CF_ALPHA_2_BIT", "CF_ALPHA_4_BIT",
+            "CF_ALPHA_8_BIT", "CF_INDEXED_1_BIT", "CF_INDEXED_2_BIT", "CF_INDEXED_4_BIT",
+            "CF_INDEXED_8_BIT", "CF_RAW", "CF_RAW_CHROMA", "CF_RAW_ALPHA",
+            "CF_TRUE_COLOR", "CF_TRUE_COLOR_ALPHA", "CF_TRUE_COLOR_CHROMA", "CF_RGB565A8",
+        ],
+        required=True)
+    parser.add_argument("-t", "--output-format",
+        help="output format of image",
+        default="bin", # default in original is 'c'
+        choices=["c", "bin"])
+    parser.add_argument("--binary-format",
+        help="binary color format (needed if output-format is binary)",
+        default="ARGB8565_RBSWAP",
+        choices=["ARGB8332", "ARGB8565", "ARGB8565_RBSWAP", "ARGB8888"])
+    parser.add_argument("-s", "--swap-endian",
+        help="swap endian of image (not implemented)",
+        action="store_true")
+    parser.add_argument("-d", "--dither",
+        help="enable dither (not implemented)",
+        action="store_true")
+    args = parser.parse_args()
+
+    img_path = pathlib.Path(args.img)
+    out = pathlib.Path(args.output_file)
+    if not img_path.is_file():
+        print(f"Input file is missing: '{args.img}'")
+        return 1
+    print(f"Beginning conversion of {args.img}")
+    if out.exists():
+        if args.force:
+            print(f"overwriting {args.output_file}")
+        else:
+            pritn(f"Error: refusing to overwrite {args.output_file} without -f specified.")
+            return 1
+    out.touch()
+
+    # only implemented the bare minimum, everything else is not implemented
+    if args.color_format not in ["CF_INDEXED_1_BIT", "CF_TRUE_COLOR_ALPHA"]:
+        raise NotImplementedError(f"argument --color-format '{args.color_format}' not implemented")
+    if args.output_format != "bin":
+        raise NotImplementedError(f"argument --output-format '{args.output_format}' not implemented")
+    if args.binary_format not in ["ARGB8565_RBSWAP", "ARGB8888"]:
+        raise NotImplementedError(f"argument --binary-format '{args.binary_format}' not implemented")
+    if args.image_name:
+        raise NotImplementedError(f"argument --image-name not implemented")
+    if args.swap_endian:
+        raise NotImplementedError(f"argument --swap-endian not implemented")
+    if args.dither:
+        raise NotImplementedError(f"argument --dither not implemented")
+
+    # open image using Pillow
+    img = Image.open(img_path)
+    img_height = img.height
+    img_width = img.width
+    if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
+        buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
+        for y in range(img_height):
+            for x in range(img_width):
+                i = (y*img_width + x)*4 # buffer-index
+                pixel = img.getpixel((x,y))
+                r, g, b, a = pixel
+                buf[i + 0] = r
+                buf[i + 1] = g
+                buf[i + 2] = b
+                buf[i + 3] = a
+
+    elif args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8565_RBSWAP":
+        buf = bytearray(img_height*img_width*3) # 3 bytes (24 bit) per pixel
+        for y in range(img_height):
+            for x in range(img_width):
+                i = (y*img_width + x)*3 # buffer-index
+                pixel = img.getpixel((x,y))
+                r_act = classify_pixel(pixel[0], 5)
+                g_act = classify_pixel(pixel[1], 6)
+                b_act = classify_pixel(pixel[2], 5)
+                a = pixel[3]
+                r_act = min(r_act, 0xF8)
+                g_act = min(g_act, 0xFC)
+                b_act = min(b_act, 0xF8)
+                c16 = ((r_act) << 8) | ((g_act) << 3) | ((b_act) >> 3) # RGR565
+                buf[i + 0] = (c16 >> 8) & 0xFF
+                buf[i + 1] = c16 & 0xFF
+                buf[i + 2] = a
+
+    elif args.color_format == "CF_INDEXED_1_BIT": # ignore binary format, use color format as binary format
+        w = img_width >> 3
+        if img_width & 0x07:
+            w+=1
+        max_p = w * (img_height-1) + ((img_width-1) >> 3) + 8  # +8 for the palette
+        buf = bytearray(max_p+1)
+
+        for y in range(img_height):
+            for x in range(img_width):
+                c, a = img.getpixel((x,y))
+                p = w * y + (x >> 3) + 8  # +8 for the palette
+                buf[p] |= (c & 0x1) << (7 - (x & 0x7))
+        # write palette information, for indexed-1-bit we need palette with two values
+        # write 8 palette bytes
+        buf[0] = 0
+        buf[1] = 0
+        buf[2] = 0
+        buf[3] = 0
+        # Normally there is much math behind this, but for the current use case this is close enough
+        # only needs to be more complicated if we have more than 2 colors in the palette
+        buf[4] = 255
+        buf[5] = 255
+        buf[6] = 255
+        buf[7] = 255
+    else:
+        # raise just to be sure
+        raise NotImplementedError(f"args.color_format '{args.color_format}' with args.binary_format '{args.binary_format}' not implemented")
+
+    # write header
+    match args.color_format:
+        case "CF_TRUE_COLOR_ALPHA":
+            lv_cf = 5
+        case "CF_INDEXED_1_BIT":
+            lv_cf = 7
+        case _:
+            # raise just to be sure
+            raise NotImplementedError(f"args.color_format '{args.color_format}' not implemented")
+    header_32bit = lv_cf | (img_width << 10) | (img_height << 21)
+    buf_out = bytearray(4 + len(buf))
+    buf_out[0] = header_32bit & 0xFF
+    buf_out[1] = (header_32bit & 0xFF00) >> 8
+    buf_out[2] = (header_32bit & 0xFF0000) >> 16
+    buf_out[3] = (header_32bit & 0xFF000000) >> 24
+    buf_out[4:] = buf
+
+    # write byte buffer to file
+    with open(out, "wb") as f:
+        f.write(buf_out)
+    return 0
+
+
+if __name__ == '__main__':
+    if "--test" in sys.argv:
+        # run small set of tests and exit
+        print("running tests")
+        test_classify_pixel()
+        print("success!")
+        sys.exit(0)
+    # run normal program
+    sys.exit(main())




diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index b199d53f02077cc406a6b5f560160687b6bbe617..b245e3922750d03cc516bf27a1f9d869ffd01df0 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -136,6 +136,9 @@   motionController.Init(motionSensor.DeviceType());
   settingsController.Init();
 
   displayApp.Register(this);
+  displayApp.Register(&nimbleController.weather());
+  displayApp.Register(&nimbleController.music());
+  displayApp.Register(&nimbleController.navigation());
   displayApp.Start(bootError);
 
   heartRateSensor.Init();
@@ -430,11 +433,15 @@   motionController.Update(motionValues.x, motionValues.y, motionValues.z, motionValues.steps);
 
   if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep) {
     if ((settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) &&
-         motionController.ShouldRaiseWake(state == SystemTaskState::Sleeping)) ||
+         motionController.ShouldRaiseWake()) ||
         (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) &&
          motionController.ShouldShakeWake(settingsController.GetShakeThreshold()))) {
       GoToRunning();
     }
+  }
+  if (settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::LowerWrist) && state == SystemTaskState::Running &&
+      motionController.ShouldLowerSleep()) {
+    PushMessage(Messages::GoToSleep);
   }
 }
 




diff --git a/src/utility/CircularBuffer.h b/src/utility/CircularBuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8abe92e4e58284004ee866cc93fabcbed7e7ed1
--- /dev/null
+++ b/src/utility/CircularBuffer.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+
+namespace Pinetime {
+  namespace Utility {
+    template <class T, size_t S>
+    struct CircularBuffer {
+      constexpr size_t Size() const {
+        return S;
+      }
+
+      size_t Idx() const {
+        return idx;
+      }
+
+      T& operator[](size_t n) {
+        return data[(idx + n) % S];
+      }
+
+      const T& operator[](size_t n) const {
+        return data[(idx + n) % S];
+      }
+
+      void operator++() {
+        idx++;
+        idx %= S;
+      }
+
+      void operator++(int) {
+        operator++();
+      }
+
+      void operator--() {
+        if (idx > 0) {
+          idx--;
+        } else {
+          idx = S - 1;
+        }
+      }
+
+      void operator--(int) {
+        operator--();
+      }
+
+      std::array<T, S> data;
+      size_t idx = 0;
+    };
+  }
+}




diff --git a/src/utility/Math.cpp b/src/utility/Math.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fee4f64af3ad3c9199f603ab7fe9d480f6fed647
--- /dev/null
+++ b/src/utility/Math.cpp
@@ -0,0 +1,49 @@
+#include "utility/Math.h"
+
+#include <lvgl/src/lv_misc/lv_math.h>
+
+using namespace Pinetime::Utility;
+
+#ifndef PINETIME_IS_RECOVERY
+
+int16_t Pinetime::Utility::Asin(int16_t arg) {
+  int16_t a = arg < 0 ? -arg : arg;
+
+  int16_t angle = 45;
+  int16_t low = 0;
+  int16_t high = 90;
+  while (low <= high) {
+    int16_t sinAngle = _lv_trigo_sin(angle);
+    int16_t sinAngleSub = _lv_trigo_sin(angle - 1);
+    int16_t sinAngleAdd = _lv_trigo_sin(angle + 1);
+
+    if (a >= sinAngleSub && a <= sinAngleAdd) {
+      if (a <= (sinAngleSub + sinAngle) / 2) {
+        angle--;
+      } else if (a > (sinAngle + sinAngleAdd) / 2) {
+        angle++;
+      }
+      break;
+    }
+
+    if (a < sinAngle) {
+      high = angle - 1;
+    }
+
+    else {
+      low = angle + 1;
+    }
+
+    angle = (low + high) / 2;
+  }
+
+  return arg < 0 ? -angle : angle;
+}
+
+#else
+
+int16_t Pinetime::Utility::Asin(int16_t /*arg*/) {
+  return 0;
+}
+
+#endif




diff --git a/src/utility/Math.h b/src/utility/Math.h
new file mode 100644
index 0000000000000000000000000000000000000000..e8d190c72dcd31df104d791353f02deb86a1f33f
--- /dev/null
+++ b/src/utility/Math.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <cstdint>
+
+namespace Pinetime {
+  namespace Utility {
+    // returns the arcsin of `arg`. asin(-32767) = -90, asin(32767) = 90
+    int16_t Asin(int16_t arg);
+  }
+}