Compare commits
68 Commits
wb/fuzzy-c
...
wb/fuzzy-n
Author | SHA1 | Date | |
---|---|---|---|
|
b7301d51cd | ||
|
5c87829d65 | ||
|
7cd9e8493d | ||
|
d2989ae23a | ||
|
66f6c34b50 | ||
|
6d615b374c | ||
|
3df7c6a4f3 | ||
|
e1c652bdfb | ||
|
f34aede897 | ||
|
95e2f9e701 | ||
|
b4ff1f9ca2 | ||
|
9b36afc787 | ||
|
3f51923c3b | ||
|
82db3dcd18 | ||
|
ab8515f766 | ||
|
f17bbc38da | ||
|
02a7a3a45f | ||
|
b0f1a49775 | ||
|
e88d0afabf | ||
|
d889f3e444 | ||
|
c04813b6d3 | ||
|
fc5424cb72 | ||
|
2ee2b8523a | ||
|
06171dad38 | ||
|
7794378e96 | ||
|
f0e8bb26e9 | ||
|
1ad7840072 | ||
|
f2996f54a8 | ||
|
e00b98b82c | ||
|
2bc9318451 | ||
|
ca7d8a668d | ||
|
d56df38aeb | ||
|
6f83a3bade | ||
|
e5b73212f6 | ||
|
ad090ab188 | ||
|
ef2c431569 | ||
|
199aefc617 | ||
|
d29eb1ea99 | ||
|
3a8c7dc038 | ||
|
fe4b07c610 | ||
|
50c679023f | ||
|
c94a59e7d3 | ||
|
088082d32d | ||
|
39bc166e54 | ||
|
a544da9ed1 | ||
|
69b9d30a30 | ||
|
d79766bccd | ||
|
80607282dd | ||
|
54b4750c6f | ||
|
41a4813c8b | ||
|
42fcb99b38 | ||
|
cd8216d1c9 | ||
|
c9fbcd8818 | ||
|
e89e5e4d66 | ||
|
f3d4f04827 | ||
|
473d9c4fa4 | ||
|
97ba1a456d | ||
|
02af09d943 | ||
|
63e0c4f4ef | ||
|
f6d7f602f5 | ||
|
930284adc2 | ||
|
9b8eb75f34 | ||
|
b191a30947 | ||
|
d930fd4fa2 | ||
|
e6b96c2863 | ||
|
77546c9fe2 | ||
|
eac460f030 | ||
|
46b664b528 |
|
@ -11,6 +11,7 @@ RUN apt-get update -qq \
|
||||||
make \
|
make \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
python3-pil \
|
||||||
tar \
|
tar \
|
||||||
unzip \
|
unzip \
|
||||||
wget \
|
wget \
|
||||||
|
|
|
@ -64,7 +64,6 @@ CmakeGenerate() {
|
||||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||||
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
|
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
|
||||||
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
|
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
|
||||||
-DBUILD_RESOURCES=0
|
|
||||||
"$SOURCES_DIR"
|
"$SOURCES_DIR"
|
||||||
cmake -L -N .
|
cmake -L -N .
|
||||||
}
|
}
|
||||||
|
|
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
@ -3,7 +3,7 @@ name: CI
|
||||||
# Run this workflow whenever the build may be affected
|
# Run this workflow whenever the build may be affected
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, wb/fuzzy-clock ]
|
branches: [ main, wb/fuzzy, wb/fuzzy-norm ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'doc/**'
|
- 'doc/**'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
|
@ -31,6 +31,10 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
- name: Install resource build dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get -y install --no-install-recommends python3-pil
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: /opt/build.sh all
|
run: /opt/build.sh all
|
||||||
|
|
2
.vscode/c_cpp_properties.json
vendored
2
.vscode/c_cpp_properties.json
vendored
|
@ -10,7 +10,7 @@
|
||||||
"defines": [],
|
"defines": [],
|
||||||
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
|
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++14",
|
"cppStandard": "c++20",
|
||||||
"intelliSenseMode": "linux-gcc-arm",
|
"intelliSenseMode": "linux-gcc-arm",
|
||||||
"configurationProvider": "ms-vscode.cpp-tools",
|
"configurationProvider": "ms-vscode.cpp-tools",
|
||||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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.13.0 LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
# set(CMAKE_GENERATOR "Unix Makefiles")
|
# set(CMAKE_GENERATOR "Unix Makefiles")
|
||||||
set(CMAKE_C_EXTENSIONS OFF)
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
@ -13,7 +13,6 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
set(NRF_TARGET "nrf52")
|
set(NRF_TARGET "nrf52")
|
||||||
|
|
||||||
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
||||||
|
@ -33,7 +32,7 @@ if(BUILD_RESOURCES)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device")
|
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 "")
|
set(PROJECT_GIT_COMMIT_HASH "")
|
||||||
|
|
||||||
|
@ -70,5 +69,4 @@ set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generate
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/Version.h)
|
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)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh)
|
||||||
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
|
@ -17,7 +17,7 @@ Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/p
|
||||||
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
||||||
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
||||||
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
|
- [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)
|
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
|
||||||
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ It integrates the following projects:
|
||||||
|
|
||||||
## Credits
|
## 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:
|
Here are some people I would like to highlight:
|
||||||
|
|
||||||
|
|
69
doc/SimpleWeatherService.md
Normal file
69
doc/SimpleWeatherService.md
Normal file
|
@ -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
|
|
@ -92,7 +92,10 @@ The following custom services are implemented in InfiniTime:
|
||||||
|
|
||||||
- Since InfiniTime 1.8:
|
- 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`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,8 @@ CMake configures the project according to variables you specify the command line
|
||||||
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
|
**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`
|
**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_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`
|
**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)
|
**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**
|
#### (\*) 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.
|
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.
|
||||||
|
@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
|
||||||
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
||||||
- **pinetime-mcuboot-app-dfu** : DFU file 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**
|
||||||
|
|
131
doc/code/Apps.md
131
doc/code/Apps.md
|
@ -9,59 +9,114 @@ This page will teach you:
|
||||||
|
|
||||||
The user interface of InfiniTime is made up of **screens**.
|
The user interface of InfiniTime is made up of **screens**.
|
||||||
Screens that are opened from the app launcher are considered **apps**.
|
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.
|
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.
|
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
|
## Interface
|
||||||
|
|
||||||
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`.
|
Every app class is declared inside the namespace `Pinetime::Applications::Screens`
|
||||||
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen.
|
and inherits
|
||||||
Other parameters should be references to controllers that the app needs.
|
from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
|
||||||
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.
|
|
||||||
|
|
||||||
### Continuous updating
|
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.
|
||||||
|
|
||||||
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class
|
The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping).
|
||||||
and calling `lv_task_create` inside the constructor.
|
|
||||||
|
|
||||||
An example call could look like this:
|
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.
|
||||||
|
|
||||||
```cpp
|
Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
|
||||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
that will call the method `Refresh()` periodically.
|
||||||
|
|
||||||
|
## App types
|
||||||
|
|
||||||
|
There are basically 2 types of applications : **system** apps and **user** apps.
|
||||||
|
|
||||||
|
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
|
||||||
|
The watchfaces, 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.
|
||||||
|
|
||||||
|
The distinction between **system** and **user** applications allows for more flexibility and customization.
|
||||||
|
This allows to easily select which user applications must be built into the firmware
|
||||||
|
without overflowing the system memory.
|
||||||
|
|
||||||
|
## Apps 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.
|
||||||
|
|
||||||
|
## User application selection at build time
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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*`.
|
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
|
||||||
Remember to delete the task again using `lv_task_del`.
|
to list all available applications.
|
||||||
The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function.
|
|
||||||
|
|
||||||
## Creating your own app
|
## Creating your own app
|
||||||
|
|
||||||
A minimal app could look like this:
|
A minimal user app could look like this:
|
||||||
|
|
||||||
MyApp.h:
|
MyApp.h:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "displayapp/Apps.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include <lvgl/lvgl.h>
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
namespace Screens {
|
namespace Screens {
|
||||||
class MyApp : public Screen {
|
class MyApp : public Screen {
|
||||||
public:
|
public:
|
||||||
MyApp(DisplayApp* app);
|
MyApp();
|
||||||
~MyApp() override;
|
~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 +125,10 @@ MyApp.cpp:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include "displayapp/screens/MyApp.h"
|
#include "displayapp/screens/MyApp.h"
|
||||||
#include "displayapp/DisplayApp.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
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_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_label_set_text_static(title, "My test application");
|
lv_label_set_text_static(title, "My test application");
|
||||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||||
|
@ -86,20 +140,33 @@ MyApp::~MyApp() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/)
|
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/).
|
||||||
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
|
|
||||||
|
|
||||||
Now we have our very own app, but InfiniTime does not know about it yet.
|
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).
|
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)).
|
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).
|
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
|
||||||
Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement.
|
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.
|
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 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" ...
|
||||||
|
```
|
||||||
|
|
||||||
You should now be able to [build](../buildAndProgram.md) the firmware
|
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||||
and flash it to your PineTime. Yay!
|
and flash it to your PineTime. Yay!
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG NODE_MAJOR=20
|
||||||
RUN apt-get update -qq \
|
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 \
|
&& apt-get install -y \
|
||||||
# x86_64 / generic packages
|
# x86_64 / generic packages
|
||||||
bash \
|
bash \
|
||||||
|
@ -9,13 +15,14 @@ RUN apt-get update -qq \
|
||||||
cmake \
|
cmake \
|
||||||
git \
|
git \
|
||||||
make \
|
make \
|
||||||
|
nodejs \
|
||||||
python3 \
|
python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
python3-pil \
|
||||||
python-is-python3 \
|
python-is-python3 \
|
||||||
tar \
|
tar \
|
||||||
unzip \
|
unzip \
|
||||||
wget \
|
wget \
|
||||||
curl \
|
|
||||||
# aarch64 packages
|
# aarch64 packages
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
|
@ -28,8 +35,6 @@ RUN apt-get update -qq \
|
||||||
libpango-1.0-0 \
|
libpango-1.0-0 \
|
||||||
ibpango1.0-dev \
|
ibpango1.0-dev \
|
||||||
libpangocairo-1.0-0 \
|
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/*;
|
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
|
||||||
|
|
||||||
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
||||||
|
@ -39,10 +44,6 @@ RUN pip3 install -Iv cryptography==3.3
|
||||||
RUN pip3 install cbor
|
RUN pip3 install cbor
|
||||||
RUN npm i lv_font_conv@1.5.2 -g
|
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
|
# build.sh knows how to compile
|
||||||
COPY build.sh /opt/
|
COPY build.sh /opt/
|
||||||
|
|
||||||
|
|
31
shell.nix
31
shell.nix
|
@ -1,31 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
|
|
||||||
with pkgs; let
|
|
||||||
py4McuBoot = python3.withPackages (ps: with ps; [
|
|
||||||
cbor
|
|
||||||
intelhex
|
|
||||||
click
|
|
||||||
cryptography
|
|
||||||
]);
|
|
||||||
in mkShell {
|
|
||||||
packages = [
|
|
||||||
gcc-arm-embedded-10
|
|
||||||
nrf5-sdk
|
|
||||||
cmake
|
|
||||||
nodePackages.lv_font_conv
|
|
||||||
lv_img_conv
|
|
||||||
py4McuBoot
|
|
||||||
clang-tools
|
|
||||||
SDL2
|
|
||||||
libpng
|
|
||||||
python3Packages.adafruit-nrfutil
|
|
||||||
];
|
|
||||||
|
|
||||||
ARM_NONE_EABI_TOOLCHAIN_PATH="${gcc-arm-embedded-10}";
|
|
||||||
NRF5_SDK_PATH="${nrf5-sdk}/share/nRF5_SDK";
|
|
||||||
CMAKE_BUILD_TYPE="Release";
|
|
||||||
BUILD_DFU=1;
|
|
||||||
BUILD_RESOURCES=1;
|
|
||||||
TARGET_DEVICE="PINETIME";
|
|
||||||
|
|
||||||
}
|
|
|
@ -355,14 +355,6 @@ set(LVGL_SRC
|
||||||
libs/lvgl/src/lv_widgets/lv_win.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
|
list(APPEND IMAGE_FILES
|
||||||
displayapp/icons/battery/batteryicon.c
|
displayapp/icons/battery/batteryicon.c
|
||||||
)
|
)
|
||||||
|
@ -373,8 +365,9 @@ list(APPEND SOURCE_FILES
|
||||||
logging/NrfLogger.cpp
|
logging/NrfLogger.cpp
|
||||||
displayapp/DisplayApp.cpp
|
displayapp/DisplayApp.cpp
|
||||||
displayapp/screens/Screen.cpp
|
displayapp/screens/Screen.cpp
|
||||||
displayapp/screens/Clock.cpp
|
|
||||||
displayapp/screens/Tile.cpp
|
displayapp/screens/Tile.cpp
|
||||||
|
displayapp/screens/InfiniPaint.cpp
|
||||||
|
displayapp/screens/Paddle.cpp
|
||||||
displayapp/screens/StopWatch.cpp
|
displayapp/screens/StopWatch.cpp
|
||||||
displayapp/screens/BatteryIcon.cpp
|
displayapp/screens/BatteryIcon.cpp
|
||||||
displayapp/screens/BleIcon.cpp
|
displayapp/screens/BleIcon.cpp
|
||||||
|
@ -383,7 +376,6 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/Label.cpp
|
displayapp/screens/Label.cpp
|
||||||
displayapp/screens/FirmwareUpdate.cpp
|
displayapp/screens/FirmwareUpdate.cpp
|
||||||
displayapp/screens/Music.cpp
|
displayapp/screens/Music.cpp
|
||||||
displayapp/screens/Weather.cpp
|
|
||||||
displayapp/screens/Navigation.cpp
|
displayapp/screens/Navigation.cpp
|
||||||
displayapp/screens/Metronome.cpp
|
displayapp/screens/Metronome.cpp
|
||||||
displayapp/screens/Motion.cpp
|
displayapp/screens/Motion.cpp
|
||||||
|
@ -392,7 +384,6 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/Notifications.cpp
|
displayapp/screens/Notifications.cpp
|
||||||
displayapp/screens/Twos.cpp
|
displayapp/screens/Twos.cpp
|
||||||
displayapp/screens/HeartRate.cpp
|
displayapp/screens/HeartRate.cpp
|
||||||
displayapp/screens/Motion.cpp
|
|
||||||
displayapp/screens/FlashLight.cpp
|
displayapp/screens/FlashLight.cpp
|
||||||
displayapp/screens/List.cpp
|
displayapp/screens/List.cpp
|
||||||
displayapp/screens/CheckboxList.cpp
|
displayapp/screens/CheckboxList.cpp
|
||||||
|
@ -414,6 +405,7 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/settings/Settings.cpp
|
displayapp/screens/settings/Settings.cpp
|
||||||
displayapp/screens/settings/SettingWatchFace.cpp
|
displayapp/screens/settings/SettingWatchFace.cpp
|
||||||
displayapp/screens/settings/SettingTimeFormat.cpp
|
displayapp/screens/settings/SettingTimeFormat.cpp
|
||||||
|
displayapp/screens/settings/SettingWeatherFormat.cpp
|
||||||
displayapp/screens/settings/SettingWakeUp.cpp
|
displayapp/screens/settings/SettingWakeUp.cpp
|
||||||
displayapp/screens/settings/SettingDisplay.cpp
|
displayapp/screens/settings/SettingDisplay.cpp
|
||||||
displayapp/screens/settings/SettingSteps.cpp
|
displayapp/screens/settings/SettingSteps.cpp
|
||||||
|
@ -428,9 +420,9 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/WatchFaceAnalog.cpp
|
displayapp/screens/WatchFaceAnalog.cpp
|
||||||
displayapp/screens/WatchFaceDigital.cpp
|
displayapp/screens/WatchFaceDigital.cpp
|
||||||
displayapp/screens/WatchFaceInfineat.cpp
|
displayapp/screens/WatchFaceInfineat.cpp
|
||||||
|
displayapp/screens/WatchFaceTerminal.cpp
|
||||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
displayapp/screens/WatchFacePineTimeStyle.cpp
|
||||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
||||||
displayapp/screens/WatchFaceFuzzy.cpp
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@ -459,7 +451,7 @@ list(APPEND SOURCE_FILES
|
||||||
components/ble/CurrentTimeService.cpp
|
components/ble/CurrentTimeService.cpp
|
||||||
components/ble/AlertNotificationService.cpp
|
components/ble/AlertNotificationService.cpp
|
||||||
components/ble/MusicService.cpp
|
components/ble/MusicService.cpp
|
||||||
components/ble/weather/WeatherService.cpp
|
components/ble/SimpleWeatherService.cpp
|
||||||
components/ble/NavigationService.cpp
|
components/ble/NavigationService.cpp
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
components/ble/FSService.cpp
|
components/ble/FSService.cpp
|
||||||
|
@ -528,7 +520,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||||
components/ble/CurrentTimeService.cpp
|
components/ble/CurrentTimeService.cpp
|
||||||
components/ble/AlertNotificationService.cpp
|
components/ble/AlertNotificationService.cpp
|
||||||
components/ble/MusicService.cpp
|
components/ble/MusicService.cpp
|
||||||
components/ble/weather/WeatherService.cpp
|
components/ble/SimpleWeatherService.cpp
|
||||||
components/ble/BatteryInformationService.cpp
|
components/ble/BatteryInformationService.cpp
|
||||||
components/ble/FSService.cpp
|
components/ble/FSService.cpp
|
||||||
components/ble/ImmediateAlertService.cpp
|
components/ble/ImmediateAlertService.cpp
|
||||||
|
@ -596,9 +588,10 @@ set(INCLUDE_FILES
|
||||||
displayapp/Messages.h
|
displayapp/Messages.h
|
||||||
displayapp/TouchEvents.h
|
displayapp/TouchEvents.h
|
||||||
displayapp/screens/Screen.h
|
displayapp/screens/Screen.h
|
||||||
displayapp/screens/Clock.h
|
|
||||||
displayapp/screens/Tile.h
|
displayapp/screens/Tile.h
|
||||||
|
displayapp/screens/InfiniPaint.h
|
||||||
displayapp/screens/StopWatch.h
|
displayapp/screens/StopWatch.h
|
||||||
|
displayapp/screens/Paddle.h
|
||||||
displayapp/screens/BatteryIcon.h
|
displayapp/screens/BatteryIcon.h
|
||||||
displayapp/screens/BleIcon.h
|
displayapp/screens/BleIcon.h
|
||||||
displayapp/screens/NotificationIcon.h
|
displayapp/screens/NotificationIcon.h
|
||||||
|
@ -610,7 +603,6 @@ set(INCLUDE_FILES
|
||||||
displayapp/screens/ApplicationList.h
|
displayapp/screens/ApplicationList.h
|
||||||
displayapp/screens/CheckboxList.h
|
displayapp/screens/CheckboxList.h
|
||||||
displayapp/Apps.h
|
displayapp/Apps.h
|
||||||
displayapp/WatchFaces.h
|
|
||||||
displayapp/screens/Notifications.h
|
displayapp/screens/Notifications.h
|
||||||
displayapp/screens/HeartRate.h
|
displayapp/screens/HeartRate.h
|
||||||
displayapp/screens/Metronome.h
|
displayapp/screens/Metronome.h
|
||||||
|
@ -655,7 +647,7 @@ set(INCLUDE_FILES
|
||||||
components/ble/BleClient.h
|
components/ble/BleClient.h
|
||||||
components/ble/HeartRateService.h
|
components/ble/HeartRateService.h
|
||||||
components/ble/MotionService.h
|
components/ble/MotionService.h
|
||||||
components/ble/weather/WeatherService.h
|
components/ble/SimpleWeatherService.h
|
||||||
components/settings/Settings.h
|
components/settings/Settings.h
|
||||||
components/timer/Timer.h
|
components/timer/Timer.h
|
||||||
components/alarm/AlarmController.h
|
components/alarm/AlarmController.h
|
||||||
|
@ -802,18 +794,18 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
|
||||||
if(TARGET_DEVICE STREQUAL "PINETIME")
|
if(TARGET_DEVICE STREQUAL "PINETIME")
|
||||||
add_definitions(-DDRIVER_PINMAP_PINETIME)
|
add_definitions(-DDRIVER_PINMAP_PINETIME)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
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(-DDRIVER_PINMAP_P8)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
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(-DDRIVER_PINMAP_P8)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
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(-DDRIVER_PINMAP_P8)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
|
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(-DDRIVER_PINMAP_P8)
|
||||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||||
|
@ -854,6 +846,8 @@ target_compile_options(infinitime_fonts PUBLIC
|
||||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(displayapp/apps)
|
||||||
|
|
||||||
# NRF SDK
|
# NRF SDK
|
||||||
add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
|
add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
|
||||||
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
|
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
|
||||||
|
@ -891,27 +885,6 @@ target_compile_options(lvgl PRIVATE
|
||||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_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
|
# LITTLEFS_SRC
|
||||||
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
||||||
target_include_directories(littlefs SYSTEM PUBLIC . ../)
|
target_include_directories(littlefs SYSTEM PUBLIC . ../)
|
||||||
|
@ -930,7 +903,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime
|
||||||
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
||||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
||||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
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
|
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
${WARNING_FLAGS}
|
${WARNING_FLAGS}
|
||||||
|
@ -964,7 +937,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS
|
||||||
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
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")
|
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
||||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
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})
|
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
@ -1006,7 +979,7 @@ endif()
|
||||||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
||||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
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})
|
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})
|
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_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
||||||
|
@ -1038,7 +1011,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$
|
||||||
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
|
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)
|
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})
|
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})
|
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_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
||||||
|
@ -1078,7 +1051,7 @@ endif()
|
||||||
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
||||||
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
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})
|
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})
|
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
@ -1113,7 +1086,7 @@ set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_N
|
||||||
set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME_HEX ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex)
|
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)
|
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})
|
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})
|
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#include "components/ble/NavigationService.h"
|
#include "components/ble/NavigationService.h"
|
||||||
#include "components/ble/ServiceDiscovery.h"
|
#include "components/ble/ServiceDiscovery.h"
|
||||||
#include "components/ble/MotionService.h"
|
#include "components/ble/MotionService.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/fs/FS.h"
|
#include "components/fs/FS.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
@ -67,7 +67,7 @@ namespace Pinetime {
|
||||||
return anService;
|
return anService;
|
||||||
};
|
};
|
||||||
|
|
||||||
Pinetime::Controllers::WeatherService& weather() {
|
Pinetime::Controllers::SimpleWeatherService& weather() {
|
||||||
return weatherService;
|
return weatherService;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ namespace Pinetime {
|
||||||
AlertNotificationClient alertNotificationClient;
|
AlertNotificationClient alertNotificationClient;
|
||||||
CurrentTimeService currentTimeService;
|
CurrentTimeService currentTimeService;
|
||||||
MusicService musicService;
|
MusicService musicService;
|
||||||
WeatherService weatherService;
|
SimpleWeatherService weatherService;
|
||||||
NavigationService navService;
|
NavigationService navService;
|
||||||
BatteryInformationService batteryInformationService;
|
BatteryInformationService batteryInformationService;
|
||||||
ImmediateAlertService immediateAlertService;
|
ImmediateAlertService immediateAlertService;
|
||||||
|
|
160
src/components/ble/SimpleWeatherService.cpp
Normal file
160
src/components/ble/SimpleWeatherService.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
145
src/components/ble/SimpleWeatherService.h
Normal file
145
src/components/ble/SimpleWeatherService.h
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -140,9 +140,9 @@ std::string DateTime::FormattedTime() {
|
||||||
hour12 = (hour == 12) ? 12 : hour - 12;
|
hour12 = (hour == 12) ? 12 : hour - 12;
|
||||||
amPmStr = "PM";
|
amPmStr = "PM";
|
||||||
}
|
}
|
||||||
sprintf(buff, "%i:%02i %s", hour12, minute, amPmStr);
|
snprintf(buff, sizeof(buff), "%i:%02i %s", hour12, minute, amPmStr);
|
||||||
} else {
|
} else {
|
||||||
sprintf(buff, "%02i:%02i", hour, minute);
|
snprintf(buff, sizeof(buff), "%02i:%02i", hour, minute);
|
||||||
}
|
}
|
||||||
return std::string(buff);
|
return std::string(buff);
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ void Gfx::SetBackgroundColor(uint16_t color) {
|
||||||
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
|
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
|
||||||
if (!state.busy)
|
if (!state.busy)
|
||||||
return false;
|
return false;
|
||||||
state.remainingIterations--;
|
state.remainingIterations = state.remainingIterations - 1;
|
||||||
if (state.remainingIterations == 0) {
|
if (state.remainingIterations == 0) {
|
||||||
state.busy = false;
|
state.busy = false;
|
||||||
NotifyEndOfTransfer(state.taskToNotify);
|
NotifyEndOfTransfer(state.taskToNotify);
|
||||||
|
@ -170,7 +170,7 @@ bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
|
||||||
size = bytes_in_line * 8 * 2;
|
size = bytes_in_line * 8 * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.currentIteration++;
|
state.currentIteration = state.currentIteration + 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include "components/brightness/BrightnessController.h"
|
#include "components/brightness/BrightnessController.h"
|
||||||
#include "components/fs/FS.h"
|
#include "components/fs/FS.h"
|
||||||
#include "displayapp/WatchFaces.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
class Settings {
|
class Settings {
|
||||||
public:
|
public:
|
||||||
enum class ClockType : uint8_t { H24, H12 };
|
enum class ClockType : uint8_t { H24, H12, Fuzzy };
|
||||||
|
enum class WeatherFormat : uint8_t { Metric, Imperial };
|
||||||
enum class Notification : uint8_t { On, Off, Sleep };
|
enum class Notification : uint8_t { On, Off, Sleep };
|
||||||
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
||||||
enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
|
enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
|
||||||
|
@ -180,6 +181,17 @@ namespace Pinetime {
|
||||||
return settings.clockType;
|
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) {
|
void SetNotificationStatus(Notification status) {
|
||||||
if (status != settings.notificationStatus) {
|
if (status != settings.notificationStatus) {
|
||||||
settingsChanged = true;
|
settingsChanged = true;
|
||||||
|
@ -274,7 +286,7 @@ namespace Pinetime {
|
||||||
private:
|
private:
|
||||||
Pinetime::Controllers::FS& fs;
|
Pinetime::Controllers::FS& fs;
|
||||||
|
|
||||||
static constexpr uint32_t settingsVersion = 0x0006;
|
static constexpr uint32_t settingsVersion = 0x0007;
|
||||||
|
|
||||||
struct SettingsData {
|
struct SettingsData {
|
||||||
uint32_t version = settingsVersion;
|
uint32_t version = settingsVersion;
|
||||||
|
@ -282,6 +294,7 @@ namespace Pinetime {
|
||||||
uint32_t screenTimeOut = 15000;
|
uint32_t screenTimeOut = 15000;
|
||||||
|
|
||||||
ClockType clockType = ClockType::H24;
|
ClockType clockType = ClockType::H24;
|
||||||
|
WeatherFormat weatherFormat = WeatherFormat::Metric;
|
||||||
Notification notificationStatus = Notification::On;
|
Notification notificationStatus = Notification::On;
|
||||||
|
|
||||||
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
|
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Applications {
|
|
||||||
enum class Apps {
|
|
||||||
None,
|
|
||||||
Launcher,
|
|
||||||
Clock,
|
|
||||||
SysInfo,
|
|
||||||
FirmwareUpdate,
|
|
||||||
FirmwareValidation,
|
|
||||||
NotificationsPreview,
|
|
||||||
Notifications,
|
|
||||||
Timer,
|
|
||||||
Alarm,
|
|
||||||
FlashLight,
|
|
||||||
BatteryInfo,
|
|
||||||
Music,
|
|
||||||
Twos,
|
|
||||||
HeartRate,
|
|
||||||
Navigation,
|
|
||||||
StopWatch,
|
|
||||||
Metronome,
|
|
||||||
Motion,
|
|
||||||
Steps,
|
|
||||||
PassKey,
|
|
||||||
QuickSettings,
|
|
||||||
Settings,
|
|
||||||
SettingWatchFace,
|
|
||||||
SettingTimeFormat,
|
|
||||||
SettingDisplay,
|
|
||||||
SettingWakeUp,
|
|
||||||
SettingSteps,
|
|
||||||
SettingSetDateTime,
|
|
||||||
SettingChimes,
|
|
||||||
SettingShakeThreshold,
|
|
||||||
SettingBluetooth,
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
56
src/displayapp/Controllers.h
Normal file
56
src/displayapp/Controllers.h
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,10 @@
|
||||||
#include "components/motion/MotionController.h"
|
#include "components/motion/MotionController.h"
|
||||||
#include "components/motor/MotorController.h"
|
#include "components/motor/MotorController.h"
|
||||||
#include "displayapp/screens/ApplicationList.h"
|
#include "displayapp/screens/ApplicationList.h"
|
||||||
#include "displayapp/screens/Clock.h"
|
|
||||||
#include "displayapp/screens/FirmwareUpdate.h"
|
#include "displayapp/screens/FirmwareUpdate.h"
|
||||||
#include "displayapp/screens/FirmwareValidation.h"
|
#include "displayapp/screens/FirmwareValidation.h"
|
||||||
|
#include "displayapp/screens/InfiniPaint.h"
|
||||||
|
#include "displayapp/screens/Paddle.h"
|
||||||
#include "displayapp/screens/StopWatch.h"
|
#include "displayapp/screens/StopWatch.h"
|
||||||
#include "displayapp/screens/Metronome.h"
|
#include "displayapp/screens/Metronome.h"
|
||||||
#include "displayapp/screens/Music.h"
|
#include "displayapp/screens/Music.h"
|
||||||
|
@ -27,7 +28,6 @@
|
||||||
#include "displayapp/screens/Steps.h"
|
#include "displayapp/screens/Steps.h"
|
||||||
#include "displayapp/screens/PassKey.h"
|
#include "displayapp/screens/PassKey.h"
|
||||||
#include "displayapp/screens/Error.h"
|
#include "displayapp/screens/Error.h"
|
||||||
#include "displayapp/screens/Weather.h"
|
|
||||||
|
|
||||||
#include "drivers/Cst816s.h"
|
#include "drivers/Cst816s.h"
|
||||||
#include "drivers/St7789.h"
|
#include "drivers/St7789.h"
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
#include "displayapp/screens/settings/Settings.h"
|
#include "displayapp/screens/settings/Settings.h"
|
||||||
#include "displayapp/screens/settings/SettingWatchFace.h"
|
#include "displayapp/screens/settings/SettingWatchFace.h"
|
||||||
#include "displayapp/screens/settings/SettingTimeFormat.h"
|
#include "displayapp/screens/settings/SettingTimeFormat.h"
|
||||||
|
#include "displayapp/screens/settings/SettingWeatherFormat.h"
|
||||||
#include "displayapp/screens/settings/SettingWakeUp.h"
|
#include "displayapp/screens/settings/SettingWakeUp.h"
|
||||||
#include "displayapp/screens/settings/SettingDisplay.h"
|
#include "displayapp/screens/settings/SettingDisplay.h"
|
||||||
#include "displayapp/screens/settings/SettingSteps.h"
|
#include "displayapp/screens/settings/SettingSteps.h"
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
#include "displayapp/screens/settings/SettingBluetooth.h"
|
#include "displayapp/screens/settings/SettingBluetooth.h"
|
||||||
|
|
||||||
#include "libs/lv_conf.h"
|
#include "libs/lv_conf.h"
|
||||||
|
#include "UserApps.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications;
|
using namespace Pinetime::Applications;
|
||||||
using namespace Pinetime::Applications::Display;
|
using namespace Pinetime::Applications::Display;
|
||||||
|
@ -94,7 +96,25 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
||||||
touchHandler {touchHandler},
|
touchHandler {touchHandler},
|
||||||
filesystem {filesystem},
|
filesystem {filesystem},
|
||||||
lvgl {lcd, 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) {
|
void DisplayApp::Start(System::BootErrors error) {
|
||||||
|
@ -400,26 +420,31 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
SetFullRefresh(direction);
|
SetFullRefresh(direction);
|
||||||
|
|
||||||
switch (app) {
|
switch (app) {
|
||||||
case Apps::Launcher:
|
case Apps::Launcher: {
|
||||||
currentScreen =
|
std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
|
||||||
std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController, filesystem);
|
int i = 0;
|
||||||
break;
|
for (const auto& userApp : userApps) {
|
||||||
case Apps::Motion:
|
apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
|
||||||
// currentScreen = std::make_unique<Screens::Motion>(motionController);
|
}
|
||||||
// break;
|
currentScreen = std::make_unique<Screens::ApplicationList>(this,
|
||||||
case Apps::None:
|
settingsController,
|
||||||
case Apps::Clock:
|
|
||||||
currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
|
|
||||||
batteryController,
|
batteryController,
|
||||||
bleController,
|
bleController,
|
||||||
notificationManager,
|
dateTimeController,
|
||||||
settingsController,
|
filesystem,
|
||||||
heartRateController,
|
std::move(apps));
|
||||||
motionController,
|
} break;
|
||||||
systemTask->nimble().weather(),
|
case Apps::Clock: {
|
||||||
filesystem);
|
const auto* watchFace =
|
||||||
break;
|
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:
|
case Apps::Error:
|
||||||
currentScreen = std::make_unique<Screens::Error>(bootError);
|
currentScreen = std::make_unique<Screens::Error>(bootError);
|
||||||
break;
|
break;
|
||||||
|
@ -451,14 +476,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
*systemTask,
|
*systemTask,
|
||||||
Screens::Notifications::Modes::Preview);
|
Screens::Notifications::Modes::Preview);
|
||||||
break;
|
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:
|
case Apps::QuickSettings:
|
||||||
currentScreen = std::make_unique<Screens::QuickSettings>(this,
|
currentScreen = std::make_unique<Screens::QuickSettings>(this,
|
||||||
batteryController,
|
batteryController,
|
||||||
|
@ -471,12 +488,20 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
case Apps::Settings:
|
case Apps::Settings:
|
||||||
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
||||||
break;
|
break;
|
||||||
case Apps::SettingWatchFace:
|
case Apps::SettingWatchFace: {
|
||||||
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, settingsController, filesystem);
|
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
|
||||||
break;
|
int i = 0;
|
||||||
|
for (const auto& userWatchFace : userWatchFaces) {
|
||||||
|
items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
|
||||||
|
}
|
||||||
|
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
|
||||||
|
} break;
|
||||||
case Apps::SettingTimeFormat:
|
case Apps::SettingTimeFormat:
|
||||||
currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
|
currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
|
||||||
break;
|
break;
|
||||||
|
case Apps::SettingWeatherFormat:
|
||||||
|
currentScreen = std::make_unique<Screens::SettingWeatherFormat>(settingsController);
|
||||||
|
break;
|
||||||
case Apps::SettingWakeUp:
|
case Apps::SettingWakeUp:
|
||||||
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
||||||
break;
|
break;
|
||||||
|
@ -514,33 +539,18 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
case Apps::FlashLight:
|
case Apps::FlashLight:
|
||||||
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
||||||
break;
|
break;
|
||||||
case Apps::StopWatch:
|
default: {
|
||||||
currentScreen = std::make_unique<Screens::StopWatch>(*systemTask);
|
const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
|
||||||
break;
|
return appDescription.app == app;
|
||||||
case Apps::Twos:
|
});
|
||||||
currentScreen = std::make_unique<Screens::Twos>();
|
if (d != userApps.end()) {
|
||||||
break;
|
currentScreen.reset(d->create(controllers));
|
||||||
case Apps::Music:
|
} else {
|
||||||
currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music());
|
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||||
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);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
currentApp = app;
|
currentApp = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,7 +562,15 @@ void DisplayApp::PushMessage(Messages msg) {
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,6 +607,19 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
|
||||||
|
|
||||||
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
|
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
|
||||||
this->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() {
|
void DisplayApp::ApplyBrightness() {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <systemtask/Messages.h>
|
#include <systemtask/Messages.h>
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/LittleVgl.h"
|
#include "displayapp/LittleVgl.h"
|
||||||
#include "displayapp/TouchEvents.h"
|
#include "displayapp/TouchEvents.h"
|
||||||
#include "components/brightness/BrightnessController.h"
|
#include "components/brightness/BrightnessController.h"
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
#include "BootErrors.h"
|
#include "BootErrors.h"
|
||||||
|
|
||||||
#include "utility/StaticStack.h"
|
#include "utility/StaticStack.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ namespace Pinetime {
|
||||||
class HeartRateController;
|
class HeartRateController;
|
||||||
class MotionController;
|
class MotionController;
|
||||||
class TouchHandler;
|
class TouchHandler;
|
||||||
|
class SimpleWeatherService;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
|
@ -73,6 +75,9 @@ namespace Pinetime {
|
||||||
void SetFullRefresh(FullRefreshDirections direction);
|
void SetFullRefresh(FullRefreshDirections direction);
|
||||||
|
|
||||||
void Register(Pinetime::System::SystemTask* systemTask);
|
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:
|
private:
|
||||||
Pinetime::Drivers::St7789& lcd;
|
Pinetime::Drivers::St7789& lcd;
|
||||||
|
@ -96,6 +101,7 @@ namespace Pinetime {
|
||||||
Pinetime::Components::LittleVgl lvgl;
|
Pinetime::Components::LittleVgl lvgl;
|
||||||
Pinetime::Controllers::Timer timer;
|
Pinetime::Controllers::Timer timer;
|
||||||
|
|
||||||
|
AppControllers controllers;
|
||||||
TaskHandle_t taskHandle;
|
TaskHandle_t taskHandle;
|
||||||
|
|
||||||
States state = States::Running;
|
States state = States::Running;
|
||||||
|
|
|
@ -121,3 +121,12 @@ void DisplayApp::PushMessage(Display::Messages msg) {
|
||||||
|
|
||||||
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
|
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*/) {
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#include <components/motor/MotorController.h>
|
#include <components/motor/MotorController.h>
|
||||||
#include "BootErrors.h"
|
#include "BootErrors.h"
|
||||||
#include "displayapp/TouchEvents.h"
|
#include "displayapp/TouchEvents.h"
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/Messages.h"
|
#include "displayapp/Messages.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
@ -34,6 +34,9 @@ namespace Pinetime {
|
||||||
class AlarmController;
|
class AlarmController;
|
||||||
class BrightnessController;
|
class BrightnessController;
|
||||||
class FS;
|
class FS;
|
||||||
|
class SimpleWeatherService;
|
||||||
|
class MusicService;
|
||||||
|
class NavigationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
|
@ -66,6 +69,9 @@ namespace Pinetime {
|
||||||
|
|
||||||
void PushMessage(Pinetime::Applications::Display::Messages msg);
|
void PushMessage(Pinetime::Applications::Display::Messages msg);
|
||||||
void Register(Pinetime::System::SystemTask* systemTask);
|
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:
|
private:
|
||||||
TaskHandle_t taskHandle;
|
TaskHandle_t taskHandle;
|
||||||
|
|
59
src/displayapp/UserApps.h
Normal file
59
src/displayapp/UserApps.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#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/WatchFaceAnalog.h"
|
||||||
|
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||||
|
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||||
|
#include "displayapp/screens/WatchFacePineTimeStyle.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 {});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Applications {
|
|
||||||
enum class WatchFace : uint8_t {
|
|
||||||
Digital = 0,
|
|
||||||
Analog = 1,
|
|
||||||
PineTimeStyle = 2,
|
|
||||||
Infineat = 3,
|
|
||||||
CasioStyleG7710 = 4,
|
|
||||||
Fuzzy = 5,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
83
src/displayapp/apps/Apps.h.in
Normal file
83
src/displayapp/apps/Apps.h.in
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#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,
|
||||||
|
Analog,
|
||||||
|
PineTimeStyle,
|
||||||
|
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::Digital,
|
||||||
|
WatchFace::Analog,
|
||||||
|
WatchFace::PineTimeStyle,
|
||||||
|
WatchFace::Terminal,
|
||||||
|
WatchFace::Infineat,
|
||||||
|
WatchFace::CasioStyleG7710>;
|
||||||
|
|
||||||
|
static_assert(UserWatchFaceTypes::Count >= 1);
|
||||||
|
}
|
||||||
|
}
|
15
src/displayapp/apps/CMakeLists.txt
Normal file
15
src/displayapp/apps/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
if(DEFINED ENABLE_USERAPPS)
|
||||||
|
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
|
||||||
|
else ()
|
||||||
|
set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Alarm, Apps::Timer, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Twos" CACHE STRING "List of user apps to build into the firmware")
|
||||||
|
#Apps::Paint,
|
||||||
|
#Apps::Metronome,
|
||||||
|
#Apps::Paddle,
|
||||||
|
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)
|
|
@ -1,6 +1,6 @@
|
||||||
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20
|
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20
|
||||||
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
||||||
open_sans_light fontawesome_weathericons vulf_mono_italic)
|
open_sans_light fontawesome_weathericons)
|
||||||
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
||||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
||||||
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
||||||
|
|
|
@ -3,31 +3,22 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Bold.ttf",
|
"file": "JetBrainsMono-Bold.ttf",
|
||||||
"range": "0x20-0x7e, 0xB0, 0x410-0x44f"
|
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
"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,
|
"bpp": 1,
|
||||||
"size": 20
|
"size": 20,
|
||||||
},
|
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
|
||||||
"vulf_mono_italic": {
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"file": "Vulf_Mono-Italic.woff",
|
|
||||||
"range": "0x20-0x7e, 0xB0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bpp": 1,
|
|
||||||
"size": 30
|
|
||||||
},
|
},
|
||||||
"jetbrains_mono_42": {
|
"jetbrains_mono_42": {
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Regular.ttf",
|
"file": "JetBrainsMono-Regular.ttf",
|
||||||
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a"
|
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
|
@ -47,7 +38,7 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-ExtraBold.ttf",
|
"file": "JetBrainsMono-ExtraBold.ttf",
|
||||||
"range": "0x20, 0x30-0x3a"
|
"range": "0x30-0x3a"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
|
@ -77,7 +68,7 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
"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,
|
"bpp": 1,
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "displayapp/screens/Symbols.h"
|
#include "displayapp/screens/Symbols.h"
|
||||||
#include "displayapp/InfiniTimeTheme.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 namespace Pinetime::Applications::Screens;
|
||||||
using Pinetime::Controllers::AlarmController;
|
using Pinetime::Controllers::AlarmController;
|
||||||
|
|
|
@ -17,18 +17,19 @@
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include "components/settings/Settings.h"
|
||||||
#include "displayapp/screens/Screen.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/widgets/Counter.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
namespace Screens {
|
namespace Screens {
|
||||||
class Alarm : public Screen {
|
class Alarm : public Screen {
|
||||||
public:
|
public:
|
||||||
Alarm(Controllers::AlarmController& alarmController,
|
explicit Alarm(Controllers::AlarmController& alarmController,
|
||||||
Controllers::Settings::ClockType clockType,
|
Controllers::Settings::ClockType clockType,
|
||||||
System::SystemTask& systemTask,
|
System::SystemTask& systemTask,
|
||||||
Controllers::MotorController& motorController);
|
Controllers::MotorController& motorController);
|
||||||
|
@ -63,6 +64,19 @@ namespace Pinetime {
|
||||||
Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
|
Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
|
||||||
Widgets::Counter minuteCounter = Widgets::Counter(0, 59, 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);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include "displayapp/screens/ApplicationList.h"
|
#include "displayapp/screens/ApplicationList.h"
|
||||||
|
#include "displayapp/screens/Tile.h"
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "displayapp/Apps.h"
|
#include <algorithm>
|
||||||
#include "displayapp/DisplayApp.h"
|
#include "components/settings/Settings.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
@ -16,18 +17,20 @@ auto ApplicationList::CreateScreenList() const {
|
||||||
return screens;
|
return screens;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app,
|
ApplicationList::ApplicationList(DisplayApp* app,
|
||||||
Pinetime::Controllers::Settings& settingsController,
|
Pinetime::Controllers::Settings& settingsController,
|
||||||
const Pinetime::Controllers::Battery& batteryController,
|
const Pinetime::Controllers::Battery& batteryController,
|
||||||
const Pinetime::Controllers::Ble& bleController,
|
const Pinetime::Controllers::Ble& bleController,
|
||||||
Controllers::DateTime& dateTimeController,
|
Controllers::DateTime& dateTimeController,
|
||||||
Pinetime::Controllers::FS& filesystem)
|
Pinetime::Controllers::FS& filesystem,
|
||||||
|
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
|
||||||
: app {app},
|
: app {app},
|
||||||
settingsController {settingsController},
|
settingsController {settingsController},
|
||||||
batteryController {batteryController},
|
batteryController {batteryController},
|
||||||
bleController {bleController},
|
bleController {bleController},
|
||||||
dateTimeController {dateTimeController},
|
dateTimeController {dateTimeController},
|
||||||
filesystem {filesystem},
|
filesystem {filesystem},
|
||||||
|
apps {std::move(apps)},
|
||||||
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,9 +43,14 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
|
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++) {
|
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,
|
return std::make_unique<Screens::Tile>(screenNum,
|
||||||
|
@ -52,5 +60,5 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
|
||||||
batteryController,
|
batteryController,
|
||||||
bleController,
|
bleController,
|
||||||
dateTimeController,
|
dateTimeController,
|
||||||
apps);
|
pageApps);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,12 @@
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "Screen.h"
|
||||||
#include "displayapp/screens/ScreenList.h"
|
#include "ScreenList.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "displayapp/Controllers.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "Symbols.h"
|
||||||
#include "components/battery/BatteryController.h"
|
#include "Tile.h"
|
||||||
#include "displayapp/screens/Symbols.h"
|
|
||||||
#include "displayapp/screens/Tile.h"
|
|
||||||
#include "displayapp/screens/Navigation.h"
|
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
|
@ -22,7 +19,8 @@ namespace Pinetime {
|
||||||
const Pinetime::Controllers::Battery& batteryController,
|
const Pinetime::Controllers::Battery& batteryController,
|
||||||
const Pinetime::Controllers::Ble& bleController,
|
const Pinetime::Controllers::Ble& bleController,
|
||||||
Controllers::DateTime& dateTimeController,
|
Controllers::DateTime& dateTimeController,
|
||||||
Pinetime::Controllers::FS& filesystem);
|
Pinetime::Controllers::FS& filesystem,
|
||||||
|
std::array<Tile::Applications, UserAppTypes::Count>&& apps);
|
||||||
~ApplicationList() override;
|
~ApplicationList() override;
|
||||||
bool OnTouchEvent(TouchEvents event) override;
|
bool OnTouchEvent(TouchEvents event) override;
|
||||||
|
|
||||||
|
@ -36,27 +34,12 @@ namespace Pinetime {
|
||||||
const Pinetime::Controllers::Ble& bleController;
|
const Pinetime::Controllers::Ble& bleController;
|
||||||
Controllers::DateTime& dateTimeController;
|
Controllers::DateTime& dateTimeController;
|
||||||
Pinetime::Controllers::FS& filesystem;
|
Pinetime::Controllers::FS& filesystem;
|
||||||
|
std::array<Tile::Applications, UserAppTypes::Count> apps;
|
||||||
|
|
||||||
static constexpr int appsPerScreen = 6;
|
static constexpr int appsPerScreen = 6;
|
||||||
|
|
||||||
// Increment this when more space is needed
|
static constexpr int nScreens = UserAppTypes::Count > 0 ? (UserAppTypes::Count - 1) / appsPerScreen + 1 : 1;
|
||||||
static constexpr int nScreens = 2;
|
|
||||||
|
|
||||||
std::array<Tile::Applications, appsPerScreen * nScreens> applications {{
|
|
||||||
{Symbols::stopWatch, Apps::StopWatch, true},
|
|
||||||
{Symbols::clock, Apps::Alarm, true},
|
|
||||||
{Symbols::hourGlass, Apps::Timer, true},
|
|
||||||
{Symbols::shoe, Apps::Steps, true},
|
|
||||||
{Symbols::heartBeat, Apps::HeartRate, true},
|
|
||||||
{Symbols::music, Apps::Music, true},
|
|
||||||
|
|
||||||
{"2", Apps::Twos, true},
|
|
||||||
{Symbols::drum, Apps::Metronome, true},
|
|
||||||
{Symbols::map, Apps::Navigation, Applications::Screens::Navigation::IsAvailable(filesystem)},
|
|
||||||
{Symbols::none, Apps::None, false},
|
|
||||||
|
|
||||||
// {"M", Apps::Motion},
|
|
||||||
}};
|
|
||||||
ScreenList<nScreens> screens;
|
ScreenList<nScreens> screens;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
|
@ -1,145 +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/WatchFaceInfineat.h"
|
|
||||||
#include "displayapp/screens/WatchFaceAnalog.h"
|
|
||||||
#include "displayapp/screens/WatchFaceFuzzy.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::Analog:
|
|
||||||
return WatchFaceAnalogScreen();
|
|
||||||
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;
|
|
||||||
case WatchFace::Fuzzy:
|
|
||||||
return WatchFaceFuzzy();
|
|
||||||
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::WatchFaceAnalogScreen() {
|
|
||||||
return std::make_unique<Screens::WatchFaceAnalog>(dateTimeController,
|
|
||||||
batteryController,
|
|
||||||
bleController,
|
|
||||||
notificationManager,
|
|
||||||
settingsController);
|
|
||||||
}
|
|
||||||
|
|
||||||
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::WatchFaceFuzzy() {
|
|
||||||
return std::make_unique<Screens::WatchFaceFuzzy>(dateTimeController,
|
|
||||||
batteryController,
|
|
||||||
bleController,
|
|
||||||
notificationManager,
|
|
||||||
heartRateController,
|
|
||||||
motionController);
|
|
||||||
}
|
|
|
@ -1,61 +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> WatchFaceAnalogScreen();
|
|
||||||
std::unique_ptr<Screen> WatchFacePineTimeStyleScreen();
|
|
||||||
std::unique_ptr<Screen> WatchFaceTerminalScreen();
|
|
||||||
std::unique_ptr<Screen> WatchFaceInfineatScreen();
|
|
||||||
std::unique_ptr<Screen> WatchFaceCasioStyleG7710();
|
|
||||||
std::unique_ptr<Screen> WatchFaceFuzzy();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "systemtask/SystemTask.h"
|
#include "systemtask/SystemTask.h"
|
||||||
|
#include "Symbols.h"
|
||||||
#include <lvgl/src/lv_core/lv_style.h>
|
#include <lvgl/src/lv_core/lv_style.h>
|
||||||
#include <lvgl/src/lv_core/lv_obj.h>
|
#include <lvgl/src/lv_core/lv_obj.h>
|
||||||
|
|
||||||
|
@ -37,5 +38,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
#include <algorithm> // std::fill
|
#include <algorithm> // std::fill
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "components/motor/MotorController.h"
|
#include "components/motor/MotorController.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include <displayapp/Controllers.h>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
|
@ -35,5 +38,15 @@ namespace Pinetime {
|
||||||
uint8_t color = 2;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "displayapp/widgets/PageIndicator.h"
|
#include "displayapp/widgets/PageIndicator.h"
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
|
|
||||||
#define MAXLISTITEMS 4
|
#define MAXLISTITEMS 4
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "systemtask/SystemTask.h"
|
#include "systemtask/SystemTask.h"
|
||||||
#include "components/motor/MotorController.h"
|
#include "components/motor/MotorController.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
|
@ -36,5 +37,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include <lvgl/src/lv_core/lv_style.h>
|
#include <lvgl/src/lv_core/lv_style.h>
|
||||||
#include <lvgl/src/lv_core/lv_obj.h>
|
#include <lvgl/src/lv_core/lv_obj.h>
|
||||||
#include <components/motion/MotionController.h>
|
#include <components/motion/MotionController.h>
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
|
@ -30,5 +32,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@
|
||||||
#include <lvgl/src/lv_core/lv_obj.h>
|
#include <lvgl/src/lv_core/lv_obj.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
@ -82,5 +85,15 @@ namespace Pinetime {
|
||||||
/** Watchapp */
|
/** 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,19 +203,21 @@ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navServi
|
||||||
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
||||||
|
|
||||||
txtNarrative = lv_label_create(lv_scr_act(), nullptr);
|
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_obj_set_width(txtNarrative, LV_HOR_RES);
|
||||||
|
lv_obj_set_height(txtNarrative, 80);
|
||||||
lv_label_set_text_static(txtNarrative, "Navigation");
|
lv_label_set_text_static(txtNarrative, "Navigation");
|
||||||
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
||||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10);
|
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
|
||||||
|
|
||||||
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
|
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_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_obj_set_width(txtManDist, LV_HOR_RES);
|
||||||
lv_label_set_text_static(txtManDist, "--M");
|
lv_label_set_text_static(txtManDist, "--M");
|
||||||
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
||||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60);
|
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
|
||||||
|
|
||||||
// Route Progress
|
// Route Progress
|
||||||
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
@ -55,5 +58,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
|
@ -45,5 +48,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include <components/motion/MotionController.h>
|
#include <components/motion/MotionController.h>
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
|
||||||
|
@ -39,5 +42,15 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,11 +198,11 @@ void StopWatch::stopLapBtnEventHandler() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
|
TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
|
||||||
char buffer[16];
|
char buffer[17];
|
||||||
if (times.hours == 0) {
|
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 {
|
} 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);
|
lv_label_ins_text(lapText, LV_LABEL_POS_LAST, buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,13 @@
|
||||||
#include "portmacro_cmsis.h"
|
#include "portmacro_cmsis.h"
|
||||||
|
|
||||||
#include "systemtask/SystemTask.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 };
|
||||||
|
|
||||||
|
@ -54,3 +59,15 @@ namespace Pinetime::Applications::Screens {
|
||||||
lv_task_t* taskRefresh;
|
lv_task_t* taskRefresh;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AppTraits<Apps::StopWatch> {
|
||||||
|
static constexpr Apps app = Apps::StopWatch;
|
||||||
|
static constexpr const char* icon = Screens::Symbols::stopWatch;
|
||||||
|
|
||||||
|
static Screens::Screen* Create(AppControllers& controllers) {
|
||||||
|
return new Screens::StopWatch(*controllers.systemTask);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,9 @@ namespace Pinetime {
|
||||||
static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
|
static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
|
||||||
static constexpr const char* smog = "\xEF\x9D\x9F";
|
static constexpr const char* smog = "\xEF\x9D\x9F";
|
||||||
static constexpr const char* cloud = "\xEF\x83\x82";
|
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";
|
static constexpr const char* ban = "\xEF\x81\x9E";
|
||||||
|
|
||||||
// lv_font_sys_48.c
|
// lv_font_sys_48.c
|
||||||
|
|
|
@ -236,9 +236,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||||
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
|
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
|
||||||
std::sort(tasksStatus, tasksStatus + nb, sortById);
|
std::sort(tasksStatus, tasksStatus + nb, sortById);
|
||||||
for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
|
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);
|
lv_table_set_cell_value(infoTask, i + 1, 0, buffer);
|
||||||
switch (tasksStatus[i].eCurrentState) {
|
switch (tasksStatus[i].eCurrentState) {
|
||||||
case eReady:
|
case eReady:
|
||||||
|
@ -262,9 +262,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
||||||
lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
|
lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
|
||||||
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
|
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
|
||||||
if (tasksStatus[i].usStackHighWaterMark < 20) {
|
if (tasksStatus[i].usStackHighWaterMark < 20) {
|
||||||
sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark);
|
snprintf(buffer, sizeof(buffer), "%" PRIu16 " low", tasksStatus[i].usStackHighWaterMark);
|
||||||
} else {
|
} 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);
|
lv_table_set_cell_value(infoTask, i + 1, 3, buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "displayapp/screens/Tile.h"
|
#include "displayapp/screens/Tile.h"
|
||||||
#include "displayapp/DisplayApp.h"
|
|
||||||
#include "displayapp/screens/BatteryIcon.h"
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "displayapp/InfiniTimeTheme.h"
|
#include "displayapp/InfiniTimeTheme.h"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
#include "components/battery/BatteryController.h"
|
#include "components/battery/BatteryController.h"
|
||||||
|
|
|
@ -62,7 +62,7 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} {
|
||||||
txtPlayPause = lv_label_create(lv_scr_act(), nullptr);
|
txtPlayPause = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
|
lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
|
||||||
if (timerController.IsRunning()) {
|
if (timer.IsRunning()) {
|
||||||
SetTimerRunning();
|
SetTimerRunning();
|
||||||
} else {
|
} else {
|
||||||
SetTimerStopped();
|
SetTimerStopped();
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
|
|
||||||
#include "components/timer/Timer.h"
|
#include "components/timer/Timer.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
|
||||||
namespace Pinetime::Applications::Screens {
|
namespace Pinetime::Applications {
|
||||||
|
namespace Screens {
|
||||||
class Timer : public Screen {
|
class Timer : public Screen {
|
||||||
public:
|
public:
|
||||||
Timer(Controllers::Timer& timerController);
|
Timer(Controllers::Timer& timerController);
|
||||||
|
@ -24,7 +26,7 @@ namespace Pinetime::Applications::Screens {
|
||||||
void SetTimerRunning();
|
void SetTimerRunning();
|
||||||
void SetTimerStopped();
|
void SetTimerStopped();
|
||||||
void UpdateMask();
|
void UpdateMask();
|
||||||
Controllers::Timer& timer;
|
Pinetime::Controllers::Timer& timer;
|
||||||
|
|
||||||
lv_obj_t* btnPlayPause;
|
lv_obj_t* btnPlayPause;
|
||||||
lv_obj_t* txtPlayPause;
|
lv_obj_t* txtPlayPause;
|
||||||
|
@ -43,3 +45,14 @@ namespace Pinetime::Applications::Screens {
|
||||||
TickType_t pressTime = 0;
|
TickType_t pressTime = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AppTraits<Apps::Timer> {
|
||||||
|
static constexpr Apps app = Apps::Timer;
|
||||||
|
static constexpr const char* icon = Screens::Symbols::hourGlass;
|
||||||
|
|
||||||
|
static Screens::Screen* Create(AppControllers& controllers) {
|
||||||
|
return new Screens::Timer(controllers.timer);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -242,7 +242,7 @@ void Twos::updateGridDisplay() {
|
||||||
const unsigned int col = i % nCols;
|
const unsigned int col = i % nCols;
|
||||||
if (grid[row][col].value > 0) {
|
if (grid[row][col].value > 0) {
|
||||||
char buffer[7];
|
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);
|
lv_table_set_cell_value(gridDisplay, row, col, buffer);
|
||||||
} else {
|
} else {
|
||||||
lv_table_set_cell_value(gridDisplay, row, col, "");
|
lv_table_set_cell_value(gridDisplay, row, col, "");
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <lvgl/src/lv_core/lv_obj.h>
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "displayapp/Controllers.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
|
@ -35,5 +36,15 @@ namespace Pinetime {
|
||||||
bool placeNewTile();
|
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();
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,5 +88,23 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
lv_task_t* taskRefresh;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <displayapp/Controllers.h>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
@ -100,5 +102,26 @@ namespace Pinetime {
|
||||||
lv_font_t* font_segment115 = 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,11 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
|
||||||
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
|
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
|
||||||
|
|
||||||
label_time = lv_label_create(lv_scr_act(), nullptr);
|
label_time = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
|
||||||
|
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||||
|
} else {
|
||||||
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
|
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
|
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
|
||||||
|
|
||||||
|
@ -91,7 +95,30 @@ void WatchFaceDigital::Refresh() {
|
||||||
uint8_t hour = dateTimeController.Hours();
|
uint8_t hour = dateTimeController.Hours();
|
||||||
uint8_t minute = dateTimeController.Minutes();
|
uint8_t minute = dateTimeController.Minutes();
|
||||||
|
|
||||||
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
/* Begin difference from WatchFaceDigital*/
|
||||||
|
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
|
||||||
|
std::string hourStr, timeStr;
|
||||||
|
hour = hour % 12; // 12 becomes 0, 13 becomes 1
|
||||||
|
auto sector = minute / 15 + (minute % 15 > 7);
|
||||||
|
// advance the hour modulo 12 and reset the minutes if we're close to the top
|
||||||
|
// so we get "quarter to $hour+1" instead of needing "three quarters past $hour"
|
||||||
|
if (sector > 3) {
|
||||||
|
hour = (hour + 1) % 12;
|
||||||
|
sector = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStr = timeSectors[sector];
|
||||||
|
if (timeStr.find("%1") != std::string::npos) {
|
||||||
|
hour = (hour + 1) % 12;
|
||||||
|
}
|
||||||
|
//hourStr = std::string("#") + timeAccent + " " + hourNames[hour] + "#";
|
||||||
|
hourStr = hourNames[hour];
|
||||||
|
timeStr.replace(timeStr.find("%"), 2, hourStr);
|
||||||
|
|
||||||
|
lv_label_set_text(label_time, timeStr.c_str());
|
||||||
|
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
||||||
|
/* End difference from WatchFaceDigital*/
|
||||||
|
} else if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
||||||
char ampmChar[3] = "AM";
|
char ampmChar[3] = "AM";
|
||||||
if (hour == 0) {
|
if (hour == 0) {
|
||||||
hour = 12;
|
hour = 12;
|
||||||
|
@ -154,3 +181,31 @@ void WatchFaceDigital::Refresh() {
|
||||||
lv_obj_realign(stepIcon);
|
lv_obj_realign(stepIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inspired by XFCE4-panel's fuzzy clock.
|
||||||
|
*
|
||||||
|
* https://salsa.debian.org/xfce-team/desktop/xfce4-panel/-/blob/debian/master/plugins/clock/clock-fuzzy.c
|
||||||
|
*
|
||||||
|
* Strings contain either a `%0` or a `%1`, indicating the position of
|
||||||
|
* the `hour` or `hour+1`, respectively.
|
||||||
|
*/
|
||||||
|
const char* WatchFaceDigital::timeSectors[] = {
|
||||||
|
"%0\no'clock",
|
||||||
|
"quarter\npast\n%0",
|
||||||
|
"half past\n%0",
|
||||||
|
"quarter\nto %1",
|
||||||
|
};
|
||||||
|
const char* WatchFaceDigital::hourNames[] = {
|
||||||
|
"twelve",
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"three",
|
||||||
|
"four",
|
||||||
|
"five",
|
||||||
|
"six",
|
||||||
|
"seven",
|
||||||
|
"eight",
|
||||||
|
"nine",
|
||||||
|
"ten",
|
||||||
|
"eleven",
|
||||||
|
};
|
|
@ -9,6 +9,7 @@
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "displayapp/widgets/StatusIcons.h"
|
#include "displayapp/widgets/StatusIcons.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
@ -39,6 +40,8 @@ namespace Pinetime {
|
||||||
private:
|
private:
|
||||||
uint8_t displayedHour = -1;
|
uint8_t displayedHour = -1;
|
||||||
uint8_t displayedMinute = -1;
|
uint8_t displayedMinute = -1;
|
||||||
|
static const char* timeSectors[4];
|
||||||
|
static const char* hourNames[12];
|
||||||
|
|
||||||
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
|
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
|
||||||
Utility::DirtyValue<bool> powerPresent {};
|
Utility::DirtyValue<bool> powerPresent {};
|
||||||
|
@ -71,5 +74,25 @@ namespace Pinetime {
|
||||||
Widgets::StatusIcons statusIcons;
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
#include "displayapp/screens/WatchFaceFuzzy.h"
|
|
||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <lvgl/src/lv_core/lv_obj_style_dec.h>
|
|
||||||
#include <lvgl/src/lv_font/lv_font.h>
|
|
||||||
#include <lvgl/src/lv_misc/lv_area.h>
|
|
||||||
#include "displayapp/screens/NotificationIcon.h"
|
|
||||||
#include "displayapp/screens/Symbols.h"
|
|
||||||
#include "components/battery/BatteryController.h"
|
|
||||||
#include "components/ble/BleController.h"
|
|
||||||
#include "components/ble/NotificationManager.h"
|
|
||||||
#include "components/heartrate/HeartRateController.h"
|
|
||||||
#include "components/motion/MotionController.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
|
||||||
|
|
||||||
WatchFaceFuzzy::WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
|
|
||||||
const Controllers::Battery& batteryController,
|
|
||||||
const Controllers::Ble& bleController,
|
|
||||||
Controllers::NotificationManager& notificationManager,
|
|
||||||
Controllers::HeartRateController& heartRateController,
|
|
||||||
Controllers::MotionController& motionController)
|
|
||||||
: currentDateTime {{}},
|
|
||||||
dateTimeController {dateTimeController},
|
|
||||||
notificationManager {notificationManager},
|
|
||||||
heartRateController {heartRateController},
|
|
||||||
motionController {motionController},
|
|
||||||
statusIcons(batteryController, bleController) {
|
|
||||||
|
|
||||||
statusIcons.Create();
|
|
||||||
|
|
||||||
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_LIME);
|
|
||||||
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
|
|
||||||
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
|
|
||||||
|
|
||||||
label_time = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &vulf_mono_italic);
|
|
||||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
|
|
||||||
lv_label_set_recolor(label_time, true);
|
|
||||||
|
|
||||||
label_date = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
|
|
||||||
lv_obj_align(label_date, label_time, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 10);
|
|
||||||
|
|
||||||
heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_label_set_text_static(heartbeatIcon, Symbols::heartBeat);
|
|
||||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
|
|
||||||
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
|
|
||||||
|
|
||||||
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
|
|
||||||
lv_label_set_text_static(heartbeatValue, "");
|
|
||||||
lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
|
|
||||||
|
|
||||||
stepValue = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
|
||||||
lv_label_set_text_static(stepValue, "0");
|
|
||||||
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
|
|
||||||
|
|
||||||
stepIcon = lv_label_create(lv_scr_act(), nullptr);
|
|
||||||
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
|
|
||||||
lv_label_set_text_static(stepIcon, Symbols::shoe);
|
|
||||||
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
|
|
||||||
|
|
||||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
|
||||||
Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
WatchFaceFuzzy::~WatchFaceFuzzy() {
|
|
||||||
lv_task_del(taskRefresh);
|
|
||||||
lv_obj_clean(lv_scr_act());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatchFaceFuzzy::Refresh() {
|
|
||||||
statusIcons.Update();
|
|
||||||
|
|
||||||
notificationState = notificationManager.AreNewNotificationsAvailable();
|
|
||||||
if (notificationState.IsUpdated()) {
|
|
||||||
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(notificationState.Get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
|
|
||||||
|
|
||||||
if (currentDateTime.IsUpdated()) {
|
|
||||||
uint8_t hour = dateTimeController.Hours();
|
|
||||||
uint8_t minute = dateTimeController.Minutes();
|
|
||||||
|
|
||||||
printTimeWords(static_cast<int>(hour), static_cast<int>(minute));
|
|
||||||
|
|
||||||
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
|
||||||
if (currentDate.IsUpdated()) {
|
|
||||||
uint16_t year = dateTimeController.Year();
|
|
||||||
uint8_t day = dateTimeController.Day();
|
|
||||||
lv_label_set_text_fmt(label_date,
|
|
||||||
"%s %d %s %d",
|
|
||||||
dateTimeController.DayOfWeekShortToString(),
|
|
||||||
day,
|
|
||||||
dateTimeController.MonthShortToString(),
|
|
||||||
year);
|
|
||||||
lv_obj_realign(label_date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
heartbeat = heartRateController.HeartRate();
|
|
||||||
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
|
|
||||||
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
|
|
||||||
if (heartbeatRunning.Get()) {
|
|
||||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
|
|
||||||
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
|
|
||||||
} else {
|
|
||||||
lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x1B1B1B));
|
|
||||||
lv_label_set_text_static(heartbeatValue, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_realign(heartbeatIcon);
|
|
||||||
lv_obj_realign(heartbeatValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
stepCount = motionController.NbSteps();
|
|
||||||
if (stepCount.IsUpdated()) {
|
|
||||||
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
|
|
||||||
lv_obj_realign(stepValue);
|
|
||||||
lv_obj_realign(stepIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char const* WatchFaceFuzzy::mods[] = {"", "five", "ten", "quarter", "twenty", "twenty five", "half"};
|
|
||||||
char const* WatchFaceFuzzy::nums[] = {"twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"};
|
|
||||||
|
|
||||||
void WatchFaceFuzzy::printTimeWords(int h, int m) {
|
|
||||||
const char* mod;
|
|
||||||
|
|
||||||
if (m <= 30) {
|
|
||||||
mod = mods[m / 5];
|
|
||||||
} else {
|
|
||||||
mod = mods[(60-m) / 5];
|
|
||||||
}
|
|
||||||
h = (h % 12);
|
|
||||||
|
|
||||||
if (m >= 56) {
|
|
||||||
sprintf(timeStr, "#ffffff nearly %s#\n#808080 o' clock#", nums[(h+1) % 12]);
|
|
||||||
}
|
|
||||||
else if (m == 0 || m <= 4) {
|
|
||||||
sprintf(timeStr, "#ffffff %s#\n#808080 o' clock#", nums[h]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (m <= 32) {
|
|
||||||
sprintf(timeStr, "#ffffff %s#\n#808080 past# #FFFFFF %s#", mod, nums[h]);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (m > 32) {
|
|
||||||
sprintf(timeStr, "#ffffff %s#\n#808080 to# #FFFFFF %s#", mod, nums[(h+1) % 12]);
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_label_set_text(label_time, timeStr);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <lvgl/src/lv_core/lv_obj.h>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include "displayapp/screens/Screen.h"
|
|
||||||
#include "components/datetime/DateTimeController.h"
|
|
||||||
#include "components/ble/BleController.h"
|
|
||||||
#include "displayapp/widgets/StatusIcons.h"
|
|
||||||
#include "utility/DirtyValue.h"
|
|
||||||
|
|
||||||
extern lv_font_t vulf_mono_italic;
|
|
||||||
|
|
||||||
namespace Pinetime {
|
|
||||||
namespace Controllers {
|
|
||||||
class Battery;
|
|
||||||
class Ble;
|
|
||||||
class NotificationManager;
|
|
||||||
class HeartRateController;
|
|
||||||
class MotionController;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Applications {
|
|
||||||
namespace Screens {
|
|
||||||
|
|
||||||
class WatchFaceFuzzy : public Screen {
|
|
||||||
public:
|
|
||||||
WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
|
|
||||||
const Controllers::Battery& batteryController,
|
|
||||||
const Controllers::Ble& bleController,
|
|
||||||
Controllers::NotificationManager& notificationManager,
|
|
||||||
Controllers::HeartRateController& heartRateController,
|
|
||||||
Controllers::MotionController& motionController);
|
|
||||||
~WatchFaceFuzzy() override;
|
|
||||||
|
|
||||||
void Refresh() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t displayedHour = -1;
|
|
||||||
uint8_t displayedMinute = -1;
|
|
||||||
|
|
||||||
static char const *nums[];
|
|
||||||
static char const *mods[];
|
|
||||||
|
|
||||||
char timeStr[64];
|
|
||||||
|
|
||||||
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
|
|
||||||
Utility::DirtyValue<bool> powerPresent {};
|
|
||||||
Utility::DirtyValue<bool> bleState {};
|
|
||||||
Utility::DirtyValue<bool> bleRadioEnabled {};
|
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
|
|
||||||
Utility::DirtyValue<uint32_t> stepCount {};
|
|
||||||
Utility::DirtyValue<uint8_t> heartbeat {};
|
|
||||||
Utility::DirtyValue<bool> heartbeatRunning {};
|
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
|
||||||
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
|
||||||
|
|
||||||
lv_obj_t* label_time;
|
|
||||||
lv_obj_t* label_date;
|
|
||||||
lv_obj_t* heartbeatIcon;
|
|
||||||
lv_obj_t* heartbeatValue;
|
|
||||||
lv_obj_t* stepIcon;
|
|
||||||
lv_obj_t* stepValue;
|
|
||||||
lv_obj_t* notificationIcon;
|
|
||||||
|
|
||||||
Controllers::DateTime& dateTimeController;
|
|
||||||
Controllers::NotificationManager& notificationManager;
|
|
||||||
Controllers::HeartRateController& heartRateController;
|
|
||||||
Controllers::MotionController& motionController;
|
|
||||||
|
|
||||||
lv_task_t* taskRefresh;
|
|
||||||
Widgets::StatusIcons statusIcons;
|
|
||||||
|
|
||||||
void printTimeWords(int h, int m);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,9 +4,11 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <displayapp/Controllers.h>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
#include "displayapp/apps/Apps.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
@ -98,5 +100,25 @@ namespace Pinetime {
|
||||||
lv_font_t* font_bebas = 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,14 @@
|
||||||
#include "displayapp/screens/BleIcon.h"
|
#include "displayapp/screens/BleIcon.h"
|
||||||
#include "displayapp/screens/NotificationIcon.h"
|
#include "displayapp/screens/NotificationIcon.h"
|
||||||
#include "displayapp/screens/Symbols.h"
|
#include "displayapp/screens/Symbols.h"
|
||||||
|
#include "displayapp/screens/WeatherSymbols.h"
|
||||||
#include "components/battery/BatteryController.h"
|
#include "components/battery/BatteryController.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "components/ble/NotificationManager.h"
|
#include "components/ble/NotificationManager.h"
|
||||||
#include "components/motion/MotionController.h"
|
#include "components/motion/MotionController.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
#include "displayapp/DisplayApp.h"
|
#include "displayapp/DisplayApp.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::MotionController& motionController,
|
Controllers::MotionController& motionController,
|
||||||
Controllers::WeatherService& weatherService)
|
Controllers::SimpleWeatherService& weatherService)
|
||||||
: currentDateTime {{}},
|
: currentDateTime {{}},
|
||||||
batteryIcon(false),
|
batteryIcon(false),
|
||||||
dateTimeController {dateTimeController},
|
dateTimeController {dateTimeController},
|
||||||
|
@ -115,7 +116,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
||||||
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
|
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_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_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_align(weatherIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 35);
|
||||||
lv_obj_set_auto_realign(weatherIcon, true);
|
lv_obj_set_auto_realign(weatherIcon, true);
|
||||||
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
||||||
|
@ -126,6 +127,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
||||||
|
|
||||||
temperature = lv_label_create(lv_scr_act(), nullptr);
|
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_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);
|
lv_obj_align(temperature, sidebar, LV_ALIGN_IN_TOP_MID, 0, 65);
|
||||||
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
||||||
lv_obj_set_hidden(temperature, false);
|
lv_obj_set_hidden(temperature, false);
|
||||||
|
@ -537,29 +539,23 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 &&
|
currentWeather = weatherService.Current();
|
||||||
weatherService.GetCurrentPrecipitation()->timestamp != 0) {
|
|
||||||
nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
|
if (currentWeather.IsUpdated()) {
|
||||||
clouds = (weatherService.GetCurrentClouds()->amount);
|
auto optCurrentWeather = currentWeather.Get();
|
||||||
precip = (weatherService.GetCurrentPrecipitation()->amount);
|
if (optCurrentWeather) {
|
||||||
if (nowTemp.IsUpdated()) {
|
int16_t temp = optCurrentWeather->temperature;
|
||||||
lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get());
|
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||||
if ((clouds <= 30) && (precip == 0)) {
|
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||||
lv_label_set_text(weatherIcon, Symbols::sun);
|
}
|
||||||
} else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) {
|
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||||
lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
|
lv_label_set_text_fmt(temperature, "%d°", temp);
|
||||||
} else if ((clouds > 90) && (precip == 0)) {
|
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||||
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_obj_realign(temperature);
|
lv_obj_realign(temperature);
|
||||||
lv_obj_realign(weatherIcon);
|
lv_obj_realign(weatherIcon);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lv_label_set_text_static(temperature, "--");
|
lv_label_set_text(temperature, "--");
|
||||||
lv_label_set_text(weatherIcon, Symbols::ban);
|
lv_label_set_text(weatherIcon, Symbols::ban);
|
||||||
lv_obj_realign(temperature);
|
lv_obj_realign(temperature);
|
||||||
lv_obj_realign(weatherIcon);
|
lv_obj_realign(weatherIcon);
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <displayapp/Controllers.h>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "displayapp/screens/BatteryIcon.h"
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
#include "displayapp/Colors.h"
|
#include "displayapp/Colors.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
#include "components/ble/weather/WeatherService.h"
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ namespace Pinetime {
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::MotionController& motionController,
|
Controllers::MotionController& motionController,
|
||||||
Controllers::WeatherService& weather);
|
Controllers::SimpleWeatherService& weather);
|
||||||
~WatchFacePineTimeStyle() override;
|
~WatchFacePineTimeStyle() override;
|
||||||
|
|
||||||
bool OnTouchEvent(TouchEvents event) override;
|
bool OnTouchEvent(TouchEvents event) override;
|
||||||
|
@ -60,9 +61,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
||||||
Utility::DirtyValue<uint32_t> stepCount {};
|
Utility::DirtyValue<uint32_t> stepCount {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
Utility::DirtyValue<int16_t> nowTemp {};
|
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
|
||||||
int16_t clouds = 0;
|
|
||||||
int16_t precip = 0;
|
|
||||||
|
|
||||||
static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
|
static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
|
||||||
static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
|
static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
|
||||||
|
@ -113,7 +112,7 @@ namespace Pinetime {
|
||||||
Controllers::NotificationManager& notificationManager;
|
Controllers::NotificationManager& notificationManager;
|
||||||
Controllers::Settings& settingsController;
|
Controllers::Settings& settingsController;
|
||||||
Controllers::MotionController& motionController;
|
Controllers::MotionController& motionController;
|
||||||
Controllers::WeatherService& weatherService;
|
Controllers::SimpleWeatherService& weatherService;
|
||||||
|
|
||||||
void SetBatteryIcon();
|
void SetBatteryIcon();
|
||||||
void CloseMenu();
|
void CloseMenu();
|
||||||
|
@ -121,5 +120,25 @@ namespace Pinetime {
|
||||||
lv_task_t* taskRefresh;
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
151
src/displayapp/screens/WatchFaceTerminal.cpp
Normal file
151
src/displayapp/screens/WatchFaceTerminal.cpp
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
|
#include "displayapp/screens/WatchFaceTerminal.h"
|
||||||
|
#include "displayapp/screens/BatteryIcon.h"
|
||||||
|
#include "displayapp/screens/NotificationIcon.h"
|
||||||
|
#include "displayapp/screens/Symbols.h"
|
||||||
|
#include "components/battery/BatteryController.h"
|
||||||
|
#include "components/ble/BleController.h"
|
||||||
|
#include "components/ble/NotificationManager.h"
|
||||||
|
#include "components/heartrate/HeartRateController.h"
|
||||||
|
#include "components/motion/MotionController.h"
|
||||||
|
#include "components/settings/Settings.h"
|
||||||
|
|
||||||
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
|
||||||
|
const Controllers::Battery& batteryController,
|
||||||
|
const Controllers::Ble& bleController,
|
||||||
|
Controllers::NotificationManager& notificationManager,
|
||||||
|
Controllers::Settings& settingsController,
|
||||||
|
Controllers::HeartRateController& heartRateController,
|
||||||
|
Controllers::MotionController& motionController)
|
||||||
|
: currentDateTime {{}},
|
||||||
|
dateTimeController {dateTimeController},
|
||||||
|
batteryController {batteryController},
|
||||||
|
bleController {bleController},
|
||||||
|
notificationManager {notificationManager},
|
||||||
|
settingsController {settingsController},
|
||||||
|
heartRateController {heartRateController},
|
||||||
|
motionController {motionController} {
|
||||||
|
batteryValue = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(batteryValue, true);
|
||||||
|
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
|
||||||
|
|
||||||
|
connectState = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(connectState, true);
|
||||||
|
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
|
||||||
|
|
||||||
|
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -100);
|
||||||
|
|
||||||
|
label_date = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(label_date, true);
|
||||||
|
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
|
||||||
|
|
||||||
|
label_prompt_1 = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_obj_align(label_prompt_1, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -80);
|
||||||
|
lv_label_set_text_static(label_prompt_1, "user@watch:~ $ now");
|
||||||
|
|
||||||
|
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
|
||||||
|
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
|
||||||
|
|
||||||
|
label_time = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(label_time, true);
|
||||||
|
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -60);
|
||||||
|
|
||||||
|
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(heartbeatValue, true);
|
||||||
|
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);
|
||||||
|
|
||||||
|
stepValue = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_recolor(stepValue, true);
|
||||||
|
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
|
||||||
|
|
||||||
|
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchFaceTerminal::~WatchFaceTerminal() {
|
||||||
|
lv_task_del(taskRefresh);
|
||||||
|
lv_obj_clean(lv_scr_act());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchFaceTerminal::Refresh() {
|
||||||
|
powerPresent = batteryController.IsPowerPresent();
|
||||||
|
batteryPercentRemaining = batteryController.PercentRemaining();
|
||||||
|
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
|
||||||
|
lv_label_set_text_fmt(batteryValue, "[BATT]#387b54 %d%%", batteryPercentRemaining.Get());
|
||||||
|
if (batteryController.IsPowerPresent()) {
|
||||||
|
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bleState = bleController.IsConnected();
|
||||||
|
bleRadioEnabled = bleController.IsRadioEnabled();
|
||||||
|
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
|
||||||
|
if (!bleRadioEnabled.Get()) {
|
||||||
|
lv_label_set_text_static(connectState, "[STAT]#0082fc Disabled#");
|
||||||
|
} else {
|
||||||
|
if (bleState.Get()) {
|
||||||
|
lv_label_set_text_static(connectState, "[STAT]#0082fc Connected#");
|
||||||
|
} else {
|
||||||
|
lv_label_set_text_static(connectState, "[STAT]#0082fc Disconnected#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationState = notificationManager.AreNewNotificationsAvailable();
|
||||||
|
if (notificationState.IsUpdated()) {
|
||||||
|
if (notificationState.Get()) {
|
||||||
|
lv_label_set_text_static(notificationIcon, "You have mail.");
|
||||||
|
} else {
|
||||||
|
lv_label_set_text_static(notificationIcon, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
|
||||||
|
if (currentDateTime.IsUpdated()) {
|
||||||
|
uint8_t hour = dateTimeController.Hours();
|
||||||
|
uint8_t minute = dateTimeController.Minutes();
|
||||||
|
uint8_t second = dateTimeController.Seconds();
|
||||||
|
|
||||||
|
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
||||||
|
char ampmChar[3] = "AM";
|
||||||
|
if (hour == 0) {
|
||||||
|
hour = 12;
|
||||||
|
} else if (hour == 12) {
|
||||||
|
ampmChar[0] = 'P';
|
||||||
|
} else if (hour > 12) {
|
||||||
|
hour = hour - 12;
|
||||||
|
ampmChar[0] = 'P';
|
||||||
|
}
|
||||||
|
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
|
||||||
|
} else {
|
||||||
|
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
||||||
|
if (currentDate.IsUpdated()) {
|
||||||
|
uint16_t year = dateTimeController.Year();
|
||||||
|
Controllers::DateTime::Months month = dateTimeController.Month();
|
||||||
|
uint8_t day = dateTimeController.Day();
|
||||||
|
lv_label_set_text_fmt(label_date, "[DATE]#007fff %04d-%02d-%02d#", short(year), char(month), char(day));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeat = heartRateController.HeartRate();
|
||||||
|
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
|
||||||
|
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
|
||||||
|
if (heartbeatRunning.Get()) {
|
||||||
|
lv_label_set_text_fmt(heartbeatValue, "[L_HR]#ee3311 %d bpm#", heartbeat.Get());
|
||||||
|
} else {
|
||||||
|
lv_label_set_text_static(heartbeatValue, "[L_HR]#ee3311 ---#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stepCount = motionController.NbSteps();
|
||||||
|
if (stepCount.IsUpdated()) {
|
||||||
|
lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get());
|
||||||
|
}
|
||||||
|
}
|
93
src/displayapp/screens/WatchFaceTerminal.h
Normal file
93
src/displayapp/screens/WatchFaceTerminal.h
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl/src/lv_core/lv_obj.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <displayapp/Controllers.h>
|
||||||
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "components/datetime/DateTimeController.h"
|
||||||
|
#include "utility/DirtyValue.h"
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Controllers {
|
||||||
|
class Settings;
|
||||||
|
class Battery;
|
||||||
|
class Ble;
|
||||||
|
class NotificationManager;
|
||||||
|
class HeartRateController;
|
||||||
|
class MotionController;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Applications {
|
||||||
|
namespace Screens {
|
||||||
|
|
||||||
|
class WatchFaceTerminal : public Screen {
|
||||||
|
public:
|
||||||
|
WatchFaceTerminal(Controllers::DateTime& dateTimeController,
|
||||||
|
const Controllers::Battery& batteryController,
|
||||||
|
const Controllers::Ble& bleController,
|
||||||
|
Controllers::NotificationManager& notificationManager,
|
||||||
|
Controllers::Settings& settingsController,
|
||||||
|
Controllers::HeartRateController& heartRateController,
|
||||||
|
Controllers::MotionController& motionController);
|
||||||
|
~WatchFaceTerminal() override;
|
||||||
|
|
||||||
|
void Refresh() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Utility::DirtyValue<int> batteryPercentRemaining {};
|
||||||
|
Utility::DirtyValue<bool> powerPresent {};
|
||||||
|
Utility::DirtyValue<bool> bleState {};
|
||||||
|
Utility::DirtyValue<bool> bleRadioEnabled {};
|
||||||
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>> currentDateTime {};
|
||||||
|
Utility::DirtyValue<uint32_t> stepCount {};
|
||||||
|
Utility::DirtyValue<uint8_t> heartbeat {};
|
||||||
|
Utility::DirtyValue<bool> heartbeatRunning {};
|
||||||
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
|
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
||||||
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
||||||
|
|
||||||
|
lv_obj_t* label_time;
|
||||||
|
lv_obj_t* label_date;
|
||||||
|
lv_obj_t* label_prompt_1;
|
||||||
|
lv_obj_t* label_prompt_2;
|
||||||
|
lv_obj_t* batteryValue;
|
||||||
|
lv_obj_t* heartbeatValue;
|
||||||
|
lv_obj_t* stepValue;
|
||||||
|
lv_obj_t* notificationIcon;
|
||||||
|
lv_obj_t* connectState;
|
||||||
|
|
||||||
|
Controllers::DateTime& dateTimeController;
|
||||||
|
const Controllers::Battery& batteryController;
|
||||||
|
const Controllers::Ble& bleController;
|
||||||
|
Controllers::NotificationManager& notificationManager;
|
||||||
|
Controllers::Settings& settingsController;
|
||||||
|
Controllers::HeartRateController& heartRateController;
|
||||||
|
Controllers::MotionController& motionController;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
46
src/displayapp/screens/WeatherSymbols.h
Normal file
46
src/displayapp/screens/WeatherSymbols.h
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,10 +43,10 @@ SettingDisplay::SettingDisplay(Pinetime::Applications::DisplayApp* app, Pinetime
|
||||||
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
|
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
|
||||||
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
|
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++) {
|
for (unsigned int i = 0; i < options.size(); i++) {
|
||||||
cbOption[i] = lv_checkbox_create(container1, nullptr);
|
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);
|
lv_checkbox_set_text(cbOption[i], buffer);
|
||||||
cbOption[i]->user_data = this;
|
cbOption[i]->user_data = this;
|
||||||
lv_obj_set_event_cb(cbOption[i], event_handler);
|
lv_obj_set_event_cb(cbOption[i], event_handler);
|
||||||
|
|
|
@ -13,9 +13,10 @@ namespace {
|
||||||
const char* name;
|
const char* name;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array<Option, 2> options = {{
|
constexpr std::array<Option, 3> options = {{
|
||||||
{Pinetime::Controllers::Settings::ClockType::H12, "12-hour"},
|
{Pinetime::Controllers::Settings::ClockType::H12, "12-hour"},
|
||||||
{Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
|
{Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
|
||||||
|
{Pinetime::Controllers::Settings::ClockType::Fuzzy, "Fuzzy"},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
|
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "displayapp/DisplayApp.h"
|
#include "displayapp/DisplayApp.h"
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
#include "displayapp/WatchFaces.h"
|
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
||||||
|
@ -21,9 +20,11 @@ auto SettingWatchFace::CreateScreenList() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
|
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
|
||||||
|
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||||
Pinetime::Controllers::Settings& settingsController,
|
Pinetime::Controllers::Settings& settingsController,
|
||||||
Pinetime::Controllers::FS& filesystem)
|
Pinetime::Controllers::FS& filesystem)
|
||||||
: app {app},
|
: app {app},
|
||||||
|
watchfaceItems {std::move(watchfaceItems)},
|
||||||
settingsController {settingsController},
|
settingsController {settingsController},
|
||||||
filesystem {filesystem},
|
filesystem {filesystem},
|
||||||
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||||
|
@ -40,7 +41,11 @@ bool SettingWatchFace::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||||
std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) const {
|
std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) const {
|
||||||
std::array<Screens::CheckboxList::Item, settingsPerScreen> watchfacesOnThisScreen;
|
std::array<Screens::CheckboxList::Item, settingsPerScreen> watchfacesOnThisScreen;
|
||||||
for (int i = 0; i < settingsPerScreen; i++) {
|
for (int i = 0; i < settingsPerScreen; i++) {
|
||||||
watchfacesOnThisScreen[i] = watchfaces[screenNum * settingsPerScreen + i];
|
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
|
||||||
|
watchfacesOnThisScreen[i] = {"", false};
|
||||||
|
} else {
|
||||||
|
watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_unique<Screens::CheckboxList>(
|
return std::make_unique<Screens::CheckboxList>(
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include "displayapp/screens/CheckboxList.h"
|
#include "displayapp/screens/CheckboxList.h"
|
||||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||||
#include "displayapp/screens/WatchFaceFuzzy.h"
|
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
|
||||||
|
@ -20,7 +19,10 @@ namespace Pinetime {
|
||||||
|
|
||||||
class SettingWatchFace : public Screen {
|
class SettingWatchFace : public Screen {
|
||||||
public:
|
public:
|
||||||
SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem);
|
SettingWatchFace(DisplayApp* app,
|
||||||
|
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||||
|
Pinetime::Controllers::Settings& settingsController,
|
||||||
|
Pinetime::Controllers::FS& filesystem);
|
||||||
~SettingWatchFace() override;
|
~SettingWatchFace() override;
|
||||||
|
|
||||||
bool OnTouchEvent(TouchEvents event) override;
|
bool OnTouchEvent(TouchEvents event) override;
|
||||||
|
@ -30,25 +32,16 @@ namespace Pinetime {
|
||||||
auto CreateScreenList() const;
|
auto CreateScreenList() const;
|
||||||
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
|
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
|
||||||
|
|
||||||
|
static constexpr int settingsPerScreen = 4;
|
||||||
|
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> watchfaceItems;
|
||||||
|
static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
|
||||||
|
|
||||||
Controllers::Settings& settingsController;
|
Controllers::Settings& settingsController;
|
||||||
Pinetime::Controllers::FS& filesystem;
|
Pinetime::Controllers::FS& filesystem;
|
||||||
|
|
||||||
static constexpr const char* title = "Watch face";
|
static constexpr const char* title = "Watch face";
|
||||||
static constexpr const char* symbol = Symbols::home;
|
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},
|
|
||||||
{"Analog face", true},
|
|
||||||
{"PineTimeStyle", true},
|
|
||||||
{"Infineat face", Applications::Screens::WatchFaceInfineat::IsAvailable(filesystem)},
|
|
||||||
{"Casio G7710", Applications::Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem)},
|
|
||||||
{"Fuzzy Clock", true},
|
|
||||||
{"", false}}};
|
|
||||||
ScreenList<nScreens> screens;
|
ScreenList<nScreens> screens;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
63
src/displayapp/screens/settings/SettingWeatherFormat.cpp
Normal file
63
src/displayapp/screens/settings/SettingWeatherFormat.cpp
Normal file
|
@ -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());
|
||||||
|
}
|
26
src/displayapp/screens/settings/SettingWeatherFormat.h
Normal file
26
src/displayapp/screens/settings/SettingWeatherFormat.h
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
#include "displayapp/screens/settings/Settings.h"
|
#include "displayapp/screens/settings/Settings.h"
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "displayapp/Apps.h"
|
#include "displayapp/apps/Apps.h"
|
||||||
#include "displayapp/DisplayApp.h"
|
#include "displayapp/DisplayApp.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace Pinetime {
|
||||||
static constexpr int entriesPerScreen = 4;
|
static constexpr int entriesPerScreen = 4;
|
||||||
|
|
||||||
// Increment this when more space is needed
|
// 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 {{
|
static constexpr std::array<List::Applications, entriesPerScreen * nScreens> entries {{
|
||||||
{Symbols::sun, "Display", Apps::SettingDisplay},
|
{Symbols::sun, "Display", Apps::SettingDisplay},
|
||||||
|
@ -39,12 +39,14 @@ namespace Pinetime {
|
||||||
|
|
||||||
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
||||||
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
||||||
|
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
|
||||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
||||||
{Symbols::clock, "Chimes", Apps::SettingChimes},
|
|
||||||
|
|
||||||
|
{Symbols::clock, "Chimes", Apps::SettingChimes},
|
||||||
{Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold},
|
{Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold},
|
||||||
{Symbols::check, "Firmware", Apps::FirmwareValidation},
|
{Symbols::check, "Firmware", Apps::FirmwareValidation},
|
||||||
{Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
|
{Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
|
||||||
|
|
||||||
{Symbols::list, "About", Apps::SysInfo},
|
{Symbols::list, "About", Apps::SysInfo},
|
||||||
|
|
||||||
// {Symbols::none, "None", Apps::None},
|
// {Symbols::none, "None", Apps::None},
|
||||||
|
|
|
@ -131,8 +131,8 @@ void SpiMaster::OnEndEvent() {
|
||||||
if (s > 0) {
|
if (s > 0) {
|
||||||
auto currentSize = std::min((size_t) 255, s);
|
auto currentSize = std::min((size_t) 255, s);
|
||||||
PrepareTx(currentBufferAddr, currentSize);
|
PrepareTx(currentBufferAddr, currentSize);
|
||||||
currentBufferAddr += currentSize;
|
currentBufferAddr = currentBufferAddr + currentSize;
|
||||||
currentBufferSize -= currentSize;
|
currentBufferSize = currentBufferSize - currentSize;
|
||||||
|
|
||||||
spiBaseAddress->TASKS_START = 1;
|
spiBaseAddress->TASKS_START = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -153,7 +153,7 @@ void SpiMaster::OnEndEvent() {
|
||||||
void SpiMaster::OnStartedEvent() {
|
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.PTR = bufferAddress;
|
||||||
spiBaseAddress->TXD.MAXCNT = size;
|
spiBaseAddress->TXD.MAXCNT = size;
|
||||||
spiBaseAddress->TXD.LIST = 0;
|
spiBaseAddress->TXD.LIST = 0;
|
||||||
|
@ -163,7 +163,7 @@ void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile
|
||||||
spiBaseAddress->EVENTS_END = 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.PTR = 0;
|
||||||
spiBaseAddress->TXD.MAXCNT = 0;
|
spiBaseAddress->TXD.MAXCNT = 0;
|
||||||
spiBaseAddress->TXD.LIST = 0;
|
spiBaseAddress->TXD.LIST = 0;
|
||||||
|
@ -195,8 +195,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
|
||||||
|
|
||||||
auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize);
|
auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize);
|
||||||
PrepareTx(currentBufferAddr, currentSize);
|
PrepareTx(currentBufferAddr, currentSize);
|
||||||
currentBufferSize -= currentSize;
|
currentBufferSize = currentBufferSize - currentSize;
|
||||||
currentBufferAddr += currentSize;
|
currentBufferAddr = currentBufferAddr + currentSize;
|
||||||
spiBaseAddress->TASKS_START = 1;
|
spiBaseAddress->TASKS_START = 1;
|
||||||
|
|
||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace {
|
||||||
// RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent
|
// RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent
|
||||||
// one of the eight reload registers available.
|
// one of the eight reload registers available.
|
||||||
// In this case, we enable only the first one.
|
// 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
|
/// Returns the reset reason provided by the POWER subsystem
|
||||||
|
|
|
@ -3,8 +3,8 @@ find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
||||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
||||||
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
||||||
|
|
||||||
find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED
|
find_program(LV_IMG_CONV "lv_img_conv.py" NO_CACHE REQUIRED
|
||||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
HINTS "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||||
|
|
|
@ -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):
|
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]
|
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
|
return args
|
||||||
|
|
||||||
|
|
193
src/resources/lv_img_conv.py
Executable file
193
src/resources/lv_img_conv.py
Executable file
|
@ -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())
|
|
@ -136,6 +136,9 @@ void SystemTask::Work() {
|
||||||
settingsController.Init();
|
settingsController.Init();
|
||||||
|
|
||||||
displayApp.Register(this);
|
displayApp.Register(this);
|
||||||
|
displayApp.Register(&nimbleController.weather());
|
||||||
|
displayApp.Register(&nimbleController.music());
|
||||||
|
displayApp.Register(&nimbleController.navigation());
|
||||||
displayApp.Start(bootError);
|
displayApp.Start(bootError);
|
||||||
|
|
||||||
heartRateSensor.Init();
|
heartRateSensor.Init();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user