Compare commits
14 Commits
wb/fuzzy-n
...
wb/fuzzy-c
Author | SHA1 | Date | |
---|---|---|---|
|
628ae886c5 | ||
|
d7955498ff | ||
|
9729047f1f | ||
|
104e750a75 | ||
|
e3967e8a90 | ||
|
49ba4c29ae | ||
|
d31c8c99c8 | ||
|
92f8c37291 | ||
|
1eb624c452 | ||
|
deb8f9368f | ||
|
3ade3153e5 | ||
|
f5e7dba873 | ||
|
fb213c323b | ||
|
9241d4783c |
|
@ -11,7 +11,6 @@ RUN apt-get update -qq \
|
|||
make \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-pil \
|
||||
tar \
|
||||
unzip \
|
||||
wget \
|
||||
|
|
|
@ -64,6 +64,7 @@ CmakeGenerate() {
|
|||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
|
||||
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
|
||||
-DBUILD_RESOURCES=0
|
||||
"$SOURCES_DIR"
|
||||
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
|
||||
on:
|
||||
push:
|
||||
branches: [ main, wb/fuzzy, wb/fuzzy-norm ]
|
||||
branches: [ main, wb/fuzzy-clock ]
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- '**.md'
|
||||
|
@ -31,10 +31,6 @@ jobs:
|
|||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install resource build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -y install --no-install-recommends python3-pil
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: /opt/build.sh all
|
||||
|
|
2
.vscode/c_cpp_properties.json
vendored
2
.vscode/c_cpp_properties.json
vendored
|
@ -10,7 +10,7 @@
|
|||
"defines": [],
|
||||
"compilerPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gcc",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++20",
|
||||
"cppStandard": "c++14",
|
||||
"intelliSenseMode": "linux-gcc-arm",
|
||||
"configurationProvider": "ms-vscode.cpp-tools",
|
||||
"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)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
# set(CMAKE_GENERATOR "Unix Makefiles")
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
@ -13,6 +13,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
|||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(NRF_TARGET "nrf52")
|
||||
|
||||
if (NOT ARM_NONE_EABI_TOOLCHAIN_PATH)
|
||||
|
@ -32,7 +33,7 @@ if(BUILD_RESOURCES)
|
|||
endif()
|
||||
|
||||
set(TARGET_DEVICE "PINETIME" CACHE STRING "Target device")
|
||||
set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY_TFK5 MOY_TIN5 MOY_TON5 MOY_UNK)
|
||||
set_property(CACHE TARGET_DEVICE PROPERTY STRINGS PINETIME MOY-TFK5 MOY-TIN5 MOY-TON5 MOY-UNK)
|
||||
|
||||
set(PROJECT_GIT_COMMIT_HASH "")
|
||||
|
||||
|
@ -69,4 +70,5 @@ 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}/docker/post_build.sh.in ${CMAKE_CURRENT_BINARY_DIR}/post_build.sh)
|
||||
|
||||
|
||||
add_subdirectory(src)
|
||||
|
|
20
README.md
20
README.md
|
@ -17,11 +17,11 @@ Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/p
|
|||
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
||||
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
||||
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
|
||||
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
|
||||
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS) **[Looking for a new maintainer]**
|
||||
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
|
||||
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
||||
|
||||
***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
***Note** : We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -72,17 +72,17 @@ This project is released under the GNU General Public License version 3 or, at y
|
|||
|
||||
It integrates the following projects:
|
||||
|
||||
- RTOS: **[FreeRTOS](https://freertos.org)** under the MIT license
|
||||
- UI: **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
|
||||
- BLE stack: **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
|
||||
- Font: **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
|
||||
- RTOS : **[FreeRTOS](https://freertos.org)** under the MIT license
|
||||
- UI : **[LittleVGL/LVGL](https://lvgl.io/)** under the MIT license
|
||||
- BLE stack : **[NimBLE](https://github.com/apache/mynewt-nimble)** under the Apache 2.0 license
|
||||
- Font : **[Jetbrains Mono](https://www.jetbrains.com/fr-fr/lp/mono/)** under the Apache 2.0 license
|
||||
|
||||
## Credits
|
||||
|
||||
I’m not working alone on this project. First, many people create 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.
|
||||
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.
|
||||
|
||||
Here are some people I would like to highlight:
|
||||
|
||||
- [Atc1441](https://github.com/atc1441/): He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
|
||||
- [Koen](https://github.com/bosmoment): He’s working on a firmware based on RiotOS. He integrated similar libs as me: NimBLE, LittleVGL,… His help was invaluable too!
|
||||
- [Lup Yuen Lee](https://github.com/lupyuen): He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!
|
||||
- [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver.
|
||||
- [Koen](https://github.com/bosmoment) : He’s working on a firmware based on RiotOS. He integrated similar libs as me : NimBLE, LittleVGL,… His help was invaluable too!
|
||||
- [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere: he works on a Rust firmware, builds a MCUBoot based bootloader for the Pinetime, designs a Flutter based companion app for smartphones and writes a lot of articles about the Pinetime!
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
# 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,10 +92,7 @@ The following custom services are implemented in InfiniTime:
|
|||
|
||||
- Since InfiniTime 1.8:
|
||||
|
||||
- ~~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`
|
||||
- [Weather Service](/src/components/ble/weather/WeatherService.h): `00040000-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`|
|
||||
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
|
||||
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
|
||||
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [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)
|
||||
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1`
|
||||
**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
|
||||
|
||||
#### (\*) 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.
|
||||
|
@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
|
|||
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
|
||||
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware
|
||||
|
||||
The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**
|
||||
The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader**
|
||||
|
|
131
doc/code/Apps.md
131
doc/code/Apps.md
|
@ -9,114 +9,59 @@ This page will teach you:
|
|||
|
||||
The user interface of InfiniTime is made up of **screens**.
|
||||
Screens that are opened from the app launcher are considered **apps**.
|
||||
Every app in InfiniTime is its own class.
|
||||
Every app in InfiniTime is it's own class.
|
||||
An instance of the class is created when the app is launched, and destroyed when the user exits the app.
|
||||
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 can be refreshed periodically and reacts to external events (touch or button).
|
||||
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected.
|
||||
|
||||
## Interface
|
||||
|
||||
Every app class is declared inside the namespace `Pinetime::Applications::Screens`
|
||||
and inherits
|
||||
from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
|
||||
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`.
|
||||
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen.
|
||||
Other parameters should be references to controllers that the app needs.
|
||||
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
|
||||
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
|
||||
If an app only needs to display some text and do something upon a touch screen button press,
|
||||
it does not need to override any of these functions, as LVGL can also handle touch events for you.
|
||||
If you have any doubts, you can always look at how the other apps function for reference.
|
||||
|
||||
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.
|
||||
### Continuous updating
|
||||
|
||||
The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping).
|
||||
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class
|
||||
and calling `lv_task_create` inside the constructor.
|
||||
|
||||
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.
|
||||
An example call could look like this:
|
||||
|
||||
Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
|
||||
that will call the method `Refresh()` periodically.
|
||||
|
||||
## App types
|
||||
|
||||
There are basically 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);
|
||||
};
|
||||
};
|
||||
```cpp
|
||||
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
|
||||
```
|
||||
|
||||
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
|
||||
to list all available applications.
|
||||
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`.
|
||||
Remember to delete the task again using `lv_task_del`.
|
||||
The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function.
|
||||
|
||||
## Creating your own app
|
||||
|
||||
A minimal user app could look like this:
|
||||
A minimal app could look like this:
|
||||
|
||||
MyApp.h:
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class MyApp : public Screen {
|
||||
public:
|
||||
MyApp();
|
||||
MyApp(DisplayApp* app);
|
||||
~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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -125,10 +70,11 @@ MyApp.cpp:
|
|||
|
||||
```cpp
|
||||
#include "displayapp/screens/MyApp.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
MyApp::MyApp() {
|
||||
MyApp::MyApp(DisplayApp* app) : Screen(app) {
|
||||
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_text_static(title, "My test application");
|
||||
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||
|
@ -140,33 +86,20 @@ 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.
|
||||
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
|
||||
The first step is to include your MyApp.cpp (or any new cpp files for that matter)
|
||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
||||
The next step to making it launch-able is to give your app an id.
|
||||
The next step to making it launchable is to give your app an id.
|
||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
|
||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
|
||||
to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
|
||||
If your application is a **system** application, go to the function `DisplayApp::LoadScreen`
|
||||
and add another case to the switch statement.
|
||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement.
|
||||
The case will be the id you gave your app earlier.
|
||||
If your app needs any additional arguments, this is the place to pass them.
|
||||
|
||||
If 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" ...
|
||||
```
|
||||
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).
|
||||
|
||||
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||
and flash it to your PineTime. Yay!
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG NODE_MAJOR=20
|
||||
RUN apt-get update -qq \
|
||||
&& apt-get install -y ca-certificates curl gnupg \
|
||||
&& mkdir -p /etc/apt/keyrings \
|
||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
|
||||
&& apt-get update -qq \
|
||||
&& apt-get install -y \
|
||||
# x86_64 / generic packages
|
||||
bash \
|
||||
|
@ -15,14 +9,13 @@ RUN apt-get update -qq \
|
|||
cmake \
|
||||
git \
|
||||
make \
|
||||
nodejs \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-pil \
|
||||
python-is-python3 \
|
||||
tar \
|
||||
unzip \
|
||||
wget \
|
||||
curl \
|
||||
# aarch64 packages
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
|
@ -35,6 +28,8 @@ RUN apt-get update -qq \
|
|||
libpango-1.0-0 \
|
||||
ibpango1.0-dev \
|
||||
libpangocairo-1.0-0 \
|
||||
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
|
||||
|
||||
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
||||
|
@ -44,6 +39,10 @@ RUN pip3 install -Iv cryptography==3.3
|
|||
RUN pip3 install cbor
|
||||
RUN npm i lv_font_conv@1.5.2 -g
|
||||
|
||||
RUN npm i ts-node@10.9.1 -g
|
||||
RUN npm i @swc/core -g
|
||||
RUN npm i lv_img_conv@0.3.0 -g
|
||||
|
||||
# build.sh knows how to compile
|
||||
COPY build.sh /opt/
|
||||
|
||||
|
|
31
shell.nix
Normal file
31
shell.nix
Normal file
|
@ -0,0 +1,31 @@
|
|||
{ 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,6 +355,14 @@ set(LVGL_SRC
|
|||
libs/lvgl/src/lv_widgets/lv_win.c
|
||||
)
|
||||
|
||||
set(QCBOR_SRC
|
||||
libs/QCBOR/src/ieee754.c
|
||||
libs/QCBOR/src/qcbor_decode.c
|
||||
libs/QCBOR/src/qcbor_encode.c
|
||||
libs/QCBOR/src/qcbor_err_to_str.c
|
||||
libs/QCBOR/src/UsefulBuf.c
|
||||
)
|
||||
|
||||
list(APPEND IMAGE_FILES
|
||||
displayapp/icons/battery/batteryicon.c
|
||||
)
|
||||
|
@ -365,9 +373,8 @@ list(APPEND SOURCE_FILES
|
|||
logging/NrfLogger.cpp
|
||||
displayapp/DisplayApp.cpp
|
||||
displayapp/screens/Screen.cpp
|
||||
displayapp/screens/Clock.cpp
|
||||
displayapp/screens/Tile.cpp
|
||||
displayapp/screens/InfiniPaint.cpp
|
||||
displayapp/screens/Paddle.cpp
|
||||
displayapp/screens/StopWatch.cpp
|
||||
displayapp/screens/BatteryIcon.cpp
|
||||
displayapp/screens/BleIcon.cpp
|
||||
|
@ -376,6 +383,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/Label.cpp
|
||||
displayapp/screens/FirmwareUpdate.cpp
|
||||
displayapp/screens/Music.cpp
|
||||
displayapp/screens/Weather.cpp
|
||||
displayapp/screens/Navigation.cpp
|
||||
displayapp/screens/Metronome.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
|
@ -384,6 +392,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/Notifications.cpp
|
||||
displayapp/screens/Twos.cpp
|
||||
displayapp/screens/HeartRate.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
displayapp/screens/FlashLight.cpp
|
||||
displayapp/screens/List.cpp
|
||||
displayapp/screens/CheckboxList.cpp
|
||||
|
@ -405,7 +414,6 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/settings/Settings.cpp
|
||||
displayapp/screens/settings/SettingWatchFace.cpp
|
||||
displayapp/screens/settings/SettingTimeFormat.cpp
|
||||
displayapp/screens/settings/SettingWeatherFormat.cpp
|
||||
displayapp/screens/settings/SettingWakeUp.cpp
|
||||
displayapp/screens/settings/SettingDisplay.cpp
|
||||
displayapp/screens/settings/SettingSteps.cpp
|
||||
|
@ -420,9 +428,9 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/WatchFaceAnalog.cpp
|
||||
displayapp/screens/WatchFaceDigital.cpp
|
||||
displayapp/screens/WatchFaceInfineat.cpp
|
||||
displayapp/screens/WatchFaceTerminal.cpp
|
||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
||||
displayapp/screens/WatchFaceFuzzy.cpp
|
||||
|
||||
##
|
||||
|
||||
|
@ -451,7 +459,7 @@ list(APPEND SOURCE_FILES
|
|||
components/ble/CurrentTimeService.cpp
|
||||
components/ble/AlertNotificationService.cpp
|
||||
components/ble/MusicService.cpp
|
||||
components/ble/SimpleWeatherService.cpp
|
||||
components/ble/weather/WeatherService.cpp
|
||||
components/ble/NavigationService.cpp
|
||||
components/ble/BatteryInformationService.cpp
|
||||
components/ble/FSService.cpp
|
||||
|
@ -520,7 +528,7 @@ list(APPEND RECOVERY_SOURCE_FILES
|
|||
components/ble/CurrentTimeService.cpp
|
||||
components/ble/AlertNotificationService.cpp
|
||||
components/ble/MusicService.cpp
|
||||
components/ble/SimpleWeatherService.cpp
|
||||
components/ble/weather/WeatherService.cpp
|
||||
components/ble/BatteryInformationService.cpp
|
||||
components/ble/FSService.cpp
|
||||
components/ble/ImmediateAlertService.cpp
|
||||
|
@ -588,10 +596,9 @@ set(INCLUDE_FILES
|
|||
displayapp/Messages.h
|
||||
displayapp/TouchEvents.h
|
||||
displayapp/screens/Screen.h
|
||||
displayapp/screens/Clock.h
|
||||
displayapp/screens/Tile.h
|
||||
displayapp/screens/InfiniPaint.h
|
||||
displayapp/screens/StopWatch.h
|
||||
displayapp/screens/Paddle.h
|
||||
displayapp/screens/BatteryIcon.h
|
||||
displayapp/screens/BleIcon.h
|
||||
displayapp/screens/NotificationIcon.h
|
||||
|
@ -603,6 +610,7 @@ set(INCLUDE_FILES
|
|||
displayapp/screens/ApplicationList.h
|
||||
displayapp/screens/CheckboxList.h
|
||||
displayapp/Apps.h
|
||||
displayapp/WatchFaces.h
|
||||
displayapp/screens/Notifications.h
|
||||
displayapp/screens/HeartRate.h
|
||||
displayapp/screens/Metronome.h
|
||||
|
@ -647,7 +655,7 @@ set(INCLUDE_FILES
|
|||
components/ble/BleClient.h
|
||||
components/ble/HeartRateService.h
|
||||
components/ble/MotionService.h
|
||||
components/ble/SimpleWeatherService.h
|
||||
components/ble/weather/WeatherService.h
|
||||
components/settings/Settings.h
|
||||
components/timer/Timer.h
|
||||
components/alarm/AlarmController.h
|
||||
|
@ -794,18 +802,18 @@ add_definitions(-DTARGET_DEVICE_NAME="${TARGET_DEVICE}")
|
|||
if(TARGET_DEVICE STREQUAL "PINETIME")
|
||||
add_definitions(-DDRIVER_PINMAP_PINETIME)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TFK5") # P8a
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TFK5") # P8a
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TIN5") # P8a variant 2
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TIN5") # P8a variant 2
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=1) # XTAL
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_TON5") # P8b
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-TON5") # P8b
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_CAL_ENABLED=1)
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY_UNK") # P8b mirrored
|
||||
elseif(TARGET_DEVICE STREQUAL "MOY-UNK") # P8b mirrored
|
||||
add_definitions(-DDRIVER_PINMAP_P8)
|
||||
add_definitions(-DCLOCK_CONFIG_LF_SRC=0) # RC
|
||||
add_definitions(-DMYNEWT_VAL_BLE_LL_SCA=500)
|
||||
|
@ -846,8 +854,6 @@ target_compile_options(infinitime_fonts PUBLIC
|
|||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
)
|
||||
|
||||
add_subdirectory(displayapp/apps)
|
||||
|
||||
# NRF SDK
|
||||
add_library(nrf-sdk STATIC ${SDK_SOURCE_FILES})
|
||||
target_include_directories(nrf-sdk SYSTEM PUBLIC . ../)
|
||||
|
@ -885,6 +891,27 @@ target_compile_options(lvgl PRIVATE
|
|||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
)
|
||||
|
||||
# QCBOR
|
||||
add_library(QCBOR STATIC ${QCBOR_SRC})
|
||||
target_include_directories(QCBOR SYSTEM PUBLIC libs/QCBOR/inc)
|
||||
# This is required with the current configuration
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_FLOAT_HW_USE)
|
||||
# These are for space-saving
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_PREFERRED_FLOAT)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_EXP_AND_MANTISSA)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_STRINGS)
|
||||
#target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_INDEFINITE_LENGTH_ARRAYS)
|
||||
target_compile_definitions(QCBOR PUBLIC QCBOR_DISABLE_UNCOMMON_TAGS)
|
||||
target_compile_definitions(QCBOR PUBLIC USEFULBUF_CONFIG_LITTLE_ENDIAN)
|
||||
set_target_properties(QCBOR PROPERTIES LINKER_LANGUAGE C)
|
||||
target_compile_options(QCBOR PRIVATE
|
||||
${COMMON_FLAGS}
|
||||
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
|
||||
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
-O3
|
||||
)
|
||||
|
||||
# LITTLEFS_SRC
|
||||
add_library(littlefs STATIC ${LITTLEFS_SRC})
|
||||
target_include_directories(littlefs SYSTEM PUBLIC . ../)
|
||||
|
@ -903,7 +930,7 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime
|
|||
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
|
||||
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
${WARNING_FLAGS}
|
||||
|
@ -937,7 +964,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(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
|
@ -979,7 +1006,7 @@ endif()
|
|||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
|
||||
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
||||
|
@ -1011,7 +1038,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(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
|
||||
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
||||
|
@ -1051,7 +1078,7 @@ endif()
|
|||
set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
|
||||
set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||
add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
|
@ -1086,7 +1113,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(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||
add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk infinitime_fonts infinitime_apps)
|
||||
target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk QCBOR infinitime_fonts)
|
||||
set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
|
||||
target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
|
||||
${COMMON_FLAGS}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include "components/ble/NavigationService.h"
|
||||
#include "components/ble/ServiceDiscovery.h"
|
||||
#include "components/ble/MotionService.h"
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
#include "components/fs/FS.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
@ -67,7 +67,7 @@ namespace Pinetime {
|
|||
return anService;
|
||||
};
|
||||
|
||||
Pinetime::Controllers::SimpleWeatherService& weather() {
|
||||
Pinetime::Controllers::WeatherService& weather() {
|
||||
return weatherService;
|
||||
};
|
||||
|
||||
|
@ -99,7 +99,7 @@ namespace Pinetime {
|
|||
AlertNotificationClient alertNotificationClient;
|
||||
CurrentTimeService currentTimeService;
|
||||
MusicService musicService;
|
||||
SimpleWeatherService weatherService;
|
||||
WeatherService weatherService;
|
||||
NavigationService navService;
|
||||
BatteryInformationService batteryInformationService;
|
||||
ImmediateAlertService immediateAlertService;
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
/* 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;
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
/* 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;
|
||||
};
|
||||
}
|
||||
}
|
385
src/components/ble/weather/WeatherData.h
Normal file
385
src/components/ble/weather/WeatherData.h
Normal file
|
@ -0,0 +1,385 @@
|
|||
/* 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
605
src/components/ble/weather/WeatherService.cpp
Normal file
605
src/components/ble/weather/WeatherService.cpp
Normal file
|
@ -0,0 +1,605 @@
|
|||
/* 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);
|
||||
}
|
||||
}
|
||||
}
|
169
src/components/ble/weather/WeatherService.h
Normal file
169
src/components/ble/weather/WeatherService.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
/* 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;
|
||||
amPmStr = "PM";
|
||||
}
|
||||
snprintf(buff, sizeof(buff), "%i:%02i %s", hour12, minute, amPmStr);
|
||||
sprintf(buff, "%i:%02i %s", hour12, minute, amPmStr);
|
||||
} else {
|
||||
snprintf(buff, sizeof(buff), "%02i:%02i", hour, minute);
|
||||
sprintf(buff, "%02i:%02i", hour, minute);
|
||||
}
|
||||
return std::string(buff);
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ void Gfx::SetBackgroundColor(uint16_t color) {
|
|||
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
|
||||
if (!state.busy)
|
||||
return false;
|
||||
state.remainingIterations = state.remainingIterations - 1;
|
||||
state.remainingIterations--;
|
||||
if (state.remainingIterations == 0) {
|
||||
state.busy = false;
|
||||
NotifyEndOfTransfer(state.taskToNotify);
|
||||
|
@ -170,7 +170,7 @@ bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
|
|||
size = bytes_in_line * 8 * 2;
|
||||
}
|
||||
|
||||
state.currentIteration = state.currentIteration + 1;
|
||||
state.currentIteration++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
#include <bitset>
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
#include "components/fs/FS.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/WatchFaces.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class Settings {
|
||||
public:
|
||||
enum class ClockType : uint8_t { H24, H12, Fuzzy };
|
||||
enum class WeatherFormat : uint8_t { Metric, Imperial };
|
||||
enum class ClockType : uint8_t { H24, H12 };
|
||||
enum class Notification : uint8_t { On, Off, Sleep };
|
||||
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
||||
enum class WakeUpMode : uint8_t { SingleTap = 0, DoubleTap = 1, RaiseWrist = 2, Shake = 3, LowerWrist = 4 };
|
||||
|
@ -181,17 +180,6 @@ namespace Pinetime {
|
|||
return settings.clockType;
|
||||
};
|
||||
|
||||
void SetWeatherFormat(WeatherFormat weatherFormat) {
|
||||
if (weatherFormat != settings.weatherFormat) {
|
||||
settingsChanged = true;
|
||||
}
|
||||
settings.weatherFormat = weatherFormat;
|
||||
};
|
||||
|
||||
WeatherFormat GetWeatherFormat() const {
|
||||
return settings.weatherFormat;
|
||||
};
|
||||
|
||||
void SetNotificationStatus(Notification status) {
|
||||
if (status != settings.notificationStatus) {
|
||||
settingsChanged = true;
|
||||
|
@ -286,7 +274,7 @@ namespace Pinetime {
|
|||
private:
|
||||
Pinetime::Controllers::FS& fs;
|
||||
|
||||
static constexpr uint32_t settingsVersion = 0x0007;
|
||||
static constexpr uint32_t settingsVersion = 0x0006;
|
||||
|
||||
struct SettingsData {
|
||||
uint32_t version = settingsVersion;
|
||||
|
@ -294,7 +282,6 @@ namespace Pinetime {
|
|||
uint32_t screenTimeOut = 15000;
|
||||
|
||||
ClockType clockType = ClockType::H24;
|
||||
WeatherFormat weatherFormat = WeatherFormat::Metric;
|
||||
Notification notificationStatus = Notification::On;
|
||||
|
||||
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
|
||||
|
|
41
src/displayapp/Apps.h
Normal file
41
src/displayapp/Apps.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
#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,10 +11,9 @@
|
|||
#include "components/motion/MotionController.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "displayapp/screens/ApplicationList.h"
|
||||
#include "displayapp/screens/Clock.h"
|
||||
#include "displayapp/screens/FirmwareUpdate.h"
|
||||
#include "displayapp/screens/FirmwareValidation.h"
|
||||
#include "displayapp/screens/InfiniPaint.h"
|
||||
#include "displayapp/screens/Paddle.h"
|
||||
#include "displayapp/screens/StopWatch.h"
|
||||
#include "displayapp/screens/Metronome.h"
|
||||
#include "displayapp/screens/Music.h"
|
||||
|
@ -28,6 +27,7 @@
|
|||
#include "displayapp/screens/Steps.h"
|
||||
#include "displayapp/screens/PassKey.h"
|
||||
#include "displayapp/screens/Error.h"
|
||||
#include "displayapp/screens/Weather.h"
|
||||
|
||||
#include "drivers/Cst816s.h"
|
||||
#include "drivers/St7789.h"
|
||||
|
@ -39,7 +39,6 @@
|
|||
#include "displayapp/screens/settings/Settings.h"
|
||||
#include "displayapp/screens/settings/SettingWatchFace.h"
|
||||
#include "displayapp/screens/settings/SettingTimeFormat.h"
|
||||
#include "displayapp/screens/settings/SettingWeatherFormat.h"
|
||||
#include "displayapp/screens/settings/SettingWakeUp.h"
|
||||
#include "displayapp/screens/settings/SettingDisplay.h"
|
||||
#include "displayapp/screens/settings/SettingSteps.h"
|
||||
|
@ -49,7 +48,6 @@
|
|||
#include "displayapp/screens/settings/SettingBluetooth.h"
|
||||
|
||||
#include "libs/lv_conf.h"
|
||||
#include "UserApps.h"
|
||||
|
||||
using namespace Pinetime::Applications;
|
||||
using namespace Pinetime::Applications::Display;
|
||||
|
@ -96,25 +94,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
|||
touchHandler {touchHandler},
|
||||
filesystem {filesystem},
|
||||
lvgl {lcd, filesystem},
|
||||
timer(this, TimerCallback),
|
||||
controllers {batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
notificationManager,
|
||||
heartRateController,
|
||||
settingsController,
|
||||
motorController,
|
||||
motionController,
|
||||
alarmController,
|
||||
brightnessController,
|
||||
nullptr,
|
||||
filesystem,
|
||||
timer,
|
||||
nullptr,
|
||||
this,
|
||||
lvgl,
|
||||
nullptr,
|
||||
nullptr} {
|
||||
timer(this, TimerCallback) {
|
||||
}
|
||||
|
||||
void DisplayApp::Start(System::BootErrors error) {
|
||||
|
@ -420,31 +400,26 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
SetFullRefresh(direction);
|
||||
|
||||
switch (app) {
|
||||
case Apps::Launcher: {
|
||||
std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
|
||||
int i = 0;
|
||||
for (const auto& userApp : userApps) {
|
||||
apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
|
||||
}
|
||||
currentScreen = std::make_unique<Screens::ApplicationList>(this,
|
||||
settingsController,
|
||||
case Apps::Launcher:
|
||||
currentScreen =
|
||||
std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController, filesystem);
|
||||
break;
|
||||
case Apps::Motion:
|
||||
// currentScreen = std::make_unique<Screens::Motion>(motionController);
|
||||
// break;
|
||||
case Apps::None:
|
||||
case Apps::Clock:
|
||||
currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
|
||||
batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
filesystem,
|
||||
std::move(apps));
|
||||
} break;
|
||||
case Apps::Clock: {
|
||||
const auto* watchFace =
|
||||
std::find_if(userWatchFaces.begin(), userWatchFaces.end(), [this](const WatchFaceDescription& watchfaceDescription) {
|
||||
return watchfaceDescription.watchFace == settingsController.GetWatchFace();
|
||||
});
|
||||
if (watchFace != userWatchFaces.end())
|
||||
currentScreen.reset(watchFace->create(controllers));
|
||||
else {
|
||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||
}
|
||||
} break;
|
||||
notificationManager,
|
||||
settingsController,
|
||||
heartRateController,
|
||||
motionController,
|
||||
systemTask->nimble().weather(),
|
||||
filesystem);
|
||||
break;
|
||||
|
||||
case Apps::Error:
|
||||
currentScreen = std::make_unique<Screens::Error>(bootError);
|
||||
break;
|
||||
|
@ -476,6 +451,14 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
*systemTask,
|
||||
Screens::Notifications::Modes::Preview);
|
||||
break;
|
||||
case Apps::Timer:
|
||||
currentScreen = std::make_unique<Screens::Timer>(timer);
|
||||
break;
|
||||
case Apps::Alarm:
|
||||
currentScreen = std::make_unique<Screens::Alarm>(alarmController, settingsController.GetClockType(), *systemTask, motorController);
|
||||
break;
|
||||
|
||||
// Settings
|
||||
case Apps::QuickSettings:
|
||||
currentScreen = std::make_unique<Screens::QuickSettings>(this,
|
||||
batteryController,
|
||||
|
@ -488,20 +471,12 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
case Apps::Settings:
|
||||
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
||||
break;
|
||||
case Apps::SettingWatchFace: {
|
||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
|
||||
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::SettingWatchFace:
|
||||
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, settingsController, filesystem);
|
||||
break;
|
||||
case Apps::SettingTimeFormat:
|
||||
currentScreen = std::make_unique<Screens::SettingTimeFormat>(settingsController);
|
||||
break;
|
||||
case Apps::SettingWeatherFormat:
|
||||
currentScreen = std::make_unique<Screens::SettingWeatherFormat>(settingsController);
|
||||
break;
|
||||
case Apps::SettingWakeUp:
|
||||
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
||||
break;
|
||||
|
@ -539,17 +514,32 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
case Apps::FlashLight:
|
||||
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
||||
break;
|
||||
default: {
|
||||
const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
|
||||
return appDescription.app == app;
|
||||
});
|
||||
if (d != userApps.end()) {
|
||||
currentScreen.reset(d->create(controllers));
|
||||
} else {
|
||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||
}
|
||||
case Apps::StopWatch:
|
||||
currentScreen = std::make_unique<Screens::StopWatch>(*systemTask);
|
||||
break;
|
||||
case Apps::Twos:
|
||||
currentScreen = std::make_unique<Screens::Twos>();
|
||||
break;
|
||||
case Apps::Music:
|
||||
currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music());
|
||||
break;
|
||||
case Apps::Navigation:
|
||||
currentScreen = std::make_unique<Screens::Navigation>(systemTask->nimble().navigation());
|
||||
break;
|
||||
case Apps::HeartRate:
|
||||
currentScreen = std::make_unique<Screens::HeartRate>(heartRateController, *systemTask);
|
||||
break;
|
||||
case Apps::Metronome:
|
||||
currentScreen = std::make_unique<Screens::Metronome>(motorController, *systemTask);
|
||||
break;
|
||||
/* Weather debug app
|
||||
case Apps::Weather:
|
||||
currentScreen = std::make_unique<Screens::Weather>(this, systemTask->nimble().weather());
|
||||
break;
|
||||
*/
|
||||
case Apps::Steps:
|
||||
currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentApp = app;
|
||||
}
|
||||
|
@ -562,15 +552,7 @@ void DisplayApp::PushMessage(Messages msg) {
|
|||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
xQueueSend(msgQueue, &msg, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -607,19 +589,6 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
|
|||
|
||||
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
|
||||
this->systemTask = systemTask;
|
||||
this->controllers.systemTask = systemTask;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {
|
||||
this->controllers.weatherController = weatherService;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) {
|
||||
this->controllers.musicService = musicService;
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) {
|
||||
this->controllers.navigationService = NavigationService;
|
||||
}
|
||||
|
||||
void DisplayApp::ApplyBrightness() {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <task.h>
|
||||
#include <memory>
|
||||
#include <systemtask/Messages.h>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "displayapp/TouchEvents.h"
|
||||
#include "components/brightness/BrightnessController.h"
|
||||
|
@ -20,7 +20,6 @@
|
|||
#include "BootErrors.h"
|
||||
|
||||
#include "utility/StaticStack.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
|
@ -39,7 +38,6 @@ namespace Pinetime {
|
|||
class HeartRateController;
|
||||
class MotionController;
|
||||
class TouchHandler;
|
||||
class SimpleWeatherService;
|
||||
}
|
||||
|
||||
namespace System {
|
||||
|
@ -75,9 +73,6 @@ namespace Pinetime {
|
|||
void SetFullRefresh(FullRefreshDirections direction);
|
||||
|
||||
void Register(Pinetime::System::SystemTask* systemTask);
|
||||
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
|
||||
void Register(Pinetime::Controllers::MusicService* musicService);
|
||||
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
||||
|
||||
private:
|
||||
Pinetime::Drivers::St7789& lcd;
|
||||
|
@ -101,7 +96,6 @@ namespace Pinetime {
|
|||
Pinetime::Components::LittleVgl lvgl;
|
||||
Pinetime::Controllers::Timer timer;
|
||||
|
||||
AppControllers controllers;
|
||||
TaskHandle_t taskHandle;
|
||||
|
||||
States state = States::Running;
|
||||
|
|
|
@ -121,12 +121,3 @@ void DisplayApp::PushMessage(Display::Messages msg) {
|
|||
|
||||
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 "BootErrors.h"
|
||||
#include "displayapp/TouchEvents.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/Messages.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
@ -34,9 +34,6 @@ namespace Pinetime {
|
|||
class AlarmController;
|
||||
class BrightnessController;
|
||||
class FS;
|
||||
class SimpleWeatherService;
|
||||
class MusicService;
|
||||
class NavigationService;
|
||||
}
|
||||
|
||||
namespace System {
|
||||
|
@ -69,9 +66,6 @@ namespace Pinetime {
|
|||
|
||||
void PushMessage(Pinetime::Applications::Display::Messages msg);
|
||||
void Register(Pinetime::System::SystemTask* systemTask);
|
||||
void Register(Pinetime::Controllers::SimpleWeatherService* weatherService);
|
||||
void Register(Pinetime::Controllers::MusicService* musicService);
|
||||
void Register(Pinetime::Controllers::NavigationService* NavigationService);
|
||||
|
||||
private:
|
||||
TaskHandle_t taskHandle;
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
#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 {});
|
||||
}
|
||||
}
|
14
src/displayapp/WatchFaces.h
Normal file
14
src/displayapp/WatchFaces.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
enum class WatchFace : uint8_t {
|
||||
Digital = 0,
|
||||
Analog = 1,
|
||||
PineTimeStyle = 2,
|
||||
Infineat = 3,
|
||||
CasioStyleG7710 = 4,
|
||||
Fuzzy = 5,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
#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);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
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
|
||||
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
||||
open_sans_light fontawesome_weathericons)
|
||||
open_sans_light fontawesome_weathericons vulf_mono_italic)
|
||||
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
||||
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
||||
|
|
|
@ -3,22 +3,31 @@
|
|||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Bold.ttf",
|
||||
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
|
||||
"range": "0x20-0x7e, 0xB0, 0x410-0x44f"
|
||||
},
|
||||
{
|
||||
"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, 0xf743"
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
"size": 20,
|
||||
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
|
||||
"size": 20
|
||||
},
|
||||
"vulf_mono_italic": {
|
||||
"sources": [
|
||||
{
|
||||
"file": "Vulf_Mono-Italic.woff",
|
||||
"range": "0x20-0x7e, 0xB0"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
"size": 30
|
||||
},
|
||||
"jetbrains_mono_42": {
|
||||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Regular.ttf",
|
||||
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
|
||||
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -38,7 +47,7 @@
|
|||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-ExtraBold.ttf",
|
||||
"range": "0x30-0x3a"
|
||||
"range": "0x20, 0x30-0x3a"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -68,7 +77,7 @@
|
|||
"sources": [
|
||||
{
|
||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
||||
"range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc"
|
||||
"range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
|
|
@ -19,10 +19,6 @@
|
|||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/alarm/AlarmController.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
using Pinetime::Controllers::AlarmController;
|
||||
|
|
|
@ -17,19 +17,18 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "displayapp/LittleVgl.h"
|
||||
#include "components/alarm/AlarmController.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
class Alarm : public Screen {
|
||||
public:
|
||||
explicit Alarm(Controllers::AlarmController& alarmController,
|
||||
Alarm(Controllers::AlarmController& alarmController,
|
||||
Controllers::Settings::ClockType clockType,
|
||||
System::SystemTask& systemTask,
|
||||
Controllers::MotorController& motorController);
|
||||
|
@ -64,19 +63,6 @@ namespace Pinetime {
|
|||
Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
|
||||
Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps::Alarm> {
|
||||
static constexpr Apps app = Apps::Alarm;
|
||||
static constexpr const char* icon = Screens::Symbols::clock;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Alarm(controllers.alarmController,
|
||||
controllers.settingsController.GetClockType(),
|
||||
*controllers.systemTask,
|
||||
controllers.motorController);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#include "displayapp/screens/ApplicationList.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
|
@ -17,20 +16,18 @@ auto ApplicationList::CreateScreenList() const {
|
|||
return screens;
|
||||
}
|
||||
|
||||
ApplicationList::ApplicationList(DisplayApp* app,
|
||||
ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app,
|
||||
Pinetime::Controllers::Settings& settingsController,
|
||||
const Pinetime::Controllers::Battery& batteryController,
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
: app {app},
|
||||
settingsController {settingsController},
|
||||
batteryController {batteryController},
|
||||
bleController {bleController},
|
||||
dateTimeController {dateTimeController},
|
||||
filesystem {filesystem},
|
||||
apps {std::move(apps)},
|
||||
filesystem{filesystem},
|
||||
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||
}
|
||||
|
||||
|
@ -43,14 +40,9 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
|||
}
|
||||
|
||||
std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
|
||||
std::array<Tile::Applications, appsPerScreen> pageApps;
|
||||
|
||||
std::array<Tile::Applications, appsPerScreen> apps;
|
||||
for (int i = 0; i < appsPerScreen; i++) {
|
||||
if (i + (screenNum * appsPerScreen) >= apps.size()) {
|
||||
pageApps[i] = {"", Pinetime::Applications::Apps::None, false};
|
||||
} else {
|
||||
pageApps[i] = apps[i + (screenNum * appsPerScreen)];
|
||||
}
|
||||
apps[i] = applications[screenNum * appsPerScreen + i];
|
||||
}
|
||||
|
||||
return std::make_unique<Screens::Tile>(screenNum,
|
||||
|
@ -60,5 +52,5 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
|
|||
batteryController,
|
||||
bleController,
|
||||
dateTimeController,
|
||||
pageApps);
|
||||
apps);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "Screen.h"
|
||||
#include "ScreenList.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
#include "Tile.h"
|
||||
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/ScreenList.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "components/battery/BatteryController.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
#include "displayapp/screens/Navigation.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
|
@ -19,8 +22,7 @@ namespace Pinetime {
|
|||
const Pinetime::Controllers::Battery& batteryController,
|
||||
const Pinetime::Controllers::Ble& bleController,
|
||||
Controllers::DateTime& dateTimeController,
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
std::array<Tile::Applications, UserAppTypes::Count>&& apps);
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
~ApplicationList() override;
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
||||
|
@ -34,12 +36,27 @@ namespace Pinetime {
|
|||
const Pinetime::Controllers::Ble& bleController;
|
||||
Controllers::DateTime& dateTimeController;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
std::array<Tile::Applications, UserAppTypes::Count> apps;
|
||||
|
||||
static constexpr int appsPerScreen = 6;
|
||||
|
||||
static constexpr int nScreens = UserAppTypes::Count > 0 ? (UserAppTypes::Count - 1) / appsPerScreen + 1 : 1;
|
||||
// Increment this when more space is needed
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
|
145
src/displayapp/screens/Clock.cpp
Normal file
145
src/displayapp/screens/Clock.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#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);
|
||||
}
|
61
src/displayapp/screens/Clock.h
Normal file
61
src/displayapp/screens/Clock.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#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,7 +4,6 @@
|
|||
#include <chrono>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "Symbols.h"
|
||||
#include <lvgl/src/lv_core/lv_style.h>
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
|
||||
|
@ -38,15 +37,5 @@ namespace Pinetime {
|
|||
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,9 +5,6 @@
|
|||
#include <algorithm> // std::fill
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "Symbols.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include <displayapp/Controllers.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Components {
|
||||
|
@ -38,15 +35,5 @@ namespace Pinetime {
|
|||
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 "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/PageIndicator.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "components/settings/Settings.h"
|
||||
|
||||
#define MAXLISTITEMS 4
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "systemtask/SystemTask.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
|
@ -37,15 +36,5 @@ namespace Pinetime {
|
|||
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,8 +6,6 @@
|
|||
#include <lvgl/src/lv_core/lv_style.h>
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <components/motion/MotionController.h>
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
|
@ -32,15 +30,5 @@ namespace Pinetime {
|
|||
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,9 +21,6 @@
|
|||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include <string>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -85,15 +82,5 @@ namespace Pinetime {
|
|||
/** 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,21 +203,19 @@ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navServi
|
|||
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
|
||||
|
||||
txtNarrative = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT);
|
||||
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_width(txtNarrative, LV_HOR_RES);
|
||||
lv_obj_set_height(txtNarrative, 80);
|
||||
lv_label_set_text_static(txtNarrative, "Navigation");
|
||||
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
|
||||
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10);
|
||||
|
||||
txtManDist = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
|
||||
lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
|
||||
lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
||||
lv_obj_set_width(txtManDist, LV_HOR_RES);
|
||||
lv_label_set_text_static(txtManDist, "--M");
|
||||
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
|
||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
|
||||
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60);
|
||||
|
||||
// Route Progress
|
||||
barProgress = lv_bar_create(lv_scr_act(), nullptr);
|
||||
|
|
|
@ -22,9 +22,6 @@
|
|||
#include <string>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <array>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -58,15 +55,5 @@ namespace Pinetime {
|
|||
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,9 +3,6 @@
|
|||
#include <lvgl/lvgl.h>
|
||||
#include <cstdint>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Components {
|
||||
|
@ -48,15 +45,5 @@ namespace Pinetime {
|
|||
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,9 +4,6 @@
|
|||
#include <lvgl/lvgl.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include <components/motion/MotionController.h>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
|
@ -42,15 +39,5 @@ namespace Pinetime {
|
|||
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;
|
||||
}
|
||||
TimeSeparated_t times = convertTicksToTimeSegments(laps[i]);
|
||||
char buffer[17];
|
||||
char buffer[16];
|
||||
if (times.hours == 0) {
|
||||
snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
|
||||
sprintf(buffer, "#%2d %2d:%02d.%02d\n", i + 1, times.mins, times.secs, times.hundredths);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "#%2d %2d:%02d:%02d.%02d\n", i + 1, times.hours, times.mins, times.secs, times.hundredths);
|
||||
sprintf(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);
|
||||
}
|
||||
|
|
|
@ -7,13 +7,8 @@
|
|||
#include "portmacro_cmsis.h"
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
namespace Screens {
|
||||
namespace Pinetime::Applications::Screens {
|
||||
|
||||
enum class States { Init, Running, Halted };
|
||||
|
||||
|
@ -58,16 +53,4 @@ namespace Pinetime {
|
|||
|
||||
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,9 +45,6 @@ namespace Pinetime {
|
|||
static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80";
|
||||
static constexpr const char* smog = "\xEF\x9D\x9F";
|
||||
static constexpr const char* cloud = "\xEF\x83\x82";
|
||||
static constexpr const char* cloudMeatball = "\xEF\x9C\xBB";
|
||||
static constexpr const char* bolt = "\xEF\x83\xA7";
|
||||
static constexpr const char* snowflake = "\xEF\x8B\x9C";
|
||||
static constexpr const char* ban = "\xEF\x81\x9E";
|
||||
|
||||
// lv_font_sys_48.c
|
||||
|
|
|
@ -236,9 +236,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
|||
auto nb = uxTaskGetSystemState(tasksStatus, maxTaskCount, nullptr);
|
||||
std::sort(tasksStatus, tasksStatus + nb, sortById);
|
||||
for (uint8_t i = 0; i < nb && i < maxTaskCount; i++) {
|
||||
char buffer[11] = {0};
|
||||
char buffer[7] = {0};
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "%lu", tasksStatus[i].xTaskNumber);
|
||||
sprintf(buffer, "%lu", tasksStatus[i].xTaskNumber);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 0, buffer);
|
||||
switch (tasksStatus[i].eCurrentState) {
|
||||
case eReady:
|
||||
|
@ -262,9 +262,9 @@ std::unique_ptr<Screen> SystemInfo::CreateScreen4() {
|
|||
lv_table_set_cell_value(infoTask, i + 1, 1, buffer);
|
||||
lv_table_set_cell_value(infoTask, i + 1, 2, tasksStatus[i].pcTaskName);
|
||||
if (tasksStatus[i].usStackHighWaterMark < 20) {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu16 " low", tasksStatus[i].usStackHighWaterMark);
|
||||
sprintf(buffer, "%d low", tasksStatus[i].usStackHighWaterMark);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "%" PRIu16, tasksStatus[i].usStackHighWaterMark);
|
||||
sprintf(buffer, "%d", tasksStatus[i].usStackHighWaterMark);
|
||||
}
|
||||
lv_table_set_cell_value(infoTask, i + 1, 3, buffer);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "displayapp/screens/Tile.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/BatteryIcon.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "displayapp/InfiniTimeTheme.h"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/settings/Settings.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);
|
||||
lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
if (timer.IsRunning()) {
|
||||
if (timerController.IsRunning()) {
|
||||
SetTimerRunning();
|
||||
} else {
|
||||
SetTimerStopped();
|
||||
|
|
|
@ -8,10 +8,8 @@
|
|||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include "components/timer/Timer.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime::Applications {
|
||||
namespace Screens {
|
||||
namespace Pinetime::Applications::Screens {
|
||||
class Timer : public Screen {
|
||||
public:
|
||||
Timer(Controllers::Timer& timerController);
|
||||
|
@ -26,7 +24,7 @@ namespace Pinetime::Applications {
|
|||
void SetTimerRunning();
|
||||
void SetTimerStopped();
|
||||
void UpdateMask();
|
||||
Pinetime::Controllers::Timer& timer;
|
||||
Controllers::Timer& timer;
|
||||
|
||||
lv_obj_t* btnPlayPause;
|
||||
lv_obj_t* txtPlayPause;
|
||||
|
@ -44,15 +42,4 @@ namespace Pinetime::Applications {
|
|||
lv_coord_t maskPosition = 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;
|
||||
if (grid[row][col].value > 0) {
|
||||
char buffer[7];
|
||||
snprintf(buffer, sizeof(buffer), "%u", grid[row][col].value);
|
||||
sprintf(buffer, "%d", grid[row][col].value);
|
||||
lv_table_set_cell_value(gridDisplay, row, col, buffer);
|
||||
} else {
|
||||
lv_table_set_cell_value(gridDisplay, row, col, "");
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include <lvgl/src/lv_core/lv_obj.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Applications {
|
||||
|
@ -36,15 +35,5 @@ namespace Pinetime {
|
|||
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,23 +88,5 @@ namespace Pinetime {
|
|||
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,12 +5,10 @@
|
|||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <displayapp/Controllers.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "utility/DirtyValue.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -102,26 +100,5 @@ namespace Pinetime {
|
|||
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,11 +40,7 @@ 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));
|
||||
|
||||
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_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
|
||||
|
||||
|
@ -95,30 +91,7 @@ void WatchFaceDigital::Refresh() {
|
|||
uint8_t hour = dateTimeController.Hours();
|
||||
uint8_t minute = dateTimeController.Minutes();
|
||||
|
||||
/* 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) {
|
||||
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
||||
char ampmChar[3] = "AM";
|
||||
if (hour == 0) {
|
||||
hour = 12;
|
||||
|
@ -181,31 +154,3 @@ void WatchFaceDigital::Refresh() {
|
|||
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,7 +9,6 @@
|
|||
#include "components/ble/BleController.h"
|
||||
#include "displayapp/widgets/StatusIcons.h"
|
||||
#include "utility/DirtyValue.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -40,8 +39,6 @@ namespace Pinetime {
|
|||
private:
|
||||
uint8_t displayedHour = -1;
|
||||
uint8_t displayedMinute = -1;
|
||||
static const char* timeSectors[4];
|
||||
static const char* hourNames[12];
|
||||
|
||||
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
|
||||
Utility::DirtyValue<bool> powerPresent {};
|
||||
|
@ -74,25 +71,5 @@ namespace Pinetime {
|
|||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
158
src/displayapp/screens/WatchFaceFuzzy.cpp
Normal file
158
src/displayapp/screens/WatchFaceFuzzy.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#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);
|
||||
}
|
80
src/displayapp/screens/WatchFaceFuzzy.h
Normal file
80
src/displayapp/screens/WatchFaceFuzzy.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
#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,11 +4,9 @@
|
|||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <displayapp/Controllers.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "utility/DirtyValue.h"
|
||||
#include "displayapp/apps/Apps.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -100,25 +98,5 @@ namespace Pinetime {
|
|||
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,14 +27,13 @@
|
|||
#include "displayapp/screens/BleIcon.h"
|
||||
#include "displayapp/screens/NotificationIcon.h"
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "displayapp/screens/WeatherSymbols.h"
|
||||
#include "components/battery/BatteryController.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "components/ble/NotificationManager.h"
|
||||
#include "components/motion/MotionController.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
|
@ -51,7 +50,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
|||
Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Settings& settingsController,
|
||||
Controllers::MotionController& motionController,
|
||||
Controllers::SimpleWeatherService& weatherService)
|
||||
Controllers::WeatherService& weatherService)
|
||||
: currentDateTime {{}},
|
||||
batteryIcon(false),
|
||||
dateTimeController {dateTimeController},
|
||||
|
@ -116,7 +115,7 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
|||
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
|
||||
lv_label_set_text(weatherIcon, Symbols::ban);
|
||||
lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
|
||||
lv_obj_align(weatherIcon, sidebar, LV_ALIGN_IN_TOP_MID, 0, 35);
|
||||
lv_obj_set_auto_realign(weatherIcon, true);
|
||||
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
||||
|
@ -127,7 +126,6 @@ WatchFacePineTimeStyle::WatchFacePineTimeStyle(Controllers::DateTime& dateTimeCo
|
|||
|
||||
temperature = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
|
||||
lv_label_set_text(temperature, "--");
|
||||
lv_obj_align(temperature, sidebar, LV_ALIGN_IN_TOP_MID, 0, 65);
|
||||
if (settingsController.GetPTSWeather() == Pinetime::Controllers::Settings::PTSWeather::On) {
|
||||
lv_obj_set_hidden(temperature, false);
|
||||
|
@ -539,23 +537,29 @@ void WatchFacePineTimeStyle::Refresh() {
|
|||
}
|
||||
}
|
||||
|
||||
currentWeather = weatherService.Current();
|
||||
|
||||
if (currentWeather.IsUpdated()) {
|
||||
auto optCurrentWeather = currentWeather.Get();
|
||||
if (optCurrentWeather) {
|
||||
int16_t temp = optCurrentWeather->temperature;
|
||||
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||
}
|
||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||
lv_label_set_text_fmt(temperature, "%d°", temp);
|
||||
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||
if (weatherService.GetCurrentTemperature()->timestamp != 0 && weatherService.GetCurrentClouds()->timestamp != 0 &&
|
||||
weatherService.GetCurrentPrecipitation()->timestamp != 0) {
|
||||
nowTemp = (weatherService.GetCurrentTemperature()->temperature / 100);
|
||||
clouds = (weatherService.GetCurrentClouds()->amount);
|
||||
precip = (weatherService.GetCurrentPrecipitation()->amount);
|
||||
if (nowTemp.IsUpdated()) {
|
||||
lv_label_set_text_fmt(temperature, "%d°", nowTemp.Get());
|
||||
if ((clouds <= 30) && (precip == 0)) {
|
||||
lv_label_set_text(weatherIcon, Symbols::sun);
|
||||
} else if ((clouds >= 70) && (clouds <= 90) && (precip == 1)) {
|
||||
lv_label_set_text(weatherIcon, Symbols::cloudSunRain);
|
||||
} else if ((clouds > 90) && (precip == 0)) {
|
||||
lv_label_set_text(weatherIcon, Symbols::cloud);
|
||||
} else if ((clouds > 70) && (precip >= 2)) {
|
||||
lv_label_set_text(weatherIcon, Symbols::cloudShowersHeavy);
|
||||
} else {
|
||||
lv_label_set_text(weatherIcon, Symbols::cloudSun);
|
||||
};
|
||||
lv_obj_realign(temperature);
|
||||
lv_obj_realign(weatherIcon);
|
||||
}
|
||||
} else {
|
||||
lv_label_set_text(temperature, "--");
|
||||
lv_label_set_text_static(temperature, "--");
|
||||
lv_label_set_text(weatherIcon, Symbols::ban);
|
||||
lv_obj_realign(temperature);
|
||||
lv_obj_realign(weatherIcon);
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <displayapp/Controllers.h>
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/screens/BatteryIcon.h"
|
||||
#include "displayapp/Colors.h"
|
||||
#include "components/datetime/DateTimeController.h"
|
||||
#include "components/ble/SimpleWeatherService.h"
|
||||
#include "components/ble/weather/WeatherService.h"
|
||||
#include "components/ble/BleController.h"
|
||||
#include "utility/DirtyValue.h"
|
||||
|
||||
|
@ -33,7 +32,7 @@ namespace Pinetime {
|
|||
Controllers::NotificationManager& notificationManager,
|
||||
Controllers::Settings& settingsController,
|
||||
Controllers::MotionController& motionController,
|
||||
Controllers::SimpleWeatherService& weather);
|
||||
Controllers::WeatherService& weather);
|
||||
~WatchFacePineTimeStyle() override;
|
||||
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
@ -61,7 +60,9 @@ namespace Pinetime {
|
|||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime {};
|
||||
Utility::DirtyValue<uint32_t> stepCount {};
|
||||
Utility::DirtyValue<bool> notificationState {};
|
||||
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
|
||||
Utility::DirtyValue<int16_t> nowTemp {};
|
||||
int16_t clouds = 0;
|
||||
int16_t precip = 0;
|
||||
|
||||
static Pinetime::Controllers::Settings::Colors GetNext(Controllers::Settings::Colors color);
|
||||
static Pinetime::Controllers::Settings::Colors GetPrevious(Controllers::Settings::Colors color);
|
||||
|
@ -112,7 +113,7 @@ namespace Pinetime {
|
|||
Controllers::NotificationManager& notificationManager;
|
||||
Controllers::Settings& settingsController;
|
||||
Controllers::MotionController& motionController;
|
||||
Controllers::SimpleWeatherService& weatherService;
|
||||
Controllers::WeatherService& weatherService;
|
||||
|
||||
void SetBatteryIcon();
|
||||
void CloseMenu();
|
||||
|
@ -120,25 +121,5 @@ namespace Pinetime {
|
|||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
#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());
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
#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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
221
src/displayapp/screens/Weather.cpp
Normal file
221
src/displayapp/screens/Weather.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
/* 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));
|
||||
}
|
45
src/displayapp/screens/Weather.h
Normal file
45
src/displayapp/screens/Weather.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#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();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#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_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
|
||||
|
||||
char buffer[4];
|
||||
char buffer[12];
|
||||
for (unsigned int i = 0; i < options.size(); i++) {
|
||||
cbOption[i] = lv_checkbox_create(container1, nullptr);
|
||||
snprintf(buffer, sizeof(buffer), "%2" PRIu16 "s", options[i] / 1000);
|
||||
sprintf(buffer, "%2ds", options[i] / 1000);
|
||||
lv_checkbox_set_text(cbOption[i], buffer);
|
||||
cbOption[i]->user_data = this;
|
||||
lv_obj_set_event_cb(cbOption[i], event_handler);
|
||||
|
|
|
@ -13,10 +13,9 @@ namespace {
|
|||
const char* name;
|
||||
};
|
||||
|
||||
constexpr std::array<Option, 3> options = {{
|
||||
constexpr std::array<Option, 2> options = {{
|
||||
{Pinetime::Controllers::Settings::ClockType::H12, "12-hour"},
|
||||
{Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
|
||||
{Pinetime::Controllers::Settings::ClockType::Fuzzy, "Fuzzy"},
|
||||
}};
|
||||
|
||||
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "displayapp/DisplayApp.h"
|
||||
#include "displayapp/screens/Screen.h"
|
||||
#include "components/settings/Settings.h"
|
||||
#include "displayapp/WatchFaces.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
|
@ -20,11 +21,9 @@ auto SettingWatchFace::CreateScreenList() const {
|
|||
}
|
||||
|
||||
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
|
||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||
Pinetime::Controllers::Settings& settingsController,
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
: app {app},
|
||||
watchfaceItems {std::move(watchfaceItems)},
|
||||
settingsController {settingsController},
|
||||
filesystem {filesystem},
|
||||
screens {app, 0, CreateScreenList(), Screens::ScreenListModes::UpDown} {
|
||||
|
@ -41,11 +40,7 @@ bool SettingWatchFace::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
|||
std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) const {
|
||||
std::array<Screens::CheckboxList::Item, settingsPerScreen> watchfacesOnThisScreen;
|
||||
for (int i = 0; i < settingsPerScreen; i++) {
|
||||
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
|
||||
watchfacesOnThisScreen[i] = {"", false};
|
||||
} else {
|
||||
watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)];
|
||||
}
|
||||
watchfacesOnThisScreen[i] = watchfaces[screenNum * settingsPerScreen + i];
|
||||
}
|
||||
|
||||
return std::make_unique<Screens::CheckboxList>(
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "displayapp/screens/CheckboxList.h"
|
||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||
#include "displayapp/screens/WatchFaceFuzzy.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
||||
|
@ -19,10 +20,7 @@ namespace Pinetime {
|
|||
|
||||
class SettingWatchFace : public Screen {
|
||||
public:
|
||||
SettingWatchFace(DisplayApp* app,
|
||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||
Pinetime::Controllers::Settings& settingsController,
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
SettingWatchFace(DisplayApp* app, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem);
|
||||
~SettingWatchFace() override;
|
||||
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
|
@ -32,16 +30,25 @@ namespace Pinetime {
|
|||
auto CreateScreenList() 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;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
|
||||
static constexpr const char* title = "Watch face";
|
||||
static constexpr const char* symbol = Symbols::home;
|
||||
|
||||
static constexpr int settingsPerScreen = 4;
|
||||
|
||||
// Increment this when more space is needed
|
||||
static constexpr int nScreens = 2;
|
||||
|
||||
std::array<Screens::CheckboxList::Item, settingsPerScreen * nScreens> watchfaces {
|
||||
{{"Digital face", true},
|
||||
{"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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
#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());
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#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 <lvgl/lvgl.h>
|
||||
#include <functional>
|
||||
#include "displayapp/apps/Apps.h"
|
||||
#include "displayapp/Apps.h"
|
||||
#include "displayapp/DisplayApp.h"
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Pinetime {
|
|||
static constexpr int entriesPerScreen = 4;
|
||||
|
||||
// Increment this when more space is needed
|
||||
static constexpr int nScreens = 4;
|
||||
static constexpr int nScreens = 3;
|
||||
|
||||
static constexpr std::array<List::Applications, entriesPerScreen * nScreens> entries {{
|
||||
{Symbols::sun, "Display", Apps::SettingDisplay},
|
||||
|
@ -39,14 +39,12 @@ namespace Pinetime {
|
|||
|
||||
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
||||
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
||||
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
|
||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
||||
|
||||
{Symbols::clock, "Chimes", Apps::SettingChimes},
|
||||
|
||||
{Symbols::tachometer, "Shake Calib.", Apps::SettingShakeThreshold},
|
||||
{Symbols::check, "Firmware", Apps::FirmwareValidation},
|
||||
{Symbols::bluetooth, "Bluetooth", Apps::SettingBluetooth},
|
||||
|
||||
{Symbols::list, "About", Apps::SysInfo},
|
||||
|
||||
// {Symbols::none, "None", Apps::None},
|
||||
|
|
|
@ -131,8 +131,8 @@ void SpiMaster::OnEndEvent() {
|
|||
if (s > 0) {
|
||||
auto currentSize = std::min((size_t) 255, s);
|
||||
PrepareTx(currentBufferAddr, currentSize);
|
||||
currentBufferAddr = currentBufferAddr + currentSize;
|
||||
currentBufferSize = currentBufferSize - currentSize;
|
||||
currentBufferAddr += currentSize;
|
||||
currentBufferSize -= currentSize;
|
||||
|
||||
spiBaseAddress->TASKS_START = 1;
|
||||
} else {
|
||||
|
@ -153,7 +153,7 @@ void SpiMaster::OnEndEvent() {
|
|||
void SpiMaster::OnStartedEvent() {
|
||||
}
|
||||
|
||||
void SpiMaster::PrepareTx(const uint32_t bufferAddress, const size_t size) {
|
||||
void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size) {
|
||||
spiBaseAddress->TXD.PTR = bufferAddress;
|
||||
spiBaseAddress->TXD.MAXCNT = size;
|
||||
spiBaseAddress->TXD.LIST = 0;
|
||||
|
@ -163,7 +163,7 @@ void SpiMaster::PrepareTx(const uint32_t bufferAddress, const size_t size) {
|
|||
spiBaseAddress->EVENTS_END = 0;
|
||||
}
|
||||
|
||||
void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
|
||||
void SpiMaster::PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size) {
|
||||
spiBaseAddress->TXD.PTR = 0;
|
||||
spiBaseAddress->TXD.MAXCNT = 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);
|
||||
PrepareTx(currentBufferAddr, currentSize);
|
||||
currentBufferSize = currentBufferSize - currentSize;
|
||||
currentBufferAddr = currentBufferAddr + currentSize;
|
||||
currentBufferSize -= currentSize;
|
||||
currentBufferAddr += currentSize;
|
||||
spiBaseAddress->TASKS_START = 1;
|
||||
|
||||
if (size == 1) {
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace {
|
|||
// RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent
|
||||
// one of the eight reload registers available.
|
||||
// In this case, we enable only the first one.
|
||||
NRF_WDT->RREN = NRF_WDT->RREN | 1;
|
||||
NRF_WDT->RREN |= 1;
|
||||
}
|
||||
|
||||
/// 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")
|
||||
message(STATUS "Using ${LV_FONT_CONV} to generate font files")
|
||||
|
||||
find_program(LV_IMG_CONV "lv_img_conv.py" NO_CACHE REQUIRED
|
||||
HINTS "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED
|
||||
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
|
||||
message(STATUS "Using ${LV_IMG_CONV} to generate font files")
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
|
|
|
@ -11,9 +11,6 @@ import subprocess
|
|||
|
||||
def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str):
|
||||
args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format]
|
||||
if lv_img_conv.endswith(".py"):
|
||||
# lv_img_conv is a python script, call with current python executable
|
||||
args = [sys.executable] + args
|
||||
|
||||
return args
|
||||
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
#!/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,9 +136,6 @@ void SystemTask::Work() {
|
|||
settingsController.Init();
|
||||
|
||||
displayApp.Register(this);
|
||||
displayApp.Register(&nimbleController.weather());
|
||||
displayApp.Register(&nimbleController.music());
|
||||
displayApp.Register(&nimbleController.navigation());
|
||||
displayApp.Start(bootError);
|
||||
|
||||
heartRateSensor.Init();
|
||||
|
|
Loading…
Reference in New Issue
Block a user