67 Commits

Author SHA1 Message Date
49c801b827 Fix location counter width, font, and lat/lng bugs 2024-06-11 21:27:26 -07:00
Ryan Rix
f6a5588544 add location setting and integrate more sundial code from willb 2024-06-10 20:19:43 -07:00
Ryan Rix
41518cc529 add larger font for fuzzy face 2024-06-10 20:19:06 -07:00
Ryan Rix
c20db14c43 how much heap can one smartwatch need, michael, 40kb? 2024-06-09 20:48:06 -07:00
Ryan Rix
9520b2ec9b remove unused controllers from fuzzy 2024-06-09 20:28:52 -07:00
Ryan Rix
8cf38a3469 remove unused open sans light 2024-06-09 20:28:35 -07:00
Ryan Rix
83cae9b5bc wip sundial w/ copied analog watchface 2024-06-09 20:28:21 -07:00
Ryan Rix
bb743689e1 vulf-ify me captain 2024-06-09 00:11:00 -07:00
Ryan Rix
4b854a5d09 WIP fuzzy 2024-06-07 16:46:06 -07:00
Felipe Martínez
9e406c70f9 Remove unnecessary BMA421 reads 2024-06-02 15:48:26 +02:00
Felipe Martinez
0c87bc27b2 Move motorController.Init call to DisplayApp::Start 2024-06-02 15:14:02 +02:00
mark9064
0dcfb2edb7 Fix erratum 58 workaround 2024-05-12 13:57:12 +02:00
mark9064
06c6935315 Include task header (Fixes sim) 2024-05-01 16:17:59 +02:00
mark9064
47c104643d Move includes back 2024-05-01 16:17:59 +02:00
mark9064
7b1110187e Apply display driver datasheet delays 2024-05-01 16:17:59 +02:00
mark9064
7e460d3c80 Use FreeRTOS delay instead of spinning the CPU 2024-05-01 16:17:59 +02:00
mark9064
7a9211587a Rename to pre-transaction hook 2024-05-01 16:13:47 +02:00
mark9064
24e6a2f8ab Avoid storing lambda 2024-05-01 16:13:47 +02:00
mark9064
9a7ba405e1 Refactor lambdas 2024-05-01 16:13:47 +02:00
mark9064
940cd3459f Use functional abstraction for hooks 2024-05-01 16:13:47 +02:00
mark9064
869bec8f88 Refactor display WriteToRam 2024-05-01 16:13:47 +02:00
mark9064
ee925200c3 Remove task to notify 2024-05-01 16:13:47 +02:00
mark9064
079e676baf SPI transaction hooks 2024-05-01 16:13:47 +02:00
John Crawford
6b5235c301 fix: heartrate app displays --- instead of 000 (#1887) 2024-04-12 14:50:33 +02:00
BloodStainedCrow
57e625d4dc Unify docker devcontainer with dockerfile used for CI (#1587)
* Only use one Dockerfile and build.sh script for both docker and devcontainer
* Remove all now unneccessary tasks and scripts
* Update to clang-format-14
* Move devcontainer.json into root folder
* Fix conditional statements in Dockerfile
* Move .devcontainer/README into doc/usingDevcontainers
* Remove obsolete VSCode Task
* Change standard compiler path to the correct compiler
* Set GDB Path for debugging
* Hide broken buttons from CMake Extension
* Refactor .devcontainer
* Remove unneccessary postBuildCommand
* Add devcontainer dependencies to all docker images
* Add Devcontainer Debug launch config
* Add an additional c_cpp_properties config as a fallback for devcontainer
* Remove obsolete Docker Argument
* Fix wrong C/Cpp versions
* Fix silent fail of gdb, add libncurses5
2024-03-23 10:45:45 +01:00
Victor Kareh
9a5f516c42 WatchFaceDigital: Remove unused variables 2024-03-20 20:31:35 +01:00
JF
4ca2112890 Emit the message BleRadioEnableToggle to DisplayApp only if the enable state of the radio has actually changed. (#2037)
This fixes an issue where the BLE connected logo would disappear when opening and closing the BLE setting (without changing it) while InfiniTime was already connected to a companion app.

Co-authored-by: JustScott <development@justscott.me>
2024-03-15 09:20:19 +01:00
FintasticMan
7dbb8f54c6 chrono: Resolve TODOs related to C++20 chrono feats 2024-03-14 21:09:03 +01:00
adumelie
14c6270903 Typo in example code 2024-03-14 20:27:19 +01:00
adumelie
ef38472278 More typos in code example 2024-03-14 20:27:19 +01:00
adumelie
4c9d5332c7 Typo in code example 2024-03-14 20:27:19 +01:00
adumelie
9aa0bc8ecf Fix and update documentation
Fixed missing colon typo in template for minimal app example
Updated reference to Apps enum after file move
2024-03-14 20:27:19 +01:00
Ritvik
4d86a3c6c1 Fix conflicts of min and max from stl_algo.h (included from atomic) 2024-03-13 21:58:13 +01:00
Victor Kareh
3b4b5a5163 ApplicationList: Reset app menu screen when loading watch face
This prevents the application list from loading in the last used screen
and instead goes back to the first screen whenever the watch face is
loaded.

Fixes #2006
2024-03-12 21:01:42 +01:00
FintasticMan
f2df0c45ef lowersleep: Improve algorithm by checking wrist angle
Inspired by https://github.com/InfiniTimeOrg/InfiniTime/pull/827#issuecomment-1881580414.
2024-03-12 19:14:58 +01:00
mark9064
636af4d33d Simplify ISR task wake checks
The macro checks the variable, so we don't need to check it ourselves
2024-03-12 19:09:01 +01:00
mark9064
70f6604878 Fix chimes 2024-03-12 19:08:17 +01:00
Victor Kareh
004b2bf3a0 README: Fix broken URLs 2024-02-25 13:17:19 +01:00
Victor Kareh
6ab512a6b6 weather: Define function to round and render temperature 2024-02-18 12:57:48 +01:00
Victor Kareh
68ae335a97 weather: Pad forecast temperatures
This ensures temperatures are correctly aligned with one another
2024-02-18 12:57:48 +01:00
Victor Kareh
1857b02efa weather: Colorize forecast temperatures 2024-02-18 12:57:48 +01:00
Victor Kareh
f422929d8c weather: Add new app with forecast 2024-02-18 12:57:48 +01:00
Victor Kareh
5d971690cb DateTimeController: Make DayOfWeekShortToStringLow static
This allows it to be used outside of the current datetime context and
makes it consistent with the MonthShortToStringLow function.
2024-02-18 12:57:48 +01:00
Tim Teichmann
b0a0afdd4b README: Make hex and UTF-8 code consistent for the chosen example 2024-02-15 21:20:33 +01:00
mark9064
93d7da1691 Fix OOB flash write when DFU interrupted
If a DFU is restarted, the write indices aren't reset causing the image to be written out of bounds. The CRC check prevents the faulty image from booting but LittleFS still gets nuked.
2024-02-14 19:12:08 +01:00
Victor Kareh
fa1af55a45 Alarm: Replace clock icon with bell 2024-02-12 16:08:40 +01:00
Graham Jones
c2c53bc6ab bma421: Change acceleration values to 'binary milli-G' units
Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-02-11 20:46:26 +01:00
Victor Kareh
a49dc15a6e WatchFaceDigital: Move weather widget away from status icons
The weather widget is too high and could overlap the status icons.
Moving it to match the rest of the face avoids this issue and makes it
align with the rest of the theme.
2024-02-11 14:13:17 +01:00
mark9064
4c274421d9 Remove GFX (and unused GFX dependencies) 2024-02-11 13:28:01 +01:00
Victor Kareh
2db920599e SimpleWeatherService: Add forecast operator overrides (#2011)
Any screen that relies on DirtyValue to display up-to-date forecast data
would require the struct to provide an operator override for comparison.
2024-02-10 19:24:46 +01:00
kieranc
44be356dc2 navigation: Missing character for font 2024-02-07 08:46:09 +01:00
Victor Kareh
2135e12b33 WatchFaceDigital: Add weather display
If weather is available, display the cloud icon and temperature.
2024-01-23 23:39:28 +01:00
apilat
a6cd3679eb Only inhibit sleep if motion notifications are enabled, not just Bluetooth 2024-01-23 14:58:21 +01:00
apilat
074df0526f Keep updating motion during sleep when Bluetooth is on 2024-01-23 14:58:21 +01:00
Yusuf Ebrahim
a40168a9d7 New dice-rolling app: InfiniDice! (#1326)
Add new App `Dice.h` to randomly roll the dice(s).
The number of dice can range from 1-9 (default 1), and the sides can
range from d2-d99 (default d2).

To have a haptic feedback we make Dice vibrate on roll.

Regarding the use of C++ `<random>` library:
There are known problems with `rand()` and `srand()` (see https://en.cppreference.com/w/cpp/numeric/random/rand)
and the `<random>` library is preferred for this reason. The function used from
`<random>` also avoids a very rare bias that would occur using `rand()` and modulo,
when `RAND_MAX` is not a multiple of `d` and the initially generated number falls in
the last "short" segment. This commit also updates the seed to derive entropy
(via `seed_seq`) from a mix of the system tick count and the x,y,z components of the
PineTime motion controller -- taking inspiration from and with credit to @w4tsn
(https://github.com/InfiniTimeOrg/InfiniTime/pull/1199)

Thanks for suggestions:
* in Dice, when rolling 1d2, also show "HEADS" or "TAILS" -- suggestion by @medeyko
* ui adjustments and result realignment -- suggestion by @Boteium

---------

Co-authored-by: NeroBurner <pyro4hell@gmail.com>
Co-authored-by: Riku Isokoski <riksu9000@gmail.com>
Co-authored-by: Paul Weiß <45500341+Poohl@users.noreply.github.com>
Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-01-23 09:45:52 +01:00
Reinhold Gschweicher
a481af06cf lv_img_conv: support other modes like 'P'
Support other image modes like `P`, which uses 8 bits per pixel and a
color palette to save space.

Luckily the Pillow module can do the mode conversion for us.

Fixes: https://github.com/InfiniTimeOrg/InfiniTime/issues/1985
2024-01-23 08:51:14 +01:00
Lennart Jahn
034d83fe6b Split declaration and implementation of GetSymbol 2024-01-16 22:34:51 +01:00
Victor Kareh
264b5bed43 WatchFacePineTimeStyle: Fix conditional in weather display (#1965)
Since returning a valid weather is always considered an updated value,
if the current weather is empty, the face will attempt to display the
temperature and icon as empty values, rather than clearing the labels.
2024-01-14 22:37:26 +01:00
FintasticMan
0503248a25 hook: Find correct clang-format version better 2024-01-12 14:42:53 +01:00
FintasticMan
c634a4e3b4 hook: Use clang-format with highest version 2024-01-12 14:42:53 +01:00
FintasticMan
ab8e267e28 hook: Update pre-commit git hook 2024-01-12 14:42:53 +01:00
Jean-François Milants
ecf2f564f7 Set version to 1.14.0 2024-01-06 15:21:29 +01:00
Jean-François Milants
6505336d60 CMake watch faces selection
Documentation : watch faces are not system apps anymore.

Co-authored-by: FintasticMan <finlay.neon.kid@gmail.com>
2024-01-06 14:44:10 +01:00
Jean-François Milants
25b3e2461d CMake watch faces selection
Improve wording and replace "watchface" by "watch face" in Apps.md.
Improve CMake readability regarding watch face selection

Co-authored-by: Reinhold Gschweicher <pyro4hell@gmail.com>
2024-01-06 14:44:10 +01:00
Jean-François Milants
72c992c84e Watch face selection using CMake
Update Apps.md to mention the selection of watchfaces using Cmake.
2024-01-06 14:44:10 +01:00
Jean-François Milants
22f6d4a40b Watch face selection using CMake
The list of watch face to build into the firmware is now set by CMake (-DENABLE_WATCHFACES).

Fix SettingWatchFace : convert to index to/from WatchFace when needed.
2024-01-06 14:44:10 +01:00
Reinhold Gschweicher
12acef6a71 apps: restore default apps ordering in CMake
Restore the default list of apps to compile. The ordering was changed in
the changeset to make the app-list configurable through a CMake-variable
in https://github.com/InfiniTimeOrg/InfiniTime/pull/1928

In the process have one app per line to create the default app list in
CMake. This makes git diffs easer and more readable.
2024-01-06 12:38:36 +01:00
104 changed files with 3606 additions and 1182 deletions

32
.devcontainer.json Normal file
View File

@@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
{
"build": {
"dockerfile": "docker/Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
// Set *default* container specific settings.json values on container create.
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash"
}
},
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true,
// FIXME: This and the Dockerfile might get out of sync
"clang-format.executable": "clang-format-14"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"marus25.cortex-debug",
"notskm.clang-tidy",
"mjohns.clang-format"
]
}
},
"remoteUser": "infinitime"
}

View File

@@ -1,66 +0,0 @@
FROM ubuntu:latest
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq \
&& apt-get install -y \
# x86_64 / generic packages
bash \
build-essential \
cmake \
git \
make \
python3 \
python3-pip \
python3-pil \
tar \
unzip \
wget \
curl \
dos2unix \
clang-format-12 \
clang-tidy \
locales \
libncurses5 \
# aarch64 packages
libffi-dev \
libssl-dev \
python3-dev \
rustc \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
#SET LOCALE
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN pip3 install adafruit-nrfutil
# required for McuBoot
RUN pip3 install setuptools_rust
WORKDIR /opt/
# build.sh knows how to compile but it problimatic on Win10
COPY build.sh .
RUN chmod +x build.sh
# create_build_openocd.sh uses cmake to crate to build directory
COPY create_build_openocd.sh .
RUN chmod +x create_build_openocd.sh
# Lets get each in a separate docker layer for better downloads
# GCC
# RUN bash -c "source /opt/build.sh; GetGcc;"
RUN wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -O - | tar -xj -C /opt
# NrfSdk
# RUN bash -c "source /opt/build.sh; GetNrfSdk;"
RUN wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip" -O /tmp/nRF5_SDK_15.3.0_59ac345
RUN unzip -q /tmp/nRF5_SDK_15.3.0_59ac345 -d /opt
RUN rm /tmp/nRF5_SDK_15.3.0_59ac345
# McuBoot
# RUN bash -c "source /opt/build.sh; GetMcuBoot;"
RUN git clone https://github.com/mcu-tools/mcuboot.git
RUN pip3 install -r ./mcuboot/scripts/requirements.txt
RUN adduser infinitime
ENV NRF5_SDK_PATH /opt/nRF5_SDK_15.3.0_59ac345
ENV ARM_NONE_EABI_TOOLCHAIN_PATH /opt/gcc-arm-none-eabi-9-2020-q2-update
ENV SOURCES_DIR /workspaces/InfiniTime

View File

@@ -1,87 +0,0 @@
#!/bin/bash
(return 0 2>/dev/null) && SOURCED="true" || SOURCED="false"
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
set -x
set -e
# Default locations if the var isn't already set
export TOOLS_DIR="${TOOLS_DIR:=/opt}"
export SOURCES_DIR="${SOURCES_DIR:=/sources}"
export BUILD_DIR="${BUILD_DIR:=$SOURCES_DIR/build}"
export OUTPUT_DIR="${OUTPUT_DIR:=$BUILD_DIR/output}"
export BUILD_TYPE=${BUILD_TYPE:=Release}
export GCC_ARM_VER=${GCC_ARM_VER:="gcc-arm-none-eabi-9-2020-q2-update"}
export NRF_SDK_VER=${NRF_SDK_VER:="nRF5_SDK_15.3.0_59ac345"}
MACHINE="$(uname -m)"
[[ "$MACHINE" == "arm64" ]] && MACHINE="aarch64"
main() {
local target="$1"
mkdir -p "$TOOLS_DIR"
[[ ! -d "$TOOLS_DIR/$GCC_ARM_VER" ]] && GetGcc
[[ ! -d "$TOOLS_DIR/$NRF_SDK_VER" ]] && GetNrfSdk
[[ ! -d "$TOOLS_DIR/mcuboot" ]] && GetMcuBoot
mkdir -p "$BUILD_DIR"
CmakeGenerate
CmakeBuild $target
BUILD_RESULT=$?
if [ "$DISABLE_POSTBUILD" != "true" -a "$BUILD_RESULT" == 0 ]; then
source "$BUILD_DIR/post_build.sh"
fi
# assuming post_build.sh will never fail on a successful build
return $BUILD_RESULT
}
GetGcc() {
GCC_SRC="$GCC_ARM_VER-$MACHINE-linux.tar.bz"
wget -q https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/$GCC_SRC -O - | tar -xj -C $TOOLS_DIR/
}
GetMcuBoot() {
git clone https://github.com/mcu-tools/mcuboot.git "$TOOLS_DIR/mcuboot"
pip3 install -r "$TOOLS_DIR/mcuboot/scripts/requirements.txt"
}
GetNrfSdk() {
wget -q "https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/$NRF_SDK_VER.zip" -O /tmp/$NRF_SDK_VER
unzip -q /tmp/$NRF_SDK_VER -d "$TOOLS_DIR/"
rm /tmp/$NRF_SDK_VER
}
CmakeGenerate() {
# We can swap the CD and trailing SOURCES_DIR for -B and -S respectively
# once we go to newer CMake (Ubuntu 18.10 gives us CMake 3.10)
cd "$BUILD_DIR"
cmake -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
"$SOURCES_DIR"
cmake -L -N .
}
CmakeBuild() {
local target="$1"
[[ -n "$target" ]] && target="--target $target"
if cmake --build "$BUILD_DIR" --config $BUILD_TYPE $target -- -j$(nproc)
then return 0; else return 1;
fi
}
if [[ $SOURCED == "false" ]]; then
# It is important to return exit code of main
# To be future-proof, this is handled explicitely
main "$@"
BUILD_RESULT=$?
exit $BUILD_RESULT
else
echo "Sourced!"
fi

View File

@@ -1,2 +0,0 @@
#!/bin/bash
cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app

View File

@@ -1,3 +0,0 @@
#!/bin/bash
rm -rf build/
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 -S . -Bbuild

View File

@@ -1,38 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.2/containers/cpp
{
// "name": "Pinetime",
// "image": "feabhas/pinetime-dev"
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04
// "args": { "VARIANT": "ubuntu-20.04" }
},
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.formatOnSave": true,
"clang-format.executable": "clang-format-12"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"marus25.cortex-debug",
"notskm.clang-tidy",
"mjohns.clang-format"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "bash /opt/create_build_openocd.sh",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
// "remoteUser": "vscode"
"remoteUser": "infinitime"
}

View File

@@ -1,2 +0,0 @@
#!/bin/bash
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DUSE_OPENOCD=1 -DARM_NONE_EABI_TOOLCHAIN_PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update -DNRF5_SDK_PATH=/opt/nRF5_SDK_15.3.0_59ac345 ${SOURCES_DIR}

View File

@@ -3,7 +3,7 @@ name: CI
# Run this workflow whenever the build may be affected
on:
push:
branches: [ main, wb/fuzzy, wb/fuzzy-norm, wb/fuzzy-analog, wb/fuzzy-everything-wolf, wb/smellthis ]
branches: [ main ]
paths-ignore:
- 'doc/**'
- '**.md'
@@ -88,15 +88,6 @@ jobs:
git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main
git -C InfiniSim submodule update --init lv_drivers
- name: Add sunset lib to InfiniSim
run: patch -i docker/infinisim-cmake.patch InfiniSim/CMakeLists.txt
- name: Remove Terminal from InfiniSim
run: patch -i docker/infinisim-terminal.patch InfiniSim/littlefs-do-main.cpp
- name: Remove Digital from InfiniSim
run: patch -i docker/infinisim-main.patch InfiniSim/main.cpp
- name: CMake
# disable BUILD_RESOURCES as this is already done when building the firmware
run: |

2
.gitignore vendored
View File

@@ -50,3 +50,5 @@ src/arm-none-eabi
# clangd
.cache/
nRF5_SDK/

View File

@@ -1,4 +1,9 @@
{
"env": {
// TODO: This is a duplication of the configuration set in /docker/build.sh!
"TOOLS_DIR": "/opt",
"GCC_ARM_PATH": "gcc-arm-none-eabi-10.3-2021.10"
},
"configurations": [
{
"name": "nrfCC",
@@ -14,6 +19,21 @@
"intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cpp-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
},
{
"name": "nrfCC Devcontainer",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/src/**",
"${workspaceFolder}/src"
],
"defines": [],
"compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc",
"cStandard": "c99",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm",
"configurationProvider": "ms-vscode.cpp-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4

6
.vscode/cmake-kits.json vendored Normal file
View File

@@ -0,0 +1,6 @@
[
{
"name": "InfiniTime Compiler",
"environmentSetupScript": "${workspaceFolder}/docker/build.sh"
}
]

45
.vscode/launch.json vendored
View File

@@ -1,20 +1,18 @@
{
{
"version": "0.1.0",
"configurations": [
{
"name": "Debug - Openocd docker Remote",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "external",
// This may need to be arm-none-eabi-gdb depending on your system
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "host.docker.internal:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true,
"runToEntryPoint": "main",
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
@@ -23,18 +21,16 @@
},
{
"name": "Debug - Openocd Local",
"type":"cortex-debug",
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "openocd",
// This may need to be arm-none-eabi-gdb depending on your system
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "localhost:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToMain": true,
"runToEntryPoint": "main",
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
@@ -51,6 +47,11 @@
"showDevDebugOutput": false,
"servertype": "openocd",
"runToMain": true,
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
],
// Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb)
"armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
"svdFile": "${workspaceRoot}/nrf52.svd",
@@ -58,7 +59,25 @@
"interface/stlink.cfg",
"target/nrf52.cfg"
],
}
},
{
"name": "Debug - Openocd Devcontainer",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "${command:cmake.launchTargetPath}",
"request": "launch",
"servertype": "external",
// FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here
"gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb",
// Connect to an already running OpenOCD instance
"gdbTarget": "host.docker.internal:3333",
"svdFile": "${workspaceRoot}/nrf52.svd",
"runToEntryPoint": "main",
// Work around for stopping at main on restart
"postRestartCommands": [
"break main",
"continue"
]
},
]
}

15
.vscode/settings.json vendored
View File

@@ -1,9 +1,20 @@
{
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"cmake.configureArgs": [
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
"-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}",
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
"-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}",
],
"cmake.statusbar.advanced": {
"launch": {
"visibility": "hidden"
},
"launchTarget": {
"visibility": "hidden"
},
"debug": {
"visibility": "hidden"
}
},
"cmake.generator": "Unix Makefiles",
"clang-tidy.buildPath": "build/compile_commands.json",
"files.associations": {

22
.vscode/tasks.json vendored
View File

@@ -1,20 +1,6 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "create openocd build",
"type": "shell",
"command": "/opt/create_build_openocd.sh",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "update submodules",
"type": "shell",
@@ -31,14 +17,6 @@
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "BuildInit",
"dependsOn": [
"update submodules",
"create openocd build"
],
"problemMatcher": []
}
]
}

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
project(pinetime VERSION 1.14.0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 20)

View File

@@ -2,7 +2,7 @@
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.
## New to InfiniTime?

View File

@@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to
python3. It is sparsely tested so there are likely to be a few
remaining bytes versus string bugs remaining in the places I didn't test
. I used it primarily as part of
[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to
[wasp-os](https://github.com/wasp-os/wasp-os) as a way to
deliver OTA updates to nRF52-based smart watches, especially the
[Pine64 PineTime](https://www.pine64.org/pinetime/).
[Pine64 PineTime](https://pine64.org/devices/pinetime/).
## What does it do?

View File

@@ -21,3 +21,5 @@ The current raw motion values. This is a 3 `int16_t` array:
- [0] : X
- [1] : Y
- [2] : Z
The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024.

View File

@@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock
Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md)
More documentation is available in the [readme in .devcontainer](usingDevcontainers.md)
### DevContainer on Ubuntu

View File

@@ -35,18 +35,20 @@ that will call the method `Refresh()` periodically.
## App types
There are basically 2 types of applications : **system** apps and **user** apps.
There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**.
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
The watchfaces, settings, notifications and the application launcher are examples of such system applications.
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
**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
The distinction between **system** apps, **user** apps and watch faces allows for more flexibility and customization.
This allows to easily select which user applications and watch faces must be built into the firmware
without overflowing the system memory.
## Apps initialization
## Apps and watch faces initialization
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
@@ -55,6 +57,8 @@ The constructor of **system** apps is called directly. If the application is a *
the corresponding `AppDescription` is first retrieved from `userApps`
and then the function `create` is called to create an instance of the app.
Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`.
## User application selection at build time
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
@@ -85,6 +89,32 @@ struct AppTraits<Apps::Alarm> {
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
to list all available applications.
## Watch face selection at build time
The list of available watch faces is also generated at build time by the `consteval`
function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps.
Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
Here is an example of `WatchFaceTraits`:
```c++
template <>
struct WatchFaceTraits<WatchFace::Analog> {
static constexpr WatchFace watchFace = WatchFace::Analog;
static constexpr const char* name = "Analog face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceAnalog(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
```
## Creating your own app
A minimal user app could look like this:
@@ -110,10 +140,10 @@ namespace Pinetime {
}
template <>
struct AppTraits<Apps:MyApp> {
struct AppTraits<Apps::MyApp> {
static constexpr Apps app = Apps::MyApp;
static constexpr const char* icon = Screens::Symbol::myApp;
static Screens::Screens* Create(AppController& controllers) {
static constexpr const char* icon = Screens::Symbols::myApp;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::MyApp();
}
};
@@ -146,7 +176,7 @@ Now we have our very own app, but InfiniTime does not know about it yet.
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
The next step to making it launch-able is to give your app an id.
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)).
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
@@ -168,6 +198,15 @@ Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the ap
$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ...
```
Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake.
It must be set with the comma separated list of watch faces that will be built into the firmware.
Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat:
```cmake
$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ...
```
You should now be able to [build](../buildAndProgram.md) the firmware
and flash it to your PineTime. Yay!

View File

@@ -37,6 +37,13 @@ RUN apt-get update -qq \
libpangocairo-1.0-0 \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
# Add the necessary apt-gets for the devcontainer
RUN apt-get update -qq \
&& apt-get install -y \
clang-format-14 \
clang-tidy \
libncurses5
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
RUN pip3 install adafruit-nrfutil
@@ -55,5 +62,8 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
# McuBoot
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
# Add the infinitime user for connecting devcontainer
RUN adduser infinitime
ENV SOURCES_DIR /sources
CMD ["/opt/build.sh"]

View File

@@ -1,19 +0,0 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index edd6748..641b74a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -243,6 +243,14 @@ add_library(littlefs STATIC
target_include_directories(littlefs PUBLIC "${InfiniTime_DIR}/src/libs/littlefs")
target_link_libraries(infinisim PRIVATE littlefs)
+# sunset
+add_library(sunset STATIC
+ ${InfiniTime_DIR}/src/libs/sunset/src/sunset.h
+ ${InfiniTime_DIR}/src/libs/sunset/src/sunset.cpp
+)
+target_include_directories(sunset PUBLIC "${InfiniTime_DIR}/src/libs/sunset")
+target_link_libraries(infinisim PRIVATE sunset)
+
# QCBOR
add_library(QCBOR STATIC
${InfiniTime_DIR}/src/libs/QCBOR/src/ieee754.c

View File

@@ -1,20 +0,0 @@
diff --git a/main.cpp b/main.cpp
index 8070db7..530ff4b 100644
--- a/main.cpp
+++ b/main.cpp
@@ -823,14 +823,10 @@ public:
void switch_to_screen(uint8_t screen_idx)
{
if (screen_idx == 1) {
- settingsController.SetWatchFace(Pinetime::Applications::WatchFace::Digital);
- displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
- }
- else if (screen_idx == 2) {
settingsController.SetWatchFace(Pinetime::Applications::WatchFace::Analog);
displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
- else if (screen_idx == 3) {
+ else if (screen_idx == 2) {
settingsController.SetWatchFace(Pinetime::Applications::WatchFace::PineTimeStyle);
displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}

View File

@@ -1,17 +0,0 @@
diff --git a/littlefs-do-main.cpp b/littlefs-do-main.cpp
index 0a5dfbd..3e818af 100644
--- a/littlefs-do-main.cpp
+++ b/littlefs-do-main.cpp
@@ -537,10 +537,10 @@ int command_settings(const std::string &program_name, const std::vector<std::str
{
auto clockface = settingsController.GetWatchFace();
auto clockface_str = [](auto val) {
- if (val == Pinetime::Applications::WatchFace::Digital) return "Digital";
+ //if (val == Pinetime::Applications::WatchFace::Digital) return "Digital";
if (val == Pinetime::Applications::WatchFace::Analog) return "Analog";
if (val == Pinetime::Applications::WatchFace::PineTimeStyle) return "PineTimeStyle";
- if (val == Pinetime::Applications::WatchFace::Terminal) return "Terminal";
+ //if (val == Pinetime::Applications::WatchFace::Terminal) return "Terminal";
return "unknown";
}(clockface);
std::cout << "ClockFace: " << static_cast<uint32_t>(clockface) << " " << clockface_str << std::endl;

View File

@@ -1,25 +1,33 @@
#!/bin/bash
if clang-format --version | grep -q 'version 11\.'; then
CLANG_FORMAT_EXECUTABLE="clang-format"
else
CLANG_FORMAT_EXECUTABLE="clang-format-11"
#!/bin/sh
name="clang-format"
if [ -z "$(command -v "git-$name")" ]; then
name="$(basename -a $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'git-clang-format*') | sort | tail -n 1 | sed 's/^git-//')"
fi
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
then
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
exit 1
fi
minVersion="14.0.0"
for FILE in $(git diff --cached --name-only)
do
if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
git add -- $FILE
elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
git add -- $FILE
fi
for file in $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'clang-format*'); do
curBin="$file"
curVersion="$("$curBin" --version | cut -d ' ' -f 3)"
if [ "$(printf '%s\n' "$curVersion" "$version" "$minVersion" | sort -V | tail -n 1)" = "$curVersion" ]; then
bin="$curBin"
version="$curVersion"
fi
done
if [ -z "$name" ] || [ -z "$bin" ]; then
echo "Could not find a suitable clang-format installation. Install clang-format that includes the git-clang-format script, with at least version $minVersion"
exit 1
fi
args="--binary $bin -q --extensions cpp,h --style file --staged -- :!src/FreeRTOS :!src/libs"
changedFiles="$(git "$name" --diffstat $args)"
git "$name" $args
echo "$changedFiles" | head -n -1 | cut -d ' ' -f 2 | while read -r file; do
git add -- "$file"
done

50
shell.nix Normal file
View File

@@ -0,0 +1,50 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs; let
py4McuBoot = python3.withPackages (ps: with ps; [
cbor
intelhex
click
cryptography
pillow
]);
lv_img_convWrapper = pkgs.writeScriptBin "lv_img_conv" ''
npm install lv_img_conv
nodejs node_modules/lv_img_conv/lv_img_conv.js
'';
buildInfinitime = pkgs.writeScriptBin "build-infinitime" ''
mkdir -p build/
cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=$ARM_NONE_EABI_TOOLCHAIN_PATH \
-DNRF5_SDK_PATH=$NRF5_SDK_PATH \
-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \
-DBUILD_DFU=$BUILD_DFU \
-DBUILD_RESOURCES=$BUILD_RESOURCES \
-DTARGET_DEVICE=$TARGET_DEVICE \
-S . -B build
cmake --build build -j6
'';
in mkShell {
packages = [
gcc-arm-embedded-10
nrf5-sdk
cmake
nodePackages.lv_font_conv
lv_img_convWrapper
# lv_img_conv
nodejs
py4McuBoot
clang-tools
SDL2
libpng
adafruit-nrfutil
buildInfinitime
# watchmate # wish this worked -- use flatpak run io.gitlab.azymohliad.WatchMate
];
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";
}

View File

@@ -371,8 +371,6 @@ list(APPEND SOURCE_FILES
displayapp/DisplayApp.cpp
displayapp/screens/Screen.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
@@ -381,13 +379,9 @@ list(APPEND SOURCE_FILES
displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/Music.cpp
displayapp/screens/Navigation.cpp
displayapp/screens/Metronome.cpp
displayapp/screens/Motion.cpp
displayapp/screens/FirmwareValidation.cpp
displayapp/screens/ApplicationList.cpp
displayapp/screens/Notifications.cpp
displayapp/screens/Twos.cpp
displayapp/screens/HeartRate.cpp
displayapp/screens/FlashLight.cpp
displayapp/screens/List.cpp
@@ -395,10 +389,12 @@ list(APPEND SOURCE_FILES
displayapp/screens/BatteryInfo.cpp
displayapp/screens/Steps.cpp
displayapp/screens/Timer.cpp
displayapp/screens/Dice.cpp
displayapp/screens/PassKey.cpp
displayapp/screens/Error.cpp
displayapp/screens/Alarm.cpp
displayapp/screens/Styles.cpp
displayapp/screens/WeatherSymbols.cpp
displayapp/Colors.cpp
displayapp/widgets/Counter.cpp
displayapp/widgets/PageIndicator.cpp
@@ -417,18 +413,15 @@ list(APPEND SOURCE_FILES
displayapp/screens/settings/SettingSetDateTime.cpp
displayapp/screens/settings/SettingSetDate.cpp
displayapp/screens/settings/SettingSetTime.cpp
displayapp/screens/settings/SettingLocation.cpp
displayapp/screens/settings/SettingChimes.cpp
displayapp/screens/settings/SettingShakeThreshold.cpp
displayapp/screens/settings/SettingBluetooth.cpp
displayapp/screens/settings/SettingLocation.cpp
## Watch faces
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/WatchFaceDigital.cpp
displayapp/screens/WatchFaceFuzzy.cpp
displayapp/screens/WatchFaceSundial.cpp
##
@@ -546,7 +539,6 @@ list(APPEND RECOVERY_SOURCE_FILES
systemtask/SystemTask.cpp
systemtask/SystemMonitor.cpp
drivers/TwiMaster.cpp
components/gfx/Gfx.cpp
components/rle/RleDecoder.cpp
components/heartrate/HeartRateController.cpp
heartratetask/HeartRateTask.cpp
@@ -576,7 +568,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
components/rle/RleDecoder.cpp
components/gfx/Gfx.cpp
drivers/St7789.cpp
components/brightness/BrightnessController.cpp
@@ -595,9 +586,7 @@ set(INCLUDE_FILES
displayapp/TouchEvents.h
displayapp/screens/Screen.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
@@ -611,10 +600,7 @@ set(INCLUDE_FILES
displayapp/Apps.h
displayapp/screens/Notifications.h
displayapp/screens/HeartRate.h
displayapp/screens/Metronome.h
displayapp/screens/Motion.h
displayapp/screens/Timer.h
displayapp/screens/Alarm.h
displayapp/Colors.h
displayapp/widgets/Counter.h
displayapp/widgets/PageIndicator.h
@@ -778,7 +764,7 @@ link_directories(
set(COMMON_FLAGS -MP -MD -mthumb -mabi=aapcs -ftree-vrp -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin -fshort-enums -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -fstack-usage -fno-exceptions -fno-non-call-exceptions)
set(WARNING_FLAGS -Wall -Wextra -Warray-bounds=2 -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=2 -Wformat-nonliteral -Wno-missing-field-initializers -Wno-unknown-pragmas -Wno-expansion-to-defined -Wreturn-type -Werror=return-type -Werror)
set(DEBUG_FLAGS -Og -g3)
set(DEBUG_FLAGS -Os)
set(RELEASE_FLAGS -Os)
set(CXX_FLAGS -fno-rtti)
set(ASM_FLAGS -x assembler-with-cpp)
add_definitions(-DCONFIG_GPIO_AS_PINRESET)
@@ -847,7 +833,7 @@ add_subdirectory(displayapp/fonts)
target_compile_options(infinitime_fonts PUBLIC
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -861,7 +847,7 @@ target_include_directories(nrf-sdk SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(nrf-sdk PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
-O3
@@ -874,7 +860,7 @@ target_include_directories(nimble SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(nimble PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -886,7 +872,7 @@ target_include_directories(lvgl SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(lvgl PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -898,7 +884,7 @@ target_include_directories(littlefs SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(littlefs PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -910,7 +896,7 @@ target_include_directories(sunset SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
target_compile_options(sunset PRIVATE
${COMMON_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -926,7 +912,7 @@ target_compile_options(${EXECUTABLE_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -961,7 +947,7 @@ target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -1004,7 +990,7 @@ target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -1036,7 +1022,7 @@ target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -1075,7 +1061,7 @@ target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)
@@ -1110,7 +1096,7 @@ target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
${COMMON_FLAGS}
${WARNING_FLAGS}
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${DEBUG_FLAGS}>
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
)

View File

@@ -62,7 +62,8 @@
#define configTICK_RATE_HZ 1024
#define configMAX_PRIORITIES (3)
#define configMINIMAL_STACK_SIZE (120)
#define configTOTAL_HEAP_SIZE (1024 * 40)
// how much heap can one smartwatch need, michael, 40kb?
#define configTOTAL_HEAP_SIZE (1024 * 39)
#define configMAX_TASK_NAME_LEN (4)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
@@ -75,6 +76,7 @@
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1
#define configUSE_TASK_NOTIFICATIONS 0
/* Hook function related definitions. */
#define configUSE_IDLE_HOOK 0

View File

@@ -357,6 +357,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
this->totalSize = totalSize;
this->expectedCrc = expectedCrc;
this->ready = true;
totalWriteIndex = 0;
bufferWriteIndex = 0;
}
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {

View File

@@ -2,9 +2,9 @@
#define min // workaround: nimble's min/max macros conflict with libstdc++
#define max
#include <host/ble_gap.h>
#include <atomic>
#undef max
#undef min
#include <atomic>
namespace Pinetime {
namespace Controllers {

View File

@@ -120,3 +120,7 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
else if (attributeHandle == motionValuesHandle)
motionValuesNoficationEnabled = false;
}
bool MotionService::IsMotionNotificationSubscribed() const {
return motionValuesNoficationEnabled;
}

View File

@@ -21,6 +21,7 @@ namespace Pinetime {
void SubscribeNotification(uint16_t attributeHandle);
void UnsubscribeNotification(uint16_t attributeHandle);
bool IsMotionNotificationSubscribed() const;
private:
NimbleController& nimble;

View File

@@ -158,3 +158,16 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
std::strcmp(this->location.data(), other.location.data()) == 0;
}
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
}
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
for (int i = 0; i < this->nbDays; i++) {
if (this->days[i] != other.days[i]) {
return false;
}
}
return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
}

View File

@@ -96,9 +96,13 @@ namespace Pinetime {
int16_t minTemperature;
int16_t maxTemperature;
Icons iconId;
bool operator==(const Day& other) const;
};
std::array<Day, MaxNbForecastDays> days;
bool operator==(const Forecast& other) const;
};
std::optional<CurrentWeather> Current() const;

View File

@@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
return MonthsStringLow[static_cast<uint8_t>(month)];
}
const char* DateTime::DayOfWeekShortToStringLow() const {
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
return DaysStringShortLow[static_cast<uint8_t>(day)];
}
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
@@ -129,7 +129,7 @@ std::string DateTime::FormattedTime() {
auto hour = Hours();
auto minute = Minutes();
// Return time as a string in 12- or 24-hour format
char buff[9];
char buff[11];
if (settingsController.GetClockType() == ClockType::H12) {
uint8_t hour12;
const char* amPmStr;

View File

@@ -122,7 +122,7 @@ namespace Pinetime {
const char* MonthShortToString() const;
const char* DayOfWeekShortToString() const;
static const char* MonthShortToStringLow(Months month);
const char* DayOfWeekShortToStringLow() const;
static const char* DayOfWeekShortToStringLow(Days day);
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
return currentDateTime;

View File

@@ -1,196 +0,0 @@
#include "components/gfx/Gfx.h"
#include "drivers/St7789.h"
using namespace Pinetime::Components;
Gfx::Gfx(Pinetime::Drivers::St7789& lcd) : lcd {lcd} {
}
void Gfx::Init() {
}
void Gfx::ClearScreen() {
SetBackgroundColor(0x0000);
state.remainingIterations = 240 + 1;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(0, 0, width, height, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
SetBackgroundColor(color);
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(buffer), width * 2);
WaitTransferFinished();
}
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = 0x00;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(x, y, w, h, reinterpret_cast<const uint8_t*>(b), width * 2);
WaitTransferFinished();
}
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap) {
if (y > (height - p_font->height)) {
// Not enough space to write even single char.
return;
}
uint8_t current_x = x;
uint8_t current_y = y;
for (size_t i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
DrawChar(p_font, (uint8_t) text[i], &current_x, current_y, color);
}
uint8_t char_idx = text[i] - p_font->startChar;
uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
if (current_x > (width - char_width)) {
if (wrap) {
current_x = x;
current_y += p_font->height + p_font->height / 10;
} else {
break;
}
if (y > (height - p_font->height)) {
break;
}
}
}
}
void Gfx::DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color) {
uint8_t char_idx = c - font->startChar;
uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
uint16_t bg = 0x0000;
if (c == ' ') {
*x += font->height / 2;
return;
}
// Build first line
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
buffer[(j * 8) + k] = color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
state.remainingIterations = font->height + 0;
state.currentIteration = 0;
state.busy = true;
state.action = Action::DrawChar;
state.font = const_cast<FONT_INFO*>(font);
state.character = c;
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.DrawBuffer(*x, y, bytes_in_line * 8, font->height, reinterpret_cast<const uint8_t*>(&buffer), bytes_in_line * 8 * 2);
WaitTransferFinished();
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
}
void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
lcd.DrawPixel(x, y, color);
}
void Gfx::Sleep() {
lcd.Sleep();
}
void Gfx::Wakeup() {
lcd.Wakeup();
}
void Gfx::SetBackgroundColor(uint16_t color) {
for (int i = 0; i < width; i++) {
buffer[i] = color;
}
}
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
if (!state.busy)
return false;
state.remainingIterations = state.remainingIterations - 1;
if (state.remainingIterations == 0) {
state.busy = false;
NotifyEndOfTransfer(state.taskToNotify);
return false;
}
if (state.action == Action::FillRectangle) {
*data = reinterpret_cast<uint8_t*>(buffer);
size = width * 2;
} else if (state.action == Action::DrawChar) {
uint16_t bg = 0x0000;
uint8_t char_idx = state.character - state.font->startChar;
uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
for (uint16_t j = 0; j < bytes_in_line; j++) {
for (uint8_t k = 0; k < 8; k++) {
if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration + 1) * bytes_in_line) + j]) {
buffer[(j * 8) + k] = state.color;
} else {
buffer[(j * 8) + k] = bg;
}
}
}
*data = reinterpret_cast<uint8_t*>(buffer);
size = bytes_in_line * 8 * 2;
}
state.currentIteration = state.currentIteration + 1;
return true;
}
void Gfx::NotifyEndOfTransfer(TaskHandle_t task) {
if (task != nullptr) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
void Gfx::WaitTransferFinished() const {
ulTaskNotifyTake(pdTRUE, 500);
}
void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
}
void Gfx::SetScrollStartLine(uint16_t line) {
lcd.VerticalScrollStartAddress(line);
}

View File

@@ -1,62 +0,0 @@
#pragma once
#include <FreeRTOS.h>
#include <nrf_font.h>
#include <task.h>
#include <cstddef>
#include <cstdint>
#include "drivers/BufferProvider.h"
namespace Pinetime {
namespace Drivers {
class St7789;
}
namespace Components {
class Gfx : public Pinetime::Drivers::BufferProvider {
public:
explicit Gfx(Drivers::St7789& lcd);
void Init();
void ClearScreen();
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO* p_font, bool wrap);
void DrawChar(const FONT_INFO* font, uint8_t c, uint8_t* x, uint8_t y, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void SetScrollStartLine(uint16_t line);
void Sleep();
void Wakeup();
bool GetNextBuffer(uint8_t** buffer, size_t& size) override;
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
private:
static constexpr uint8_t width = 240;
static constexpr uint8_t height = 240;
enum class Action { None, FillRectangle, DrawChar };
struct State {
State() : busy {false}, action {Action::None}, remainingIterations {0}, currentIteration {0} {
}
volatile bool busy;
volatile Action action;
volatile uint16_t remainingIterations;
volatile uint16_t currentIteration;
volatile FONT_INFO* font;
volatile uint16_t color;
volatile uint8_t character;
volatile TaskHandle_t taskToNotify = nullptr;
};
volatile State state;
uint16_t buffer[width]; // 1 line buffer
Drivers::St7789& lcd;
void SetBackgroundColor(uint16_t color);
void WaitTransferFinished() const;
void NotifyEndOfTransfer(TaskHandle_t task);
};
}
}

View File

@@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
service->OnNewStepCountValue(nbSteps);
}
if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
service->OnNewMotionValues(x, y, z);
}
lastTime = time;
time = xTaskGetTickCount();
lastX = this->x;
this->x = x;
xHistory++;
xHistory[0] = x;
yHistory++;
yHistory[0] = y;
zHistory++;
@@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
AccelStats stats;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xMean += xHistory[histSize - i];
stats.yMean += yHistory[histSize - i];
stats.zMean += zHistory[histSize - i];
stats.prevXMean += xHistory[1 + i];
stats.prevYMean += yHistory[1 + i];
stats.prevZMean += zHistory[1 + i];
}
stats.xMean /= AccelStats::numHistory;
stats.yMean /= AccelStats::numHistory;
stats.zMean /= AccelStats::numHistory;
stats.prevXMean /= AccelStats::numHistory;
stats.prevYMean /= AccelStats::numHistory;
stats.prevZMean /= AccelStats::numHistory;
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean);
stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
}
stats.xVariance /= AccelStats::numHistory;
stats.yVariance /= AccelStats::numHistory;
stats.zVariance /= AccelStats::numHistory;
@@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const {
constexpr int16_t yThresh = -64;
constexpr int16_t rollDegreesThresh = -45;
if (x < -xThresh || x > xThresh) {
if (std::abs(stats.xMean) > xThresh) {
return false;
}
@@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const {
bool MotionController::ShouldShakeWake(uint16_t thresh) {
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
int32_t speed =
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
(xHistory[0] - xHistory[histSize - 1]) / 4) *
100 / (time - lastTime);
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
@@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
}
bool MotionController::ShouldLowerSleep() const {
if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) ||
(stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) {
return true;
}
if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
return false;
}

View File

@@ -21,7 +21,7 @@ namespace Pinetime {
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
int16_t X() const {
return x;
return xHistory[0];
}
int16_t Y() const {
@@ -62,6 +62,10 @@ namespace Pinetime {
this->service = service;
}
Pinetime::Controllers::MotionService* GetService() const {
return service;
}
private:
uint32_t nbSteps = 0;
uint32_t currentTripSteps = 0;
@@ -72,11 +76,14 @@ namespace Pinetime {
struct AccelStats {
static constexpr uint8_t numHistory = 2;
int16_t xMean = 0;
int16_t yMean = 0;
int16_t zMean = 0;
int16_t prevXMean = 0;
int16_t prevYMean = 0;
int16_t prevZMean = 0;
uint32_t xVariance = 0;
uint32_t yVariance = 0;
uint32_t zVariance = 0;
};
@@ -85,9 +92,8 @@ namespace Pinetime {
AccelStats stats = {};
int16_t lastX = 0;
int16_t x = 0;
static constexpr uint8_t histSize = 8;
Utility::CircularBuffer<int16_t, histSize> xHistory = {};
Utility::CircularBuffer<int16_t, histSize> yHistory = {};
Utility::CircularBuffer<int16_t, histSize> zHistory = {};
int32_t accumulatedSpeed = 0;

View File

@@ -9,7 +9,7 @@ namespace Pinetime {
namespace Controllers {
class Settings {
public:
enum class ClockType : uint8_t { H24, H12, Fuzzy };
enum class ClockType : uint8_t { H24, H12, FUZZY };
enum class WeatherFormat : uint8_t { Metric, Imperial };
enum class Notification : uint8_t { On, Off, Sleep };
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
@@ -283,10 +283,10 @@ namespace Pinetime {
void SetLocation(Location loc) {
if (
loc.latitude != settings.location.latitude ||
loc.longitude != settings.location.longitude ||
loc.tzOffset != settings.location.tzOffset
) {
loc.latitude != settings.location.latitude ||
loc.longitude != settings.location.longitude ||
loc.tzOffset != settings.location.tzOffset
) {
settingsChanged = true;
}
settings.location = loc;
@@ -318,7 +318,7 @@ namespace Pinetime {
WeatherFormat weatherFormat = WeatherFormat::Metric;
Notification notificationStatus = Notification::On;
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Analog; //Digital
Pinetime::Applications::WatchFace watchFace = Pinetime::Applications::WatchFace::Digital;
ChimesOption chimesOption = ChimesOption::None;
PineTimeStyle PTS;
@@ -329,7 +329,8 @@ namespace Pinetime {
uint16_t shakeWakeThreshold = 150;
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
Location location = {(int16_t)0,(int16_t)0,(int8_t)0};
Location location = {(int16_t)44,(int16_t)-123,(int8_t)-8};
};
SettingsData settings;

View File

@@ -26,6 +26,8 @@
#include "displayapp/screens/FlashLight.h"
#include "displayapp/screens/BatteryInfo.h"
#include "displayapp/screens/Steps.h"
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Weather.h"
#include "displayapp/screens/PassKey.h"
#include "displayapp/screens/Error.h"
@@ -44,10 +46,10 @@
#include "displayapp/screens/settings/SettingDisplay.h"
#include "displayapp/screens/settings/SettingSteps.h"
#include "displayapp/screens/settings/SettingSetDateTime.h"
#include "displayapp/screens/settings/SettingLocation.h"
#include "displayapp/screens/settings/SettingChimes.h"
#include "displayapp/screens/settings/SettingShakeThreshold.h"
#include "displayapp/screens/settings/SettingBluetooth.h"
#include "displayapp/screens/settings/SettingLocation.h"
#include "libs/lv_conf.h"
#include "UserApps.h"
@@ -124,6 +126,7 @@ void DisplayApp::Start(System::BootErrors error) {
bootError = error;
lvgl.Init();
motorController.Init();
if (error == System::BootErrors::TouchController) {
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
@@ -141,9 +144,6 @@ void DisplayApp::Process(void* instance) {
NRF_LOG_INFO("displayapp task started!");
app->InitHw();
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
while (true) {
app->Refresh();
}
@@ -152,7 +152,6 @@ void DisplayApp::Process(void* instance) {
void DisplayApp::InitHw() {
brightnessController.Init();
ApplyBrightness();
motorController.Init();
lcd.Init();
}
@@ -232,11 +231,6 @@ void DisplayApp::Refresh() {
Messages msg;
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
// let's do otaku chimes
uint8_t myHour = dateTimeController.Hours();
uint8_t myChimes = 9-(((int)floor(myHour/2))%6);
switch (msg) {
case Messages::DimScreen:
DimScreen();
@@ -382,39 +376,7 @@ void DisplayApp::Refresh() {
break;
case Messages::Chime:
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
// hour chime
// 0 9
// 1 9
// 2 8
// 3 8
// 4 7
// 5 7
// 6 6
// 7 6
// 8 5
// 9 5
// 10 4
// 11 4
// 12 9
// 13 9
// 14 8
// 15 8
// 16 7
// 17 7
// 18 6
// 19 6
// 20 5
// 21 5
// 22 4
// 23 4
// NRF_LOG_INFO("buzzing %d times", myChimes);
for (uint8_t i=0; i<myChimes; i++){
// NRF_LOG_INFO("buzz!");
motorController.RunForDuration(35);
vTaskDelay(800);
}
motorController.RunForDuration(35);
break;
case Messages::OnChargingEvent:
RestoreBrightness();
@@ -482,6 +444,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
else {
currentScreen.reset(userWatchFaces[0].create(controllers));
}
settingsController.SetAppMenu(0);
} break;
case Apps::Error:
currentScreen = std::make_unique<Screens::Error>(bootError);
@@ -527,10 +490,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
break;
case Apps::SettingWatchFace: {
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
int i = 0;
for (const auto& userWatchFace : userWatchFaces) {
items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
items[i++] =
Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
}
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
} break;
@@ -599,9 +563,7 @@ void DisplayApp::PushMessage(Messages msg) {
if (in_isr()) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
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

View File

@@ -38,9 +38,6 @@ void DisplayApp::Process(void* instance) {
auto* app = static_cast<DisplayApp*>(instance);
NRF_LOG_INFO("displayapp task started!");
// Send a dummy notification to unlock the lvgl display driver for the first iteration
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
app->InitHw();
while (true) {
app->Refresh();
@@ -94,7 +91,6 @@ void DisplayApp::DisplayLogo(uint16_t color) {
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
for (int i = 0; i < displayWidth; i++) {
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
ulTaskNotifyTake(pdTRUE, 500);
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
}
}
@@ -103,20 +99,15 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
const uint8_t barHeight = 20;
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
for (int i = 0; i < barHeight; i++) {
ulTaskNotifyTake(pdTRUE, 500);
uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
}
}
void DisplayApp::PushMessage(Display::Messages msg) {
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */
// TODO : should I do something here?
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {

View File

@@ -5,7 +5,6 @@
#include <drivers/SpiMaster.h>
#include <bits/unique_ptr.h>
#include <queue.h>
#include "components/gfx/Gfx.h"
#include "drivers/Cst816s.h"
#include <drivers/Watchdog.h>
#include <components/motor/MotorController.h>

View File

@@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
uint16_t y1, y2, width, height = 0;
ulTaskNotifyTake(pdTRUE, 200);
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
// which cannot be set/clear during a transfer.
if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
@@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
if (height > 0) {
lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
ulTaskNotifyTake(pdTRUE, 100);
}
uint16_t pixOffset = width * height;

View File

@@ -3,16 +3,19 @@
#include "Controllers.h"
#include "displayapp/screens/Alarm.h"
#include "displayapp/screens/Dice.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/WatchFaceDigital.h"
#include "displayapp/screens/WatchFaceAnalog.h"
// #include "displayapp/screens/WatchFaceCasioStyleG7710.h"
// #include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include "displayapp/screens/WatchFaceInfineat.h"
#include "displayapp/screens/WatchFacePineTimeStyle.h"
// #include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/WatchFaceFuzzy.h"
#include "displayapp/screens/WatchFaceSundial.h"
namespace Pinetime {
namespace Applications {

View File

@@ -27,6 +27,8 @@ namespace Pinetime {
Metronome,
Motion,
Steps,
Dice,
Weather,
PassKey,
QuickSettings,
Settings,
@@ -41,13 +43,18 @@ namespace Pinetime {
SettingChimes,
SettingShakeThreshold,
SettingBluetooth,
Error,
Weather
Error
};
enum class WatchFace : uint8_t {
Digital,
Analog,
PineTimeStyle,
Terminal,
Fuzzy,
Sundial,
Infineat,
CasioStyleG7710,
};
template <Apps>
@@ -68,8 +75,7 @@ namespace Pinetime {
static constexpr size_t Count = sizeof...(Ws);
};
using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Analog,
WatchFace::PineTimeStyle>;
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
static_assert(UserWatchFaceTypes::Count >= 1);
}

View File

@@ -1,9 +1,23 @@
#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::Timer, Apps::Alarm, 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 ()
if(DEFINED ENABLE_USERAPPS)
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
else ()
set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music")
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
endif ()
if(DEFINED ENABLE_WATCHFACES)
set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
else()
set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Fuzzy")
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Sundial")
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
endif()
add_library(infinitime_apps INTERFACE)
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")

View File

@@ -1,4 +1,4 @@
set(FONTS jetbrains_mono_42 noto_serif_cjk_20 noto_serif_cjk_15 jetbrains_mono_76 jetbrains_mono_bold_20
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20 jetbrains_mono_bold_24
jetbrains_mono_extrabold_compressed lv_font_sys_48
open_sans_light fontawesome_weathericons)
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED

View File

@@ -16,7 +16,7 @@
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
```
static constexpr const char* newSymbol = "\xEF\x86\x85";
static constexpr const char* newSymbol = "\xEF\x99\x81";
```
### the config file format:

View File

@@ -2,54 +2,46 @@
"jetbrains_mono_bold_20": {
"sources": [
{
"file": "JetBrainsMono-Bold.ttf",
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
"file": "Vulf_Mono-Italic.woff",
"range": "0x20-0x7e, 0xB0"
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
}
],
"bpp": 1,
"size": 20,
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
},
"noto_serif_cjk_20": {
"sources": [
{
"file": "NotoSerifCJKjp-VF.ttf",
"symbols": "丑寅卯辰巳午未申酉戌亥子四五六七八九"
"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, 0xf0f3, 0xf522, 0xf743"
}
],
"bpp": 1,
"size": 20
},
"noto_serif_cjk_15": {
"jetbrains_mono_bold_24": {
"sources": [
{
"file": "NotoSerifCJKjp-VF.ttf",
"symbols": "四五六七八九"
"file": "Vulf_Mono-Italic.woff",
"range": "0x20-0x7e, 0xB0"
},
{
"file": "FontAwesome5-Solid+Brands+Regular.woff",
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
}
],
"bpp": 1,
"size": 15
"size": 26
},
"jetbrains_mono_42": {
"sources": [
{
"file": "JetBrainsMono-Regular.ttf",
"disabledfile": "Vulf Mono Light Italic.ttf",
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
"file": "Vulf Mono Light Italic.ttf",
"range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
}
],
"bpp": 1,
"size": 30
"size": 42
},
"jetbrains_mono_76": {
"sources": [
{
"file": "JetBrainsMono-Light.ttf",
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
}
],
"bpp": 1,

View File

@@ -69,7 +69,7 @@ namespace Pinetime {
template <>
struct AppTraits<Apps::Alarm> {
static constexpr Apps app = Apps::Alarm;
static constexpr const char* icon = Screens::Symbols::clock;
static constexpr const char* icon = Screens::Symbols::bell;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Alarm(controllers.alarmController,

View File

@@ -0,0 +1,199 @@
#include "displayapp/screens/Dice.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h"
#include "components/settings/Settings.h"
#include "components/motor/MotorController.h"
#include "components/motion/MotionController.h"
using namespace Pinetime::Applications::Screens;
namespace {
lv_obj_t* MakeLabel(lv_font_t* font,
lv_color_t color,
lv_label_long_mode_t longMode,
uint8_t width,
lv_label_align_t labelAlignment,
const char* text,
lv_obj_t* reference,
lv_align_t alignment,
int8_t x,
int8_t y) {
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
lv_label_set_long_mode(label, longMode);
if (width != 0) {
lv_obj_set_width(label, width);
}
lv_label_set_align(label, labelAlignment);
lv_label_set_text(label, text);
lv_obj_align(label, reference, alignment, x, y);
return label;
}
void btnRollEventHandler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<Dice*>(obj->user_data);
if (event == LV_EVENT_CLICKED) {
screen->Roll();
}
}
}
Dice::Dice(Controllers::MotionController& motionController,
Controllers::MotorController& motorController,
Controllers::Settings& settingsController)
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
static_cast<uint32_t>(motionController.X()),
static_cast<uint32_t>(motionController.Y()),
static_cast<uint32_t>(motionController.Z())};
gen.seed(sseq);
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
"count",
lv_scr_act(),
LV_ALIGN_IN_TOP_LEFT,
0,
0);
lv_obj_t* dCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
"sides",
nCounterLabel,
LV_ALIGN_OUT_RIGHT_MID,
20,
0);
nCounter.Create();
lv_obj_align(nCounter.GetObject(), nCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
nCounter.SetValue(1);
dCounter.Create();
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
dCounter.SetValue(6);
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
currentColorIndex = distrib(gen);
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
resultColors[currentColorIndex],
LV_LABEL_LONG_BREAK,
120,
LV_LABEL_ALIGN_CENTER,
"",
lv_scr_act(),
LV_ALIGN_IN_TOP_RIGHT,
11,
38);
resultIndividualLabel = MakeLabel(&jetbrains_mono_bold_20,
resultColors[currentColorIndex],
LV_LABEL_LONG_BREAK,
90,
LV_LABEL_ALIGN_CENTER,
"",
resultTotalLabel,
LV_ALIGN_OUT_BOTTOM_MID,
0,
10);
Roll();
openingRoll = false;
btnRoll = lv_btn_create(lv_scr_act(), nullptr);
btnRoll->user_data = this;
lv_obj_set_event_cb(btnRoll, btnRollEventHandler);
lv_obj_set_size(btnRoll, 240, 50);
lv_obj_align(btnRoll, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 0, 0);
btnRollLabel = MakeLabel(&jetbrains_mono_bold_20,
LV_COLOR_WHITE,
LV_LABEL_LONG_EXPAND,
0,
LV_LABEL_ALIGN_CENTER,
Symbols::dice,
btnRoll,
LV_ALIGN_CENTER,
0,
0);
// Spagetti code in motion controller: it only updates the shake speed when shake to wake is on...
enableShakeForDice = !settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake);
if (enableShakeForDice) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, true);
}
refreshTask = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
}
Dice::~Dice() {
// reset the shake to wake mode.
if (enableShakeForDice) {
settingsController.setWakeUpMode(Pinetime::Controllers::Settings::WakeUpMode::Shake, false);
enableShakeForDice = false;
}
lv_task_del(refreshTask);
lv_obj_clean(lv_scr_act());
}
void Dice::Refresh() {
// we only reset the hysteresis when at rest
if (motionController.CurrentShakeSpeed() >= settingsController.GetShakeThreshold()) {
if (currentRollHysteresis <= 0) {
// this timestamp is used for the screen timeout
lv_disp_get_next(NULL)->last_activity_time = lv_tick_get();
Roll();
}
} else if (currentRollHysteresis > 0)
--currentRollHysteresis;
}
void Dice::Roll() {
uint8_t resultIndividual;
uint16_t resultTotal = 0;
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
lv_label_set_text(resultIndividualLabel, "");
if (nCounter.GetValue() == 1) {
resultTotal = distrib(gen);
if (dCounter.GetValue() == 2) {
switch (resultTotal) {
case 1:
lv_label_set_text(resultIndividualLabel, "HEADS");
break;
case 2:
lv_label_set_text(resultIndividualLabel, "TAILS");
break;
}
}
} else {
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
resultIndividual = distrib(gen);
resultTotal += resultIndividual;
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
if (i < (nCounter.GetValue() - 1)) {
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, "+");
}
}
}
lv_label_set_text_fmt(resultTotalLabel, "%d", resultTotal);
if (openingRoll == false) {
motorController.RunForDuration(30);
NextColor();
currentRollHysteresis = rollHysteresis;
}
}
void Dice::NextColor() {
currentColorIndex = (currentColorIndex + 1) % resultColors.size();
lv_obj_set_style_local_text_color(resultTotalLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
lv_obj_set_style_local_text_color(resultIndividualLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, resultColors[currentColorIndex]);
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include "displayapp/apps/Apps.h"
#include "displayapp/screens/Screen.h"
#include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
#include <array>
#include <random>
namespace Pinetime {
namespace Applications {
namespace Screens {
class Dice : public Screen {
public:
Dice(Controllers::MotionController& motionController,
Controllers::MotorController& motorController,
Controllers::Settings& settingsController);
~Dice() override;
void Roll();
void Refresh() override;
private:
lv_obj_t* btnRoll;
lv_obj_t* btnRollLabel;
lv_obj_t* resultTotalLabel;
lv_obj_t* resultIndividualLabel;
lv_task_t* refreshTask;
bool enableShakeForDice = false;
std::mt19937 gen;
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
uint8_t currentColorIndex;
void NextColor();
Widgets::Counter nCounter = Widgets::Counter(1, 9, jetbrains_mono_42);
Widgets::Counter dCounter = Widgets::Counter(2, 99, jetbrains_mono_42);
bool openingRoll = true;
uint8_t currentRollHysteresis = 0;
static constexpr uint8_t rollHysteresis = 10;
Controllers::MotorController& motorController;
Controllers::MotionController& motionController;
Controllers::Settings& settingsController;
};
}
template <>
struct AppTraits<Apps::Dice> {
static constexpr Apps app = Apps::Dice;
static constexpr const char* icon = Screens::Symbols::dice;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
};
};
}
}

View File

@@ -41,7 +41,7 @@ HeartRate::HeartRate(Controllers::HeartRateController& heartRateController, Syst
lv_obj_set_style_local_text_color(label_hr, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
}
lv_label_set_text_static(label_hr, "000");
lv_label_set_text_static(label_hr, "---");
lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
label_bpm = lv_label_create(lv_scr_act(), nullptr);
@@ -82,10 +82,14 @@ void HeartRate::Refresh() {
case Controllers::HeartRateController::States::NoTouch:
case Controllers::HeartRateController::States::NotEnoughData:
// case Controllers::HeartRateController::States::Stopped:
lv_label_set_text_static(label_hr, "000");
lv_label_set_text_static(label_hr, "---");
break;
default:
lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
if (heartRateController.HeartRate() == 0) {
lv_label_set_text_static(label_hr, "---");
} else {
lv_label_set_text_fmt(label_hr, "%03d", heartRateController.HeartRate());
}
}
lv_label_set_text_static(label_status, ToString(state));

View File

@@ -53,9 +53,9 @@ void Motion::Refresh() {
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
lv_label_set_text_fmt(label,
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#",
motionController.X() / 0x10,
motionController.Y() / 0x10,
motionController.Z() / 0x10);
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg",
motionController.X(),
motionController.Y(),
motionController.Z());
lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
}

View File

@@ -11,6 +11,7 @@ namespace Pinetime {
static constexpr const char* plug = "\xEF\x87\xA6";
static constexpr const char* shoe = "\xEF\x95\x8B";
static constexpr const char* clock = "\xEF\x80\x97";
static constexpr const char* bell = "\xEF\x83\xB3";
static constexpr const char* info = "\xEF\x84\xA9";
static constexpr const char* list = "\xEF\x80\xBA";
static constexpr const char* sun = "\xEF\x86\x85";
@@ -34,6 +35,7 @@ namespace Pinetime {
static constexpr const char* hourGlass = "\xEF\x89\x92";
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
static constexpr const char* drum = "\xEF\x95\xA9";
static constexpr const char* dice = "\xEF\x94\xA2";
static constexpr const char* eye = "\xEF\x81\xAE";
static constexpr const char* home = "\xEF\x80\x95";
static constexpr const char* sleep = "\xEE\xBD\x84";

View File

@@ -1,27 +1,22 @@
#include "displayapp/screens/WatchFaceAnalog.h"
#include <cmath>
#include <lvgl/lvgl.h>
#include <nrf_log.h>
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/NotificationIcon.h"
#include "components/settings/Settings.h"
#include "displayapp/InfiniTimeTheme.h"
#include "sunset/src/sunset.h"
using namespace Pinetime::Applications::Screens;
namespace {
int16_t HourLength = 70;
constexpr int16_t HourLength = 70;
constexpr int16_t MinuteLength = 90;
constexpr int16_t SecondLength = 110;
constexpr int16_t SunDialVerticalOffset = 40;
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
const lv_color_t DARK_GRAY = lv_color_make(48, 48, 48);
const lv_color_t DARK_ORANGE = lv_color_make(48, 26, 0);
int16_t Cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90);
@@ -44,41 +39,6 @@ namespace {
.y = CoordinateYRelocate(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)};
}
void printKoku(const char* str, uint8_t i, bool big){
lv_obj_t* koku = lv_label_create(lv_scr_act(), NULL);
lv_label_set_align(koku, LV_LABEL_ALIGN_CENTER);
lv_label_set_text(koku, str);
lv_point_t pt;
if (big) {
pt = CoordinateRelocate(LV_HOR_RES/2-20, i*30);
lv_obj_set_style_local_text_font(koku, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &noto_serif_cjk_20);
} else {
pt = CoordinateRelocate(LV_HOR_RES/2-45, i*30);
lv_obj_set_style_local_text_font(koku, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &noto_serif_cjk_15);
}
lv_obj_align(koku, NULL, LV_ALIGN_CENTER, LV_HOR_RES/2-pt.x, LV_HOR_RES/2-pt.y);
lv_obj_set_style_local_text_color(koku, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
}
void drawTime(lv_obj_t * obj, uint8_t hour, uint8_t minute, lv_color_t color, uint8_t x, uint8_t y) {
lv_label_set_align(obj, LV_LABEL_ALIGN_CENTER);
lv_label_set_text_fmt(obj, "%02d:%02d", hour, minute);
lv_obj_set_pos(obj, 0, 0);
lv_obj_align(obj, NULL, LV_ALIGN_CENTER, x, y);
lv_obj_set_style_local_text_color(obj, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
lv_obj_set_style_local_text_font(obj, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20);
}
void drawCircle(uint8_t width, bool left, lv_coord_t offset, lv_color_t color){
lv_obj_t * cir = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_size(cir, width, width);
if (left)
lv_obj_align(cir, nullptr, LV_ALIGN_IN_LEFT_MID, offset, 0);
else
lv_obj_align(cir, nullptr, LV_ALIGN_IN_RIGHT_MID, offset, 0);
lv_obj_set_style_local_bg_color(cir, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, color);
lv_obj_set_style_local_radius(cir, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
}
}
WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController,
@@ -98,97 +58,41 @@ WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController,
sMinute = 99;
sSecond = 99;
location = settingsController.GetLocation();
minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(minor_scales, 300, 51);
lv_linemeter_set_angle_offset(minor_scales, 180);
lv_obj_set_size(minor_scales, 240, 240);
lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
// begin Japanese watch face
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
lv_obj_t * nighttime = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_size(nighttime, LV_HOR_RES, LV_HOR_RES/2);
lv_obj_set_pos(nighttime, 0, 0);
lv_obj_set_style_local_bg_color(nighttime, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
lv_obj_set_style_local_radius(nighttime, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 0);
major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(major_scales, 300, 11);
lv_linemeter_set_angle_offset(major_scales, 180);
lv_obj_set_size(major_scales, 240, 240);
lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
drawCircle(35, true, 4, DARK_GRAY);
drawCircle(25, true, 33, DARK_GRAY);
drawCircle(35, false, -3, LV_COLOR_BLACK);
drawCircle(25, false, -32, LV_COLOR_BLACK);
large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(large_scales, 180, 3);
lv_linemeter_set_angle_offset(large_scales, 180);
lv_obj_set_size(large_scales, 240, 240);
lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(major_scales, 360, 13);
lv_linemeter_set_angle_offset(major_scales, 15);
lv_obj_set_size(major_scales, 240, 240);
lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 50);
lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
axis = lv_arc_create(lv_scr_act(), NULL);
lv_obj_set_size(axis, 70, 70);
lv_arc_set_bg_angles(axis, 0, 360);
lv_arc_set_end_angle(axis, 360);
lv_obj_align(axis, NULL, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_line_width(axis, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, 40);
lv_obj_set_style_local_line_color(axis, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, LV_COLOR_WHITE);
const char* kokuZodiac[] = {"","","","","","","","","","","",""};
const char* kokuNums[] = {"","","","","",""};
twelve = lv_label_create(lv_scr_act(), nullptr);
for (int i=0;i<12;i++) {
printKoku(kokuZodiac[i], i, true);
printKoku(kokuNums[i%6], i, false);
}
lv_style_init(&hour_line_style);
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 10);
lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
lv_style_init(&hour_line_style_trace);
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 10);
lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, true);
} else {
minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(minor_scales, 300, 51);
lv_linemeter_set_angle_offset(minor_scales, 180);
lv_obj_set_size(minor_scales, 240, 240);
lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(major_scales, 300, 11);
lv_linemeter_set_angle_offset(major_scales, 180);
lv_obj_set_size(major_scales, 240, 240);
lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
lv_linemeter_set_scale(large_scales, 180, 3);
lv_linemeter_set_angle_offset(large_scales, 180);
lv_obj_set_size(large_scales, 240, 240);
lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
twelve = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_align(twelve, LV_LABEL_ALIGN_CENTER);
lv_label_set_text_static(twelve, "12");
lv_obj_set_pos(twelve, 110, 10);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
lv_style_init(&hour_line_style);
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
lv_style_init(&hour_line_style_trace);
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
}
twelve = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_align(twelve, LV_LABEL_ALIGN_CENTER);
lv_label_set_text_static(twelve, "12");
lv_obj_set_pos(twelve, 110, 10);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
batteryIcon.Create(lv_scr_act());
lv_obj_align(batteryIcon.GetObject(), nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
@@ -207,6 +111,7 @@ WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController,
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
// Date - Day / Week day
label_date_day = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
@@ -237,10 +142,16 @@ WatchFaceAnalog::WatchFaceAnalog(Controllers::DateTime& dateTimeController,
lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
lv_style_init(&hour_line_style);
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
lv_style_init(&hour_line_style_trace);
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
@@ -260,73 +171,12 @@ WatchFaceAnalog::~WatchFaceAnalog() {
lv_obj_clean(lv_scr_act());
}
void WatchFaceAnalog::drawWatchFaceWadokei(){
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
if (sHour != hour || sMinute != minute) {
sun.setPosition((float)location.latitude, (float)location.longitude, location.tzOffset);
//from minutes past midnight
sun.setCurrentDate(dateTimeController.Year(), static_cast<uint8_t>(dateTimeController.Month())+1, dateTimeController.Day());
sun.setTZOffset(location.tzOffset);
minutesSunrise = sun.calcSunrise();
minutesSunset = sun.calcSunset();
minutesDaytime = (minutesSunset - minutesSunrise);
minutesNighttime = (1440 - minutesDaytime);
minutesBeforeSunset = minutesSunset - (hour * 60 + minute); // i.e.zero degrees
HourLength = 40; // wadokei hand length
int16_t hourAngle=0;
// auto const hourAngle = ((hour * 15 + minute / 2)+180)%360; //24-hr rotation with 0/24 at the bottom
if(minutesBeforeSunset > minutesDaytime) {
hourAngle = -180.0 - 180.0 * (minutesBeforeSunset - minutesDaytime) / minutesNighttime;
}
else if(minutesBeforeSunset > 0 ) {
hourAngle = -180.0 * minutesBeforeSunset / minutesDaytime;
}
else {
hourAngle = -180.0 * minutesBeforeSunset / minutesNighttime;
}
// NRF_LOG_INFO("angle: %d, day: %d, night: %d, bef: %d",
// hourAngle+90,
// minutesDaytime,
// minutesNighttime,
// minutesBeforeSunset);
sHour = hour;
sMinute = minute;
hour_point_trace[0] = CoordinateRelocate(28, hourAngle+90-66); //axis radius minus line stroke
hour_point_trace[1] = CoordinateRelocate(HourLength, hourAngle+90);
hour_point[0] = CoordinateRelocate(28, hourAngle+90+66); //axis radius minus line stroke
hour_point[1] = CoordinateRelocate(HourLength, hourAngle+90);
lv_line_set_points(hour_body, hour_point, 2);
lv_line_set_points(hour_body_trace, hour_point_trace, 2);
drawTime(twelve, hour, minute, LV_COLOR_BLACK, 0, 0);
lv_obj_move_foreground(twelve);
}
}
void WatchFaceAnalog::UpdateClock() {
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
drawWatchFaceWadokei();
return;
}
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
uint8_t second = dateTimeController.Seconds();
if (minute>99 && sMinute != minute) { // disable
if (sMinute != minute) {
auto const angle = minute * 6;
minute_point[0] = CoordinateRelocate(30, angle);
minute_point[1] = CoordinateRelocate(MinuteLength, angle);
@@ -353,8 +203,7 @@ void WatchFaceAnalog::UpdateClock() {
lv_line_set_points(hour_body_trace, hour_point_trace, 2);
}
if (second>99 && sSecond != second) { // disable
if (sSecond != second) {
sSecond = second;
auto const angle = second * 6;
@@ -407,20 +256,9 @@ void WatchFaceAnalog::Refresh() {
if (currentDateTime.IsUpdated()) {
UpdateClock();
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
/*char const* MonthsString[] = {"--", "IANUARIUS","FEBRUARIUS","MARTIUS","APRILIS","MARTIUSIUNIUS","QUINTILIS","SEXTILIS","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"};
char const* DaysString[] = {"--", "LUNAE", "MARTIS", "MERCURII", "IOVIS", "VENERIS", "SATURNI", "SOLIS"};
char const* RomanNumeralsString[] = {"--", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
lv_label_set_text_fmt(label_date_day, "%s\n%s %s",
DaysString[static_cast<uint8_t>(dateTimeController.DayOfWeek())],
RomanNumeralsString[static_cast<uint8_t>(dateTimeController.Day())],
MonthsString[static_cast<uint8_t>(dateTimeController.Month())]);*/
lv_obj_align(label_date_day, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -2000);
} else {
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
}
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
}
}
}

View File

@@ -11,7 +11,6 @@
#include "components/ble/NotificationManager.h"
#include "displayapp/screens/BatteryIcon.h"
#include "utility/DirtyValue.h"
#include "sunset/src/sunset.h"
namespace Pinetime {
namespace Controllers {
@@ -44,15 +43,12 @@ namespace Pinetime {
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
Utility::DirtyValue<bool> notificationState {false};
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;
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* minor_scales;
lv_obj_t* major_scales;
lv_obj_t* large_scales;
lv_obj_t* one;
lv_obj_t* twelve;
lv_obj_t* axis;
lv_obj_t* hour_body;
lv_obj_t* hour_body_trace;
@@ -71,7 +67,6 @@ namespace Pinetime {
lv_style_t minute_line_style;
lv_style_t minute_line_style_trace;
lv_style_t second_line_style;
lv_style_t axis_style;
lv_obj_t* label_date_day;
lv_obj_t* plugIcon;
@@ -80,21 +75,12 @@ namespace Pinetime {
BatteryIcon batteryIcon;
Controllers::Settings::Location location;
SunSet sun;
int16_t minutesSunrise;
int16_t minutesSunset;
int16_t minutesDaytime;
int16_t minutesNighttime;
int16_t minutesBeforeSunset;
const Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
void drawWatchFaceWadokei();
void UpdateClock();
void SetBatteryIcon();

View File

@@ -0,0 +1,334 @@
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/BleIcon.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;
WatchFaceCasioStyleG7710::WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem)
: currentDateTime {{}},
batteryIcon(false),
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificatioManager {notificatioManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController} {
lfs_file f = {};
if (filesystem.FileOpen(&f, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_dot40 = lv_font_load("F:/fonts/lv_font_dots_40.bin");
}
if (filesystem.FileOpen(&f, "/fonts/7segments_40.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_segment40 = lv_font_load("F:/fonts/7segments_40.bin");
}
if (filesystem.FileOpen(&f, "/fonts/7segments_115.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_segment115 = lv_font_load("F:/fonts/7segments_115.bin");
}
label_battery_value = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_battery_value, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
lv_obj_set_style_local_text_color(label_battery_value, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(label_battery_value, "00%");
batteryIcon.Create(lv_scr_act());
batteryIcon.SetColor(color_text);
lv_obj_align(batteryIcon.GetObject(), label_battery_value, LV_ALIGN_OUT_LEFT_MID, -5, 0);
batteryPlug = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(batteryPlug, Symbols::plug);
lv_obj_align(batteryPlug, batteryIcon.GetObject(), LV_ALIGN_OUT_LEFT_MID, -5, 0);
bleIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
lv_obj_align(notificationIcon, bleIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
label_day_of_week = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_day_of_week, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 10, 64);
lv_obj_set_style_local_text_color(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_day_of_week, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40);
lv_label_set_text_static(label_day_of_week, "SUN");
label_week_number = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_week_number, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 5, 22);
lv_obj_set_style_local_text_color(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_week_number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_dot40);
lv_label_set_text_static(label_week_number, "WK26");
label_day_of_year = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_day_of_year, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 30);
lv_obj_set_style_local_text_color(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_day_of_year, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40);
lv_label_set_text_static(label_day_of_year, "181-184");
lv_style_init(&style_line);
lv_style_set_line_width(&style_line, LV_STATE_DEFAULT, 2);
lv_style_set_line_color(&style_line, LV_STATE_DEFAULT, color_text);
lv_style_set_line_rounded(&style_line, LV_STATE_DEFAULT, true);
lv_style_init(&style_border);
lv_style_set_line_width(&style_border, LV_STATE_DEFAULT, 6);
lv_style_set_line_color(&style_border, LV_STATE_DEFAULT, color_text);
lv_style_set_line_rounded(&style_border, LV_STATE_DEFAULT, true);
line_icons = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_icons, line_icons_points, 3);
lv_obj_add_style(line_icons, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_icons, nullptr, LV_ALIGN_IN_TOP_RIGHT, -10, 18);
line_day_of_week_number = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_day_of_week_number, line_day_of_week_number_points, 4);
lv_obj_add_style(line_day_of_week_number, LV_LINE_PART_MAIN, &style_border);
lv_obj_align(line_day_of_week_number, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 8);
line_day_of_year = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_day_of_year, line_day_of_year_points, 3);
lv_obj_add_style(line_day_of_year, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_day_of_year, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 60);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_TOP_LEFT, 100, 70);
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment40);
lv_label_set_text_static(label_date, "6-30");
line_date = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_date, line_date_points, 3);
lv_obj_add_style(line_date, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_date, nullptr, LV_ALIGN_IN_TOP_RIGHT, 0, 100);
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_segment115);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 40);
line_time = lv_line_create(lv_scr_act(), nullptr);
lv_line_set_points(line_time, line_time_points, 3);
lv_obj_add_style(line_time, LV_LINE_PART_MAIN, &style_line);
lv_obj_align(line_time, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, -25);
label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_time_ampm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
lv_label_set_text_static(label_time_ampm, "");
lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 5, -5);
backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_click(backgroundLabel, true);
lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
lv_obj_set_size(backgroundLabel, 240, 240);
lv_obj_set_pos(backgroundLabel, 0, 0);
lv_label_set_text_static(backgroundLabel, "");
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, color_text);
lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
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, color_text);
lv_label_set_text_static(stepValue, "0");
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color_text);
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();
}
WatchFaceCasioStyleG7710::~WatchFaceCasioStyleG7710() {
lv_task_del(taskRefresh);
lv_style_reset(&style_line);
lv_style_reset(&style_border);
if (font_dot40 != nullptr) {
lv_font_free(font_dot40);
}
if (font_segment40 != nullptr) {
lv_font_free(font_segment40);
}
if (font_segment115 != nullptr) {
lv_font_free(font_segment115);
}
lv_obj_clean(lv_scr_act());
}
void WatchFaceCasioStyleG7710::Refresh() {
powerPresent = batteryController.IsPowerPresent();
if (powerPresent.IsUpdated()) {
lv_label_set_text_static(batteryPlug, BatteryIcon::GetPlugIcon(powerPresent.Get()));
}
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated()) {
auto batteryPercent = batteryPercentRemaining.Get();
batteryIcon.SetBatteryPercentage(batteryPercent);
lv_label_set_text_fmt(label_battery_value, "%d%%", batteryPercent);
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
}
lv_obj_realign(label_battery_value);
lv_obj_realign(batteryIcon.GetObject());
lv_obj_realign(batteryPlug);
lv_obj_realign(bleIcon);
lv_obj_realign(notificationIcon);
notificationState = notificatioManager.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();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
char ampmChar[2] = "A";
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(label_time_ampm, ampmChar);
lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute);
} else {
lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute);
}
lv_obj_realign(label_time);
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
const char* weekNumberFormat = "%V";
uint16_t year = dateTimeController.Year();
Controllers::DateTime::Months month = dateTimeController.Month();
uint8_t day = dateTimeController.Day();
int dayOfYear = dateTimeController.DayOfYear();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
// 24h mode: ddmmyyyy, first DOW=Monday;
lv_label_set_text_fmt(label_date, "%3d-%2d", day, month);
weekNumberFormat = "%V"; // Replaced by the week number of the year (Monday as the first day of the week) as a decimal number
// [01,53]. If the week containing 1 January has four or more days in the new year, then it is considered
// week 1. Otherwise, it is the last week of the previous year, and the next week is week 1. Both January
// 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday]
} else {
// 12h mode: mmddyyyy, first DOW=Sunday;
lv_label_set_text_fmt(label_date, "%3d-%2d", month, day);
weekNumberFormat = "%U"; // Replaced by the week number of the year as a decimal number [00,53]. The first Sunday of January is the
// first day of week 1; days in the new year before this are in week 0. [ tm_year, tm_wday, tm_yday]
}
time_t ttTime =
std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(currentDateTime.Get()));
tm* tmTime = std::localtime(&ttTime);
// TODO: When we start using C++20, use std::chrono::year::is_leap
int daysInCurrentYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 366 : 365;
uint16_t daysTillEndOfYearNumber = daysInCurrentYear - dayOfYear;
char buffer[8];
strftime(buffer, 8, weekNumberFormat, tmTime);
uint8_t weekNumber = atoi(buffer);
lv_label_set_text_fmt(label_day_of_week, "%s", dateTimeController.DayOfWeekShortToString());
lv_label_set_text_fmt(label_day_of_year, "%3d-%3d", dayOfYear, daysTillEndOfYearNumber);
lv_label_set_text_fmt(label_week_number, "WK%02d", weekNumber);
lv_obj_realign(label_day_of_week);
lv_obj_realign(label_day_of_year);
lv_obj_realign(label_week_number);
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, color_text);
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);
}
}
bool WatchFaceCasioStyleG7710::IsAvailable(Pinetime::Controllers::FS& filesystem) {
lfs_file file = {};
if (filesystem.FileOpen(&file, "/fonts/lv_font_dots_40.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/7segments_40.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/7segments_115.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
return true;
}

View File

@@ -0,0 +1,126 @@
#pragma once
#include <displayapp/screens/BatteryIcon.h>
#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 "components/ble/BleController.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceCasioStyleG7710 : public Screen {
public:
WatchFaceCasioStyleG7710(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem);
~WatchFaceCasioStyleG7710() override;
void Refresh() override;
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
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 {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}};
lv_point_t line_day_of_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}};
lv_point_t line_day_of_year_points[3] {{0, 5}, {130, 5}, {135, 0}};
lv_point_t line_date_points[3] {{0, 5}, {135, 5}, {140, 0}};
lv_point_t line_time_points[3] {{0, 0}, {230, 0}, {235, 5}};
lv_color_t color_text = lv_color_hex(0x98B69A);
lv_style_t style_line;
lv_style_t style_border;
lv_obj_t* label_time;
lv_obj_t* line_time;
lv_obj_t* label_time_ampm;
lv_obj_t* label_date;
lv_obj_t* line_date;
lv_obj_t* label_day_of_week;
lv_obj_t* label_week_number;
lv_obj_t* line_day_of_week_number;
lv_obj_t* label_day_of_year;
lv_obj_t* line_day_of_year;
lv_obj_t* backgroundLabel;
lv_obj_t* bleIcon;
lv_obj_t* batteryPlug;
lv_obj_t* label_battery_value;
lv_obj_t* heartbeatIcon;
lv_obj_t* heartbeatValue;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* line_icons;
BatteryIcon batteryIcon;
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificatioManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
lv_task_t* taskRefresh;
lv_font_t* font_dot40 = nullptr;
lv_font_t* font_segment40 = nullptr;
lv_font_t* font_segment115 = nullptr;
};
}
template <>
struct WatchFaceTraits<WatchFace::CasioStyleG7710> {
static constexpr WatchFace watchFace = WatchFace::CasioStyleG7710;
static constexpr const char* name = "Casio G7710";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceCasioStyleG7710(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
controllers.filesystem);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::WatchFaceCasioStyleG7710::IsAvailable(filesystem);
}
};
}
}

View File

@@ -0,0 +1,193 @@
#include "displayapp/screens/WatchFaceDigital.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#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/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController},
weatherService {weatherService},
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);
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_hex(0x999999));
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(weatherIcon, "");
lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
lv_obj_set_auto_realign(weatherIcon, true);
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_hex(0x999999));
lv_label_set_text(temperature, "");
lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
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);
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);
label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(label_time_ampm, "");
lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55);
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();
}
WatchFaceDigital::~WatchFaceDigital() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void WatchFaceDigital::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();
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(label_time_ampm, ampmChar);
lv_label_set_text_fmt(label_time, "%2d:%02d", hour, minute);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
} else {
lv_label_set_text_fmt(label_time, "%02d:%02d", hour, minute);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
}
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
uint16_t year = dateTimeController.Year();
uint8_t day = dateTimeController.Day();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H24) {
lv_label_set_text_fmt(label_date,
"%s %d %s %d",
dateTimeController.DayOfWeekShortToString(),
day,
dateTimeController.MonthShortToString(),
year);
} else {
lv_label_set_text_fmt(label_date,
"%s %s %d %d",
dateTimeController.DayOfWeekShortToString(),
dateTimeController.MonthShortToString(),
day,
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);
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F';
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {
lv_label_set_text_static(temperature, "");
lv_label_set_text(weatherIcon, "");
}
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
}

View File

@@ -0,0 +1,99 @@
#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/SimpleWeatherService.h"
#include "components/ble/BleController.h"
#include "displayapp/widgets/StatusIcons.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceDigital : public Screen {
public:
WatchFaceDigital(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weather);
~WatchFaceDigital() override;
void Refresh() override;
private:
uint8_t displayedHour = -1;
uint8_t displayedMinute = -1;
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 {};
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
lv_obj_t* label_time;
lv_obj_t* label_time_ampm;
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;
lv_obj_t* weatherIcon;
lv_obj_t* temperature;
Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
Controllers::SimpleWeatherService& weatherService;
lv_task_t* taskRefresh;
Widgets::StatusIcons statusIcons;
};
}
template <>
struct WatchFaceTraits<WatchFace::Digital> {
static constexpr WatchFace watchFace = WatchFace::Digital;
static constexpr const char* name = "Digital face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceDigital(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
*controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}

View File

@@ -0,0 +1,189 @@
#include "displayapp/screens/WatchFaceFuzzy.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceFuzzy::WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weatherService)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController},
weatherService {weatherService}
{
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);
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_hex(0x999999));
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(weatherIcon, "");
lv_obj_align(weatherIcon, nullptr, LV_ALIGN_IN_TOP_MID, -20, 50);
lv_obj_set_auto_realign(weatherIcon, true);
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_hex(0x999999));
lv_label_set_text(temperature, "");
lv_obj_align(temperature, nullptr, LV_ALIGN_IN_TOP_MID, 20, 50);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
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);
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_24);
lv_label_set_recolor(label_time, true);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
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() {
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(hour, minute);
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
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);
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
tempUnit = 'F';
}
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
} else {
lv_label_set_text_static(temperature, "");
lv_label_set_text(weatherIcon, "");
}
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}
}
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 >= 57) {
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]);
}
printf("%s\n", timeStr);
lv_label_set_text(label_time, timeStr);
lv_obj_realign(label_time);
}

View File

@@ -0,0 +1,100 @@
#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/SimpleWeatherService.h"
#include "components/ble/BleController.h"
#include "displayapp/widgets/StatusIcons.h"
#include "utility/DirtyValue.h"
#include "displayapp/apps/Apps.h"
extern lv_font_t jetbrains_mono_bold_24;
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class HeartRateController;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceFuzzy : public Screen {
public:
WatchFaceFuzzy(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
Controllers::SimpleWeatherService& weather);
~WatchFaceFuzzy() override;
void Refresh() override;
private:
uint8_t displayedHour = -1;
uint8_t displayedMinute = -1;
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 {};
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::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;
lv_obj_t* weatherIcon;
lv_obj_t* temperature;
Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
Controllers::MotionController& motionController;
Controllers::SimpleWeatherService& weatherService;
lv_task_t* taskRefresh;
static char const *nums[];
static char const *mods[];
char timeStr[64];
void printTimeWords(int hour, int minute);
};
}
template <>
struct WatchFaceTraits<WatchFace::Fuzzy> {
static constexpr WatchFace watchFace = WatchFace::Fuzzy;
static constexpr const char* name = "Fuzzy face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceFuzzy(controllers.dateTimeController,
controllers.notificationManager,
controllers.settingsController,
controllers.heartRateController,
controllers.motionController,
*controllers.weatherController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}

View File

@@ -0,0 +1,509 @@
#include "displayapp/screens/WatchFaceInfineat.h"
#include <lvgl/lvgl.h>
#include <cstdio>
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/BleIcon.h"
#include "components/settings/Settings.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/motion/MotionController.h"
using namespace Pinetime::Applications::Screens;
namespace {
void event_handler(lv_obj_t* obj, lv_event_t event) {
auto* screen = static_cast<WatchFaceInfineat*>(obj->user_data);
screen->UpdateSelected(obj, event);
}
enum class colors {
orange,
blue,
green,
rainbow,
gray,
nordBlue,
nordGreen,
};
constexpr int nColors = 7; // must match number of colors in InfineatColors
constexpr int nLines = WatchFaceInfineat::nLines;
constexpr std::array<lv_color_t, nLines> orangeColors = {LV_COLOR_MAKE(0xfd, 0x87, 0x2b),
LV_COLOR_MAKE(0xdb, 0x33, 0x16),
LV_COLOR_MAKE(0x6f, 0x10, 0x00),
LV_COLOR_MAKE(0xfd, 0x7a, 0x0a),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xe8, 0x51, 0x02),
LV_COLOR_MAKE(0xea, 0x1c, 0x00)};
constexpr std::array<lv_color_t, nLines> blueColors = {LV_COLOR_MAKE(0xe7, 0xf8, 0xff),
LV_COLOR_MAKE(0x22, 0x32, 0xd0),
LV_COLOR_MAKE(0x18, 0x2a, 0x8b),
LV_COLOR_MAKE(0xe7, 0xf8, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x59, 0x91, 0xff),
LV_COLOR_MAKE(0x16, 0x36, 0xff)};
constexpr std::array<lv_color_t, nLines> greenColors = {LV_COLOR_MAKE(0xb8, 0xff, 0x9b),
LV_COLOR_MAKE(0x08, 0x86, 0x08),
LV_COLOR_MAKE(0x00, 0x4a, 0x00),
LV_COLOR_MAKE(0xb8, 0xff, 0x9b),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x62, 0xd5, 0x15),
LV_COLOR_MAKE(0x00, 0x74, 0x00)};
constexpr std::array<lv_color_t, nLines> rainbowColors = {LV_COLOR_MAKE(0x2d, 0xa4, 0x00),
LV_COLOR_MAKE(0xac, 0x09, 0xc4),
LV_COLOR_MAKE(0xfe, 0x03, 0x03),
LV_COLOR_MAKE(0x0d, 0x57, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xe0, 0xb9, 0x00),
LV_COLOR_MAKE(0xe8, 0x51, 0x02)};
constexpr std::array<lv_color_t, nLines> grayColors = {LV_COLOR_MAKE(0xee, 0xee, 0xee),
LV_COLOR_MAKE(0x98, 0x95, 0x9b),
LV_COLOR_MAKE(0x19, 0x19, 0x19),
LV_COLOR_MAKE(0xee, 0xee, 0xee),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x91, 0x91, 0x91),
LV_COLOR_MAKE(0x3a, 0x3a, 0x3a)};
constexpr std::array<lv_color_t, nLines> nordBlueColors = {LV_COLOR_MAKE(0xc3, 0xda, 0xf2),
LV_COLOR_MAKE(0x4d, 0x78, 0xce),
LV_COLOR_MAKE(0x15, 0x34, 0x51),
LV_COLOR_MAKE(0xc3, 0xda, 0xf2),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x5d, 0x8a, 0xd2),
LV_COLOR_MAKE(0x21, 0x51, 0x8a)};
constexpr std::array<lv_color_t, nLines> nordGreenColors = {LV_COLOR_MAKE(0xd5, 0xf0, 0xe9),
LV_COLOR_MAKE(0x23, 0x83, 0x73),
LV_COLOR_MAKE(0x1d, 0x41, 0x3f),
LV_COLOR_MAKE(0xd5, 0xf0, 0xe9),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0xff, 0xff, 0xff),
LV_COLOR_MAKE(0x2f, 0xb8, 0xa2),
LV_COLOR_MAKE(0x11, 0x70, 0x5a)};
constexpr const std::array<lv_color_t, nLines>* returnColor(colors color) {
if (color == colors::orange) {
return &orangeColors;
}
if (color == colors::blue) {
return &blueColors;
}
if (color == colors::green) {
return &greenColors;
}
if (color == colors::rainbow) {
return &rainbowColors;
}
if (color == colors::gray) {
return &grayColors;
}
if (color == colors::nordBlue) {
return &nordBlueColors;
}
return &nordGreenColors;
}
}
WatchFaceInfineat::WatchFaceInfineat(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::FS& filesystem)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
motionController {motionController} {
lfs_file f = {};
if (filesystem.FileOpen(&f, "/fonts/teko.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_teko = lv_font_load("F:/fonts/teko.bin");
}
if (filesystem.FileOpen(&f, "/fonts/bebas.bin", LFS_O_RDONLY) >= 0) {
filesystem.FileClose(&f);
font_bebas = lv_font_load("F:/fonts/bebas.bin");
}
// Side Cover
static constexpr lv_point_t linePoints[nLines][2] = {{{30, 25}, {68, -8}},
{{26, 167}, {43, 216}},
{{27, 40}, {27, 196}},
{{12, 182}, {65, 249}},
{{17, 99}, {17, 144}},
{{14, 81}, {40, 127}},
{{14, 163}, {40, 118}},
{{-20, 124}, {25, -11}},
{{-29, 89}, {27, 254}}};
static constexpr lv_style_int_t lineWidths[nLines] = {18, 15, 14, 22, 20, 18, 18, 52, 48};
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
for (int i = 0; i < nLines; i++) {
lines[i] = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, lineWidths[i]);
lv_color_t color = (*colors)[i];
lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color);
lv_line_set_points(lines[i], linePoints[i], 2);
}
logoPine = lv_img_create(lv_scr_act(), nullptr);
lv_img_set_src(logoPine, "F:/images/pine_small.bin");
lv_obj_set_pos(logoPine, 15, 106);
lineBattery = lv_line_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_line_width(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 24);
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_line_opa(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 190);
lineBatteryPoints[0] = {27, 105};
lineBatteryPoints[1] = {27, 106};
lv_line_set_points(lineBattery, lineBatteryPoints, 2);
lv_obj_move_foreground(lineBattery);
notificationIcon = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
lv_obj_set_style_local_radius(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
lv_obj_set_size(notificationIcon, 13, 13);
lv_obj_set_hidden(notificationIcon, true);
if (!settingsController.GetInfineatShowSideCover()) {
ToggleBatteryIndicatorColor(false);
for (auto& line : lines) {
lv_obj_set_hidden(line, true);
}
}
timeContainer = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(timeContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_size(timeContainer, 185, 185);
lv_obj_align(timeContainer, lv_scr_act(), LV_ALIGN_CENTER, 0, -10);
labelHour = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(labelHour, "01");
lv_obj_set_style_local_text_font(labelHour, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas);
lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 0);
labelMinutes = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(labelMinutes, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_bebas);
lv_label_set_text_static(labelMinutes, "00");
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
labelTimeAmPm = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(labelTimeAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_label_set_text_static(labelTimeAmPm, "");
lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 15);
dateContainer = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_bg_opa(dateContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
lv_obj_set_size(dateContainer, 60, 30);
lv_obj_align(dateContainer, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 5);
static constexpr lv_color_t grayColor = LV_COLOR_MAKE(0x99, 0x99, 0x99);
labelDate = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_obj_set_style_local_text_font(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_obj_align(labelDate, dateContainer, LV_ALIGN_IN_TOP_MID, 0, 0);
lv_label_set_text_static(labelDate, "Mon 01");
bleIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_label_set_text_static(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_obj_set_style_local_text_font(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font_teko);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, 10, 0);
lv_label_set_text_static(stepValue, "0");
stepIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, grayColor);
lv_label_set_text_static(stepIcon, Symbols::shoe);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
// Setting buttons
btnClose = lv_btn_create(lv_scr_act(), nullptr);
btnClose->user_data = this;
lv_obj_set_size(btnClose, 60, 60);
lv_obj_align(btnClose, lv_scr_act(), LV_ALIGN_CENTER, 0, -80);
lv_obj_set_style_local_bg_opa(btnClose, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblClose = lv_label_create(btnClose, nullptr);
lv_label_set_text_static(lblClose, "X");
lv_obj_set_event_cb(btnClose, event_handler);
lv_obj_set_hidden(btnClose, true);
btnNextColor = lv_btn_create(lv_scr_act(), nullptr);
btnNextColor->user_data = this;
lv_obj_set_size(btnNextColor, 60, 60);
lv_obj_align(btnNextColor, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -15, 0);
lv_obj_set_style_local_bg_opa(btnNextColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblNextColor = lv_label_create(btnNextColor, nullptr);
lv_label_set_text_static(lblNextColor, ">");
lv_obj_set_event_cb(btnNextColor, event_handler);
lv_obj_set_hidden(btnNextColor, true);
btnPrevColor = lv_btn_create(lv_scr_act(), nullptr);
btnPrevColor->user_data = this;
lv_obj_set_size(btnPrevColor, 60, 60);
lv_obj_align(btnPrevColor, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 15, 0);
lv_obj_set_style_local_bg_opa(btnPrevColor, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_t* lblPrevColor = lv_label_create(btnPrevColor, nullptr);
lv_label_set_text_static(lblPrevColor, "<");
lv_obj_set_event_cb(btnPrevColor, event_handler);
lv_obj_set_hidden(btnPrevColor, true);
btnToggleCover = lv_btn_create(lv_scr_act(), nullptr);
btnToggleCover->user_data = this;
lv_obj_set_size(btnToggleCover, 60, 60);
lv_obj_align(btnToggleCover, lv_scr_act(), LV_ALIGN_CENTER, 0, 80);
lv_obj_set_style_local_bg_opa(btnToggleCover, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
const char* labelToggle = settingsController.GetInfineatShowSideCover() ? "ON" : "OFF";
lblToggle = lv_label_create(btnToggleCover, nullptr);
lv_label_set_text_static(lblToggle, labelToggle);
lv_obj_set_event_cb(btnToggleCover, event_handler);
lv_obj_set_hidden(btnToggleCover, true);
// Button to access the settings
btnSettings = lv_btn_create(lv_scr_act(), nullptr);
btnSettings->user_data = this;
lv_obj_set_size(btnSettings, 150, 150);
lv_obj_align(btnSettings, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
lv_obj_set_style_local_radius(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 30);
lv_obj_set_style_local_bg_opa(btnSettings, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_70);
lv_obj_set_event_cb(btnSettings, event_handler);
labelBtnSettings = lv_label_create(btnSettings, nullptr);
lv_obj_set_style_local_text_font(labelBtnSettings, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &lv_font_sys_48);
lv_label_set_text_static(labelBtnSettings, Symbols::settings);
lv_obj_set_hidden(btnSettings, true);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceInfineat::~WatchFaceInfineat() {
lv_task_del(taskRefresh);
if (font_bebas != nullptr) {
lv_font_free(font_bebas);
}
if (font_teko != nullptr) {
lv_font_free(font_teko);
}
lv_obj_clean(lv_scr_act());
}
bool WatchFaceInfineat::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
if ((event == Pinetime::Applications::TouchEvents::LongTap) && lv_obj_get_hidden(btnSettings)) {
lv_obj_set_hidden(btnSettings, false);
savedTick = lv_tick_get();
return true;
}
// Prevent screen from sleeping when double tapping with settings on
if ((event == Pinetime::Applications::TouchEvents::DoubleTap) && !lv_obj_get_hidden(btnClose)) {
return true;
}
return false;
}
void WatchFaceInfineat::CloseMenu() {
settingsController.SaveSettings();
lv_obj_set_hidden(btnClose, true);
lv_obj_set_hidden(btnNextColor, true);
lv_obj_set_hidden(btnPrevColor, true);
lv_obj_set_hidden(btnToggleCover, true);
}
bool WatchFaceInfineat::OnButtonPushed() {
if (!lv_obj_get_hidden(btnClose)) {
CloseMenu();
return true;
}
return false;
}
void WatchFaceInfineat::UpdateSelected(lv_obj_t* object, lv_event_t event) {
if (event == LV_EVENT_CLICKED) {
bool showSideCover = settingsController.GetInfineatShowSideCover();
int colorIndex = settingsController.GetInfineatColorIndex();
if (object == btnSettings) {
lv_obj_set_hidden(btnSettings, true);
lv_obj_set_hidden(btnClose, false);
lv_obj_set_hidden(btnNextColor, !showSideCover);
lv_obj_set_hidden(btnPrevColor, !showSideCover);
lv_obj_set_hidden(btnToggleCover, false);
}
if (object == btnClose) {
CloseMenu();
}
if (object == btnToggleCover) {
settingsController.SetInfineatShowSideCover(!showSideCover);
ToggleBatteryIndicatorColor(!showSideCover);
for (auto& line : lines) {
lv_obj_set_hidden(line, showSideCover);
}
lv_obj_set_hidden(btnNextColor, showSideCover);
lv_obj_set_hidden(btnPrevColor, showSideCover);
const char* labelToggle = showSideCover ? "OFF" : "ON";
lv_label_set_text_static(lblToggle, labelToggle);
}
if (object == btnNextColor) {
colorIndex = (colorIndex + 1) % nColors;
settingsController.SetInfineatColorIndex(colorIndex);
}
if (object == btnPrevColor) {
colorIndex -= 1;
if (colorIndex < 0)
colorIndex = nColors - 1;
settingsController.SetInfineatColorIndex(colorIndex);
}
if (object == btnNextColor || object == btnPrevColor) {
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
for (int i = 0; i < nLines; i++) {
lv_color_t color = (*colors)[i];
lv_obj_set_style_local_line_color(lines[i], LV_LINE_PART_MAIN, LV_STATE_DEFAULT, color);
}
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
}
}
}
void WatchFaceInfineat::Refresh() {
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
lv_obj_set_hidden(notificationIcon, !notificationState.Get());
lv_obj_align(notificationIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, 0, 0);
}
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
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(labelTimeAmPm, ampmChar);
}
lv_label_set_text_fmt(labelHour, "%02d", hour);
lv_label_set_text_fmt(labelMinutes, "%02d", minute);
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
lv_obj_align(labelTimeAmPm, timeContainer, LV_ALIGN_OUT_RIGHT_TOP, 0, 10);
lv_obj_align(labelHour, timeContainer, LV_ALIGN_IN_TOP_MID, 0, 5);
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
}
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
if (currentDate.IsUpdated()) {
uint8_t day = dateTimeController.Day();
Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day);
lv_obj_realign(labelDate);
}
}
batteryPercentRemaining = batteryController.PercentRemaining();
isCharging = batteryController.IsCharging();
if (batteryController.IsCharging()) { // Charging battery animation
chargingBatteryPercent += 1;
if (chargingBatteryPercent > 100) {
chargingBatteryPercent = batteryPercentRemaining.Get();
}
SetBatteryLevel(chargingBatteryPercent);
} else if (isCharging.IsUpdated() || batteryPercentRemaining.IsUpdated()) {
chargingBatteryPercent = batteryPercentRemaining.Get();
SetBatteryLevel(chargingBatteryPercent);
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated()) {
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
lv_obj_align(bleIcon, dateContainer, LV_ALIGN_OUT_BOTTOM_MID, 0, 3);
}
stepCount = motionController.NbSteps();
if (stepCount.IsUpdated()) {
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_MID, 10, 0);
lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
}
if (!lv_obj_get_hidden(btnSettings)) {
if ((savedTick > 0) && (lv_tick_get() - savedTick > 3000)) {
lv_obj_set_hidden(btnSettings, true);
savedTick = 0;
}
}
}
void WatchFaceInfineat::SetBatteryLevel(uint8_t batteryPercent) {
// starting point (y) + Pine64 logo height * (100 - batteryPercent) / 100
lineBatteryPoints[1] = {27, static_cast<lv_coord_t>(105 + 32 * (100 - batteryPercent) / 100)};
lv_line_set_points(lineBattery, lineBatteryPoints, 2);
}
void WatchFaceInfineat::ToggleBatteryIndicatorColor(bool showSideCover) {
if (!showSideCover) { // make indicator and notification icon color white
lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_100);
lv_obj_set_style_local_image_recolor(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
} else {
lv_obj_set_style_local_image_recolor_opa(logoPine, LV_IMG_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_0);
const std::array<lv_color_t, nLines>* colors = returnColor(static_cast<enum colors>(settingsController.GetInfineatColorIndex()));
lv_obj_set_style_local_line_color(lineBattery, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, (*colors)[4]);
lv_obj_set_style_local_bg_color(notificationIcon, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, (*colors)[7]);
}
}
bool WatchFaceInfineat::IsAvailable(Pinetime::Controllers::FS& filesystem) {
lfs_file file = {};
if (filesystem.FileOpen(&file, "/fonts/teko.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/fonts/bebas.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
if (filesystem.FileOpen(&file, "/images/pine_small.bin", LFS_O_RDONLY) < 0) {
return false;
}
filesystem.FileClose(&file);
return true;
}

View File

@@ -0,0 +1,123 @@
#pragma once
#include <lvgl/lvgl.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"
#include "displayapp/apps/Apps.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
class MotionController;
}
namespace Applications {
namespace Screens {
class WatchFaceInfineat : public Screen {
public:
static constexpr int nLines = 9;
WatchFaceInfineat(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::MotionController& motionController,
Controllers::FS& fs);
~WatchFaceInfineat() override;
bool OnTouchEvent(TouchEvents event) override;
bool OnButtonPushed() override;
void UpdateSelected(lv_obj_t* object, lv_event_t event);
void CloseMenu();
void Refresh() override;
static bool IsAvailable(Pinetime::Controllers::FS& filesystem);
private:
uint32_t savedTick = 0;
uint8_t chargingBatteryPercent = 101; // not a mistake ;)
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
Utility::DirtyValue<bool> isCharging {};
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<bool> notificationState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
// Lines making up the side cover
lv_obj_t* lineBattery;
lv_point_t lineBatteryPoints[2];
lv_obj_t* logoPine;
lv_obj_t* timeContainer;
lv_obj_t* labelHour;
lv_obj_t* labelMinutes;
lv_obj_t* labelTimeAmPm;
lv_obj_t* dateContainer;
lv_obj_t* labelDate;
lv_obj_t* bleIcon;
lv_obj_t* stepIcon;
lv_obj_t* stepValue;
lv_obj_t* notificationIcon;
lv_obj_t* btnClose;
lv_obj_t* btnNextColor;
lv_obj_t* btnToggleCover;
lv_obj_t* btnPrevColor;
lv_obj_t* btnSettings;
lv_obj_t* labelBtnSettings;
lv_obj_t* lblToggle;
lv_obj_t* lines[nLines];
Controllers::DateTime& dateTimeController;
const Controllers::Battery& batteryController;
const Controllers::Ble& bleController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
Controllers::MotionController& motionController;
void SetBatteryLevel(uint8_t batteryPercent);
void ToggleBatteryIndicatorColor(bool showSideCover);
lv_task_t* taskRefresh;
lv_font_t* font_teko = nullptr;
lv_font_t* font_bebas = nullptr;
};
}
template <>
struct WatchFaceTraits<WatchFace::Infineat> {
static constexpr WatchFace watchFace = WatchFace::Infineat;
static constexpr const char* name = "Infineat face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceInfineat(controllers.dateTimeController,
controllers.batteryController,
controllers.bleController,
controllers.notificationManager,
controllers.settingsController,
controllers.motionController,
controllers.filesystem);
};
static bool IsAvailable(Pinetime::Controllers::FS& filesystem) {
return Screens::WatchFaceInfineat::IsAvailable(filesystem);
}
};
}
}

View File

@@ -540,7 +540,6 @@ void WatchFacePineTimeStyle::Refresh() {
}
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
@@ -551,12 +550,10 @@ void WatchFacePineTimeStyle::Refresh() {
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
lv_label_set_text_fmt(temperature, "%d°", temp);
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
} else {
lv_label_set_text(temperature, "--");
lv_label_set_text(weatherIcon, Symbols::ban);
}
} else {
lv_label_set_text(temperature, "--");
lv_label_set_text(weatherIcon, Symbols::ban);
lv_obj_realign(temperature);
lv_obj_realign(weatherIcon);
}

View File

@@ -0,0 +1,259 @@
#include "displayapp/screens/WatchFaceSundial.h"
#include <lvgl/lvgl.h>
#include <cstdio>
// #include <cmath>
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "displayapp/InfiniTimeTheme.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/ble/SimpleWeatherService.h"
#include "components/settings/Settings.h"
#include "sunset/src/sunset.h"
using namespace Pinetime::Applications::Screens;
namespace {
int16_t HourLength = 70;
constexpr int16_t MinuteLength = 90;
constexpr int16_t SecondLength = 110;
constexpr int16_t SunDialVerticalOffset = 40;
// sin(90) = 1 so the value of _lv_trigo_sin(90) is the scaling factor
const auto LV_TRIG_SCALE = _lv_trigo_sin(90);
const lv_color_t DARK_GRAY = lv_color_make(48, 48, 48);
const lv_color_t DARK_ORANGE = lv_color_make(48, 26, 0);
int16_t Cosine(int16_t angle) {
return _lv_trigo_sin(angle + 90);
}
int16_t Sine(int16_t angle) {
return _lv_trigo_sin(angle);
}
int16_t CoordinateXRelocate(int16_t x) {
return (x + LV_HOR_RES / 2);
}
int16_t CoordinateYRelocateSundial(int16_t y) {
return std::abs(y - SunDialVerticalOffset);
}
lv_point_t CoordinateRelocateSundial(int16_t radius, int16_t angle) {
return lv_point_t {.x = CoordinateXRelocate(radius * static_cast<int32_t>(Sine(angle)) / LV_TRIG_SCALE),
.y = CoordinateYRelocateSundial(radius * static_cast<int32_t>(Cosine(angle)) / LV_TRIG_SCALE)};
}
}
WatchFaceSundial::WatchFaceSundial(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
notificationManager {notificationManager},
settingsController {settingsController}
{
// minor_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(minor_scales, 300, 51);
// lv_linemeter_set_angle_offset(minor_scales, 180);
// lv_obj_set_size(minor_scales, 240, 240);
// lv_obj_align(minor_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_line_width(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 1);
// lv_obj_set_style_local_scale_end_color(minor_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
// major_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(major_scales, 300, 11);
// lv_linemeter_set_angle_offset(major_scales, 180);
// lv_obj_set_size(major_scales, 240, 240);
// lv_obj_align(major_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 6);
// lv_obj_set_style_local_scale_end_line_width(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_color(major_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
// large_scales = lv_linemeter_create(lv_scr_act(), nullptr);
// lv_linemeter_set_scale(large_scales, 180, 3);
// lv_linemeter_set_angle_offset(large_scales, 180);
// lv_obj_set_size(large_scales, 240, 240);
// lv_obj_align(large_scales, nullptr, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_local_bg_opa(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
// lv_obj_set_style_local_scale_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 20);
// lv_obj_set_style_local_scale_end_line_width(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, 4);
// lv_obj_set_style_local_scale_end_color(large_scales, LV_LINEMETER_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_AQUA);
twelve = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_align(twelve, LV_LABEL_ALIGN_RIGHT);
lv_label_set_text_static(twelve, "XII");
lv_obj_align(twelve, NULL, LV_ALIGN_IN_TOP_RIGHT, -20, SunDialVerticalOffset-20);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
one = lv_label_create(lv_scr_act(), NULL);
lv_label_set_align(one, LV_LABEL_ALIGN_LEFT);
lv_label_set_text(one, "I");
lv_obj_align(one, NULL, LV_ALIGN_IN_TOP_LEFT, 20, SunDialVerticalOffset-20);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
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);
// Date - Day / Week day
label_date_day = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
lv_label_set_align(label_date_day, LV_LABEL_ALIGN_CENTER);
lv_obj_align(label_date_day, nullptr, LV_ALIGN_CENTER, 50, 0);
minute_body = lv_line_create(lv_scr_act(), nullptr);
minute_body_trace = lv_line_create(lv_scr_act(), nullptr);
hour_body = lv_line_create(lv_scr_act(), nullptr);
hour_body_trace = lv_line_create(lv_scr_act(), nullptr);
second_body = lv_line_create(lv_scr_act(), nullptr);
lv_style_init(&second_line_style);
lv_style_set_line_width(&second_line_style, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&second_line_style, LV_STATE_DEFAULT, LV_COLOR_RED);
lv_style_set_line_rounded(&second_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(second_body, LV_LINE_PART_MAIN, &second_line_style);
lv_style_init(&minute_line_style);
lv_style_set_line_width(&minute_line_style, LV_STATE_DEFAULT, 7);
lv_style_set_line_color(&minute_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&minute_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(minute_body, LV_LINE_PART_MAIN, &minute_line_style);
lv_style_init(&minute_line_style_trace);
lv_style_set_line_width(&minute_line_style_trace, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&minute_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
lv_style_init(&hour_line_style);
lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
lv_style_init(&hour_line_style_trace);
lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
UpdateClock();
Refresh();
}
WatchFaceSundial::~WatchFaceSundial() {
lv_task_del(taskRefresh);
lv_style_reset(&hour_line_style);
lv_style_reset(&hour_line_style_trace);
lv_style_reset(&minute_line_style);
lv_style_reset(&minute_line_style_trace);
lv_style_reset(&second_line_style);
lv_obj_clean(lv_scr_act());
}
void WatchFaceSundial::Refresh() {
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
UpdateClock();
if (currentDate.IsUpdated()) {
char const* MonthsString[] = {"--", "IANUARIUS","FEBRUARIUS","MARTIUS","APRILIS","MARTIUSIUNIUS","QUINTILIS","SEXTILIS","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"};
char const* DaysString[] = {"--", "LUNAE", "MARTIS", "MERCURII", "IOVIS", "VENERIS", "SATURNI", "SOLIS"};
char const* RomanNumeralsString[] = {"--", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
lv_label_set_text_fmt(label_date_day, "%s\n%s %s",
DaysString[static_cast<uint8_t>(dateTimeController.DayOfWeek())],
RomanNumeralsString[static_cast<uint8_t>(dateTimeController.Day())],
MonthsString[static_cast<uint8_t>(dateTimeController.Month())]);
lv_obj_align(label_date_day, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -20);
}
}
}
void WatchFaceSundial::UpdateClock() {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
location = settingsController.GetLocation();
if (sHour != hour || sMinute != minute) {
// sun.setPosition(settings.lat.toFloat(), settings.lon.toFloat(), settings.gmtOffset / 3600);
sun.setPosition((float)location.latitude, (float)location.longitude, location.tzOffset);
//from minutes past midnight
sun.setCurrentDate(dateTimeController.Year(), static_cast<uint8_t>(dateTimeController.Month())+1, dateTimeController.Day());
sun.setTZOffset(location.tzOffset);
minutesSunrise = sun.calcSunrise(); //360;
minutesSunset = sun.calcSunset(); //1080;
minutesDaytime = (minutesSunset - minutesSunrise);
minutesNighttime = (1440 - minutesDaytime);
minutesBeforeSunset = minutesSunset - (hour * 60 + minute); // i.e.zero degrees
HourLength = 90; // sundial hand length
int16_t hourAngle;
if(minutesBeforeSunset > 0 && minutesBeforeSunset < minutesDaytime) { // day (after sunrise)
hourAngle = 180.0 * minutesBeforeSunset / minutesDaytime + 90;
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
} else { // night (before sunrise or after sunset)
lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, DARK_GRAY);
lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, DARK_GRAY);
lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_ORANGE);
lv_obj_set_style_local_text_color(one, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
lv_obj_set_style_local_text_color(twelve, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, DARK_GRAY);
if(minutesBeforeSunset > minutesDaytime) { // before sunrise
hourAngle = 180.0 * (minutesBeforeSunset - minutesDaytime) / minutesNighttime + 90;
} else { // after sunset
hourAngle = 180 + 180.0 * minutesBeforeSunset / minutesNighttime + 90;
}
}
/*NRF_LOG_INFO("a: %d, la: %f, lo: %f, ri: %d, se: %d, be: %d",
hourAngle,
(float)location.latitude,
(float)location.longitude,
minutesSunrise,
minutesSunset,
minutesBeforeSunset);*/
sHour = hour;
sMinute = minute;
printf("H%d:%d lat%f lng%f z%d\n", hour, minute, (float)location.latitude, (float)location.longitude, location.tzOffset);
printf("%d before sunset, sunrise at %d:%d sunset at %d:%d angle %d\n",
minutesBeforeSunset,
minutesSunrise/60, minutesSunrise % 60,
minutesSunset/60, minutesSunset % 60, hourAngle);
hour_point_trace[0] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
hour_point_trace[1] = CoordinateRelocateSundial(HourLength, hourAngle);
hour_point[0] = CoordinateRelocateSundial(0, hourAngle);
hour_point[1] = CoordinateRelocateSundial(HourLength*.75, hourAngle);
lv_line_set_points(hour_body, hour_point, 2);
lv_line_set_points(hour_body_trace, hour_point_trace, 2);
}
}

View File

@@ -0,0 +1,111 @@
#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/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "displayapp/screens/BatteryIcon.h"
#include "utility/DirtyValue.h"
#include "sunset/src/sunset.h"
namespace Pinetime {
namespace Controllers {
class Settings;
class Battery;
class Ble;
class NotificationManager;
}
namespace Applications {
namespace Screens {
class WatchFaceSundial : public Screen {
public:
WatchFaceSundial(Controllers::DateTime& dateTimeController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController);
~WatchFaceSundial() override;
void Refresh() override;
private:
uint8_t sHour, sMinute, sSecond;
Utility::DirtyValue<uint8_t> batteryPercentRemaining {0};
Utility::DirtyValue<bool> isCharging {};
Utility::DirtyValue<bool> bleState {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
Utility::DirtyValue<bool> notificationState {false};
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* major_scales;
lv_obj_t* one;
lv_obj_t* twelve;
lv_obj_t* hour_body;
lv_obj_t* hour_body_trace;
lv_obj_t* minute_body;
lv_obj_t* minute_body_trace;
lv_obj_t* second_body;
lv_point_t hour_point[2];
lv_point_t hour_point_trace[2];
lv_point_t minute_point[2];
lv_point_t minute_point_trace[2];
lv_point_t second_point[2];
lv_style_t hour_line_style;
lv_style_t hour_line_style_trace;
lv_style_t minute_line_style;
lv_style_t minute_line_style_trace;
lv_style_t second_line_style;
lv_obj_t* label_date_day;
lv_obj_t* plugIcon;
lv_obj_t* notificationIcon;
lv_obj_t* bleIcon;
Controllers::Settings::Location location;
SunSet sun;
int16_t minutesSunrise;
int16_t minutesSunset;
int16_t minutesDaytime;
int16_t minutesNighttime;
int16_t minutesBeforeSunset;
const Controllers::DateTime& dateTimeController;
Controllers::NotificationManager& notificationManager;
Controllers::Settings& settingsController;
void drawWatchFaceModeNight();
void UpdateClock();
void SetBatteryIcon();
lv_task_t* taskRefresh;
};
}
template <>
struct WatchFaceTraits<WatchFace::Sundial> {
static constexpr WatchFace watchFace = WatchFace::Sundial;
static constexpr const char* name = "Sundial face";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::WatchFaceSundial(controllers.dateTimeController,
controllers.notificationManager,
controllers.settingsController);
};
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
return true;
}
};
}
}

View File

@@ -0,0 +1,151 @@
#include <lvgl/lvgl.h>
#include "displayapp/screens/WatchFaceTerminal.h"
#include "displayapp/screens/BatteryIcon.h"
#include "displayapp/screens/NotificationIcon.h"
#include "displayapp/screens/Symbols.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/heartrate/HeartRateController.h"
#include "components/motion/MotionController.h"
#include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens;
WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
const Controllers::Battery& batteryController,
const Controllers::Ble& bleController,
Controllers::NotificationManager& notificationManager,
Controllers::Settings& settingsController,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController)
: currentDateTime {{}},
dateTimeController {dateTimeController},
batteryController {batteryController},
bleController {bleController},
notificationManager {notificationManager},
settingsController {settingsController},
heartRateController {heartRateController},
motionController {motionController} {
batteryValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(batteryValue, true);
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
connectState = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(connectState, true);
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -100);
label_date = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label_date, true);
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
label_prompt_1 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_prompt_1, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -80);
lv_label_set_text_static(label_prompt_1, "user@watch:~ $ now");
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
label_time = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(label_time, true);
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -60);
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(heartbeatValue, true);
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);
stepValue = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_recolor(stepValue, true);
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
Refresh();
}
WatchFaceTerminal::~WatchFaceTerminal() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void WatchFaceTerminal::Refresh() {
powerPresent = batteryController.IsPowerPresent();
batteryPercentRemaining = batteryController.PercentRemaining();
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
lv_label_set_text_fmt(batteryValue, "[BATT]#387b54 %d%%", batteryPercentRemaining.Get());
if (batteryController.IsPowerPresent()) {
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
}
}
bleState = bleController.IsConnected();
bleRadioEnabled = bleController.IsRadioEnabled();
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
if (!bleRadioEnabled.Get()) {
lv_label_set_text_static(connectState, "[STAT]#0082fc Disabled#");
} else {
if (bleState.Get()) {
lv_label_set_text_static(connectState, "[STAT]#0082fc Connected#");
} else {
lv_label_set_text_static(connectState, "[STAT]#0082fc Disconnected#");
}
}
}
notificationState = notificationManager.AreNewNotificationsAvailable();
if (notificationState.IsUpdated()) {
if (notificationState.Get()) {
lv_label_set_text_static(notificationIcon, "You have mail.");
} else {
lv_label_set_text_static(notificationIcon, "");
}
}
currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
if (currentDateTime.IsUpdated()) {
uint8_t hour = dateTimeController.Hours();
uint8_t minute = dateTimeController.Minutes();
uint8_t second = dateTimeController.Seconds();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
char ampmChar[3] = "AM";
if (hour == 0) {
hour = 12;
} else if (hour == 12) {
ampmChar[0] = 'P';
} else if (hour > 12) {
hour = hour - 12;
ampmChar[0] = 'P';
}
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
} else {
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
}
currentDate = std::chrono::time_point_cast<std::chrono::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());
}
}

View File

@@ -0,0 +1,92 @@
#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 {};
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::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;
}
};
}
}

View File

@@ -0,0 +1,198 @@
#include "displayapp/screens/Weather.h"
#include <lvgl/lvgl.h>
#include "components/ble/SimpleWeatherService.h"
#include "components/datetime/DateTimeController.h"
#include "components/settings/Settings.h"
#include "displayapp/DisplayApp.h"
#include "displayapp/screens/WeatherSymbols.h"
#include "displayapp/InfiniTimeTheme.h"
using namespace Pinetime::Applications::Screens;
namespace {
lv_color_t TemperatureColor(int16_t temperature) {
if (temperature <= 0) { // freezing
return Colors::blue;
} else if (temperature <= 400) { // ice
return LV_COLOR_CYAN;
} else if (temperature >= 2700) { // hot
return Colors::deepOrange;
}
return Colors::orange; // normal
}
uint8_t TemperatureStyle(int16_t temperature) {
if (temperature <= 0) { // freezing
return LV_TABLE_PART_CELL3;
} else if (temperature <= 400) { // ice
return LV_TABLE_PART_CELL4;
} else if (temperature >= 2700) { // hot
return LV_TABLE_PART_CELL6;
}
return LV_TABLE_PART_CELL5; // normal
}
int16_t RoundTemperature(int16_t temp) {
return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
}
}
Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
: settingsController {settingsController}, weatherService {weatherService} {
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_WHITE);
lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_label_set_text(temperature, "---");
lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30);
lv_obj_set_auto_realign(temperature, true);
minTemperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
lv_label_set_text(minTemperature, "");
lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0);
lv_obj_set_auto_realign(minTemperature, true);
maxTemperature = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg);
lv_label_set_text(maxTemperature, "");
lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
lv_obj_set_auto_realign(maxTemperature, true);
condition = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray);
lv_label_set_text(condition, "");
lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10);
lv_obj_set_auto_realign(condition, true);
icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
lv_label_set_text(icon, "");
lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0);
lv_obj_set_auto_realign(icon, true);
forecast = lv_table_create(lv_scr_act(), nullptr);
lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays);
lv_table_set_row_cnt(forecast, 4);
// LV_TABLE_PART_CELL1: Default table style
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray);
// LV_TABLE_PART_CELL2: Condition icon
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons);
// LV_TABLE_PART_CELL3: Freezing
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue);
// LV_TABLE_PART_CELL4: Ice
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN);
// LV_TABLE_PART_CELL5: Normal
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange);
// LV_TABLE_PART_CELL6: Hot
lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK);
lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange);
lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
lv_table_set_col_width(forecast, i, 48);
lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2);
lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER);
lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER);
}
taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this);
Refresh();
}
Weather::~Weather() {
lv_task_del(taskRefresh);
lv_obj_clean(lv_scr_act());
}
void Weather::Refresh() {
currentWeather = weatherService.Current();
if (currentWeather.IsUpdated()) {
auto optCurrentWeather = currentWeather.Get();
if (optCurrentWeather) {
int16_t temp = optCurrentWeather->temperature;
int16_t minTemp = optCurrentWeather->minTemperature;
int16_t maxTemp = optCurrentWeather->maxTemperature;
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
char tempUnit = 'C';
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
tempUnit = 'F';
}
lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
} else {
lv_label_set_text(icon, "");
lv_label_set_text(condition, "");
lv_label_set_text(temperature, "---");
lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
lv_label_set_text(minTemperature, "");
lv_label_set_text(maxTemperature, "");
}
}
currentForecast = weatherService.GetForecast();
if (currentForecast.IsUpdated()) {
auto optCurrentForecast = currentForecast.Get();
if (optCurrentForecast) {
std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
int16_t minTemp = optCurrentForecast->days[i].minTemperature;
lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
}
uint8_t wday = localTime.tm_wday + i + 1;
if (wday > 7) {
wday -= 7;
}
maxTemp = RoundTemperature(maxTemp);
minTemp = RoundTemperature(minTemp);
const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));
// Pad cells based on the largest number of digits on each column
char maxPadding[3] = " ";
char minPadding[3] = " ";
int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp);
if (diff <= 0) {
maxPadding[-diff] = '\0';
minPadding[0] = '\0';
} else {
maxPadding[0] = '\0';
minPadding[diff] = '\0';
}
lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp);
lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp);
}
} else {
for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
lv_table_set_cell_value(forecast, 0, i, "");
lv_table_set_cell_value(forecast, 1, i, "");
lv_table_set_cell_value(forecast, 2, i, "");
lv_table_set_cell_value(forecast, 3, i, "");
lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1);
lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1);
}
}
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <cstdint>
#include <lvgl/lvgl.h>
#include "displayapp/screens/Screen.h"
#include "components/ble/SimpleWeatherService.h"
#include "displayapp/apps/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
#include "utility/DirtyValue.h"
namespace Pinetime {
namespace Controllers {
class Settings;
}
namespace Applications {
namespace Screens {
class Weather : public Screen {
public:
Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService);
~Weather() override;
void Refresh() override;
private:
Controllers::Settings& settingsController;
Controllers::SimpleWeatherService& weatherService;
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::Forecast>> currentForecast {};
lv_obj_t* icon;
lv_obj_t* condition;
lv_obj_t* temperature;
lv_obj_t* minTemperature;
lv_obj_t* maxTemperature;
lv_obj_t* forecast;
lv_task_t* taskRefresh;
};
}
template <>
struct AppTraits<Apps::Weather> {
static constexpr Apps app = Apps::Weather;
static constexpr const char* icon = Screens::Symbols::cloudSunRain;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Weather(controllers.settingsController, *controllers.weatherController);
};
};
}
}

View File

@@ -0,0 +1,61 @@
#include "displayapp/screens/WeatherSymbols.h"
const char* Pinetime::Applications::Screens::Symbols::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;
}
}
const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
switch (icon) {
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
return "Clear sky";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
return "Few clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
return "Scattered clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
return "Broken clouds";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
return "Shower rain";
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
return "Rain";
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
return "Thunderstorm";
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
return "Snow";
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
return "Mist";
default:
return "";
}
}

View File

@@ -6,40 +6,8 @@ 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;
}
}
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
}
}
}

View File

@@ -36,17 +36,19 @@ namespace {
SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
: app {app},
settings {settingsController},
checkboxList(
0,
1,
"Bluetooth",
Symbols::bluetooth,
settingsController.GetBleRadioEnabled() ? 0 : 1,
[&settings = settingsController](uint32_t index) {
[this](uint32_t index) {
const bool priorMode = settings.GetBleRadioEnabled();
const bool newMode = options[index].radioEnabled;
if (newMode != priorMode) {
settings.SetBleRadioEnabled(newMode);
this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
}
},
CreateOptionArray()) {
@@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine
SettingBluetooth::~SettingBluetooth() {
lv_obj_clean(lv_scr_act());
// Pushing the message in the OnValueChanged function causes a freeze?
app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
}

View File

@@ -20,6 +20,7 @@ namespace Pinetime {
private:
DisplayApp* app;
Pinetime::Controllers::Settings& settings;
CheckboxList checkboxList;
};
}

View File

@@ -9,6 +9,9 @@
using namespace Pinetime::Applications::Screens;
namespace {
constexpr int16_t POS_X_LAT = -80;
constexpr int16_t POS_X_LONG = 0;
constexpr int16_t POS_X_TZ = 80;
constexpr int16_t POS_Y_TEXT = 25;
void ValueChangedHandler(void* userData) {
@@ -27,6 +30,7 @@ SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsContro
lv_obj_t* icon = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
lv_label_set_text_static(icon, Symbols::map);
lv_label_set_align(icon, LV_LABEL_ALIGN_CENTER);
lv_obj_align(icon, title, LV_ALIGN_OUT_LEFT_MID, -10, 0);
@@ -34,21 +38,18 @@ SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsContro
Controllers::Settings::Location loc = settingsController.GetLocation();
latCounter.Create();
latCounter.SetWidth(80);
latCounter.SetValue(loc.latitude);
lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, -90, POS_Y_TEXT);
lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LAT, POS_Y_TEXT);
latCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
longCounter.Create();
longCounter.SetWidth(110);
longCounter.SetValue(loc.longitude);
lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, -5, POS_Y_TEXT);
lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LONG, POS_Y_TEXT);
longCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
tzCounter.Create();
tzCounter.SetWidth(60);
tzCounter.SetValue(loc.tzOffset);
lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, 75, POS_Y_TEXT);
lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_TZ, POS_Y_TEXT);
tzCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
UpdateScreen();
@@ -61,9 +62,9 @@ SettingLocation::~SettingLocation() {
void SettingLocation::UpdateScreen() {
Controllers::Settings::Location loc = {
latitude: (int16_t)latCounter.GetValue(),
longitude: (int16_t)longCounter.GetValue(),
tzOffset: (int8_t)tzCounter.GetValue(),
latitude: (int16_t)latCounter.GetValue(),
longitude: (int16_t)longCounter.GetValue(),
tzOffset: (int8_t)tzCounter.GetValue(),
};
settingsController.SetLocation(loc);
}

View File

@@ -22,9 +22,9 @@ namespace Pinetime {
private:
Controllers::Settings& settingsController;
Widgets::Counter latCounter = Widgets::Counter(-90, 90, jetbrains_mono_42);
Widgets::Counter longCounter = Widgets::Counter(-180, 180, jetbrains_mono_42);
Widgets::Counter tzCounter = Widgets::Counter(-12, 12, jetbrains_mono_42);
Widgets::Counter latCounter = Widgets::Counter(-90, 90, jetbrains_mono_bold_20);
Widgets::Counter longCounter = Widgets::Counter(-180, 180, jetbrains_mono_bold_20);
Widgets::Counter tzCounter = Widgets::Counter(-12, 12, jetbrains_mono_bold_20);
};
}
}

View File

@@ -42,8 +42,8 @@ SettingSetTime::SettingSetTime(Pinetime::Controllers::DateTime& dateTimeControll
lv_obj_t* staticLabel = lv_label_create(lv_scr_act(), nullptr);
lv_obj_set_style_local_text_font(staticLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_label_set_text_static(staticLabel, ":00");
lv_obj_align(staticLabel, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -33, POS_Y_TEXT);
lv_label_set_text_static(staticLabel, "00:00:00");
lv_obj_align(staticLabel, lv_scr_act(), LV_ALIGN_CENTER, 0, POS_Y_TEXT);
hourCounter.Create();
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {

View File

@@ -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() {

View File

@@ -9,6 +9,37 @@ using namespace Pinetime::Applications::Screens;
constexpr const char* SettingWatchFace::title;
constexpr const char* SettingWatchFace::symbol;
namespace {
uint32_t IndexOf(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
Pinetime::Applications::WatchFace watchface) {
size_t index = 0;
auto found = std::find_if(watchfaces.begin(),
watchfaces.end(),
[&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) {
const bool result = item.watchface == watchface;
if (!result) {
index++;
}
return result;
});
if (found == watchfaces.end()) {
index = 0;
}
return index;
}
Pinetime::Applications::WatchFace IndexToWatchFace(const std::array<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
size_t index) {
if (index >= watchfaces.size()) {
return watchfaces[0].watchface;
}
return watchfaces[index].watchface;
}
}
auto SettingWatchFace::CreateScreenList() const {
std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
for (size_t i = 0; i < screens.size(); i++) {
@@ -20,7 +51,7 @@ auto SettingWatchFace::CreateScreenList() const {
}
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem)
: app {app},
@@ -44,7 +75,8 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
watchfacesOnThisScreen[i] = {"", false};
} else {
watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)];
auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)];
watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled};
}
}
@@ -53,9 +85,9 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
nScreens,
title,
symbol,
static_cast<uint32_t>(settingsController.GetWatchFace()),
[&settings = settingsController](uint32_t index) {
settings.SetWatchFace(static_cast<WatchFace>(index));
static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
[this, &settings = settingsController](uint32_t index) {
settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
settings.SaveSettings();
},
watchfacesOnThisScreen);

View File

@@ -19,8 +19,14 @@ namespace Pinetime {
class SettingWatchFace : public Screen {
public:
struct Item {
const char* name;
WatchFace watchface;
bool enabled;
};
SettingWatchFace(DisplayApp* app,
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem);
~SettingWatchFace() override;
@@ -33,7 +39,7 @@ namespace Pinetime {
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
static constexpr int settingsPerScreen = 4;
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> watchfaceItems;
std::array<Item, UserWatchFaceTypes::Count> watchfaceItems;
static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
Controllers::Settings& settingsController;

View File

@@ -136,7 +136,8 @@ void Counter::Create() {
static constexpr uint8_t padding = 5;
const uint8_t width = std::max(lv_obj_get_width(number) + padding * 2, 58);
containerHeight = btnHeight * 2 + lv_obj_get_height(number) + padding * 2;
static constexpr uint8_t btnHeight = 50;
const uint8_t containerHeight = btnHeight * 2 + lv_obj_get_height(number) + padding * 2;
lv_obj_set_size(counterContainer, width, containerHeight);

View File

@@ -18,15 +18,6 @@ namespace Pinetime {
void EnableMonthMode();
void SetMax(int newMax);
void SetValueChangedEventCallback(void* userData, void (*handler)(void* userData));
void SetWidth(uint8_t width) {
lv_obj_set_size(counterContainer, width, containerHeight);
lv_obj_set_size(upBtn, width, btnHeight);
lv_obj_set_size(downBtn, width, btnHeight);
linePoints[0] = {0, 0};
linePoints[1] = {width, 0};
lv_line_set_points(upperLine, linePoints, 2);
lv_line_set_points(lowerLine, linePoints, 2);
}
int GetValue() const {
return value;

View File

@@ -22,6 +22,16 @@ namespace {
void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
nrf_delay_us(period_us);
}
// Scale factors to convert accelerometer counts to milli-g
// from datasheet: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
// The array index to use is stored in accel_conf.range
constexpr int16_t accelScaleFactors[] = {
[BMA4_ACCEL_RANGE_2G] = 1024, // LSB/g +/- 2g range
[BMA4_ACCEL_RANGE_4G] = 512, // LSB/g +/- 4g range
[BMA4_ACCEL_RANGE_8G] = 256, // LSB/g +/- 8g range
[BMA4_ACCEL_RANGE_16G] = 128 // LSB/g +/- 16g range
};
}
Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
@@ -74,7 +84,6 @@ void Bma421::Init() {
if (ret != BMA4_OK)
return;
struct bma4_accel_config accel_conf;
accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
accel_conf.range = BMA4_ACCEL_RANGE_2G;
accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
@@ -102,19 +111,21 @@ void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) {
Bma421::Values Bma421::Process() {
if (not isOk)
return {};
struct bma4_accel rawData;
struct bma4_accel data;
bma4_read_accel_xyz(&data, &bma);
bma4_read_accel_xyz(&rawData, &bma);
// Scale the measured ADC counts to units of 'binary milli-g'
// where 1g = 1024 'binary milli-g' units.
// See https://github.com/InfiniTimeOrg/InfiniTime/pull/1950 for
// discussion of why we opted for scaling to 1024 rather than 1000.
data.x = 1024 * rawData.x / accelScaleFactors[accel_conf.range];
data.y = 1024 * rawData.y / accelScaleFactors[accel_conf.range];
data.z = 1024 * rawData.z / accelScaleFactors[accel_conf.range];
uint32_t steps = 0;
bma423_step_counter_output(&steps, &bma);
int32_t temperature;
bma4_get_temperature(&temperature, BMA4_DEG, &bma);
temperature = temperature / 1000;
uint8_t activity = 0;
bma423_activity_output(&activity, &bma);
// X and Y axis are swapped because of the way the sensor is mounted in the PineTime
return {steps, data.y, data.x, data.z};
}

View File

@@ -41,6 +41,7 @@ namespace Pinetime {
TwiMaster& twiMaster;
uint8_t deviceAddress = 0x18;
struct bma4_dev bma;
struct bma4_accel_config accel_conf; // Store the device configuration for later reference.
bool isOk = false;
bool isResetOk = false;
DeviceTypes deviceType = DeviceTypes::Unknown;

View File

@@ -19,7 +19,7 @@ namespace {
}
/** Driver for the HRS3300 heart rate sensor.
* Original implementation from wasp-os : https://github.com/daniel-thompson/wasp-os/blob/master/wasp/drivers/hrs3300.py
* Original implementation from wasp-os : https://github.com/wasp-os/wasp-os/blob/master/wasp/drivers/hrs3300.py
*
* Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
*/

View File

@@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn {
nrf_gpio_pin_set(pinCsn);
}
bool Spi::Write(const uint8_t* data, size_t size) {
return spiMaster.Write(pinCsn, data, size);
bool Spi::Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
return spiMaster.Write(pinCsn, data, size, preTransactionHook);
}
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {

View File

@@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <functional>
#include "drivers/SpiMaster.h"
namespace Pinetime {
@@ -14,7 +15,7 @@ namespace Pinetime {
Spi& operator=(Spi&&) = delete;
bool Init();
bool Write(const uint8_t* data, size_t size);
bool Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
void Sleep();

View File

@@ -94,32 +94,45 @@ bool SpiMaster::Init() {
return true;
}
void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
// Create an event when SCK toggles.
NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) |
(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
void SpiMaster::SetupWorkaroundForErratum58() {
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE,
.pull = NRF_GPIO_PIN_NOPULL,
.is_watcher = false,
.hi_accuracy = true,
.skip_gpio_setup = true};
if (!workaroundActive) {
// Create an event when SCK toggles.
APP_ERROR_CHECK(nrfx_gpiote_in_init(pin, &gpioteCfg, NULL));
nrfx_gpiote_in_event_enable(pin, false);
// Stop the spim instance when SCK toggles.
nrf_ppi_channel_endpoint_setup(workaroundPpi, nrfx_gpiote_in_event_addr_get(pin), spiBaseAddress->TASKS_STOP);
nrf_ppi_channel_enable(workaroundPpi);
}
// Stop the spim instance when SCK toggles.
NRF_PPI->CH[ppi_channel].EEP = (uint32_t) &NRF_GPIOTE->EVENTS_IN[gpiote_channel];
NRF_PPI->CH[ppi_channel].TEP = (uint32_t) &spim->TASKS_STOP;
NRF_PPI->CHENSET = 1U << ppi_channel;
spiBaseAddress->EVENTS_END = 0;
// Disable IRQ
spim->INTENCLR = (1 << 6);
spim->INTENCLR = (1 << 1);
spim->INTENCLR = (1 << 19);
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
workaroundActive = true;
}
void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
NRF_GPIOTE->CONFIG[gpiote_channel] = 0;
NRF_PPI->CH[ppi_channel].EEP = 0;
NRF_PPI->CH[ppi_channel].TEP = 0;
NRF_PPI->CHENSET = ppi_channel;
void SpiMaster::DisableWorkaroundForErratum58() {
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
if (workaroundActive) {
nrfx_gpiote_in_uninit(pin);
nrf_ppi_channel_disable(workaroundPpi);
}
spiBaseAddress->EVENTS_END = 0;
spim->INTENSET = (1 << 6);
spim->INTENSET = (1 << 1);
spim->INTENSET = (1 << 19);
// Enable IRQ
spiBaseAddress->INTENSET = (1 << 6);
spiBaseAddress->INTENSET = (1 << 1);
spiBaseAddress->INTENSET = (1 << 19);
workaroundActive = false;
}
void SpiMaster::OnEndEvent() {
@@ -136,17 +149,11 @@ void SpiMaster::OnEndEvent() {
spiBaseAddress->TASKS_START = 1;
} else {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (taskToNotify != nullptr) {
vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
BaseType_t xHigherPriorityTaskWoken2 = pdFALSE;
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
@@ -173,21 +180,23 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->EVENTS_END = 0;
}
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
if (data == nullptr)
return false;
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
ASSERT(ok == true);
taskToNotify = xTaskGetCurrentTaskHandle();
this->pinCsn = pinCsn;
if (size == 1) {
SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0);
SetupWorkaroundForErratum58();
} else {
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
}
if (preTransactionHook != nullptr) {
preTransactionHook();
}
nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = (uint32_t) data;
@@ -205,7 +214,7 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
xSemaphoreGive(mutex);
}
@@ -216,10 +225,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);
@@ -265,10 +272,8 @@ void SpiMaster::Wakeup() {
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
DisableWorkaroundForErratum58();
spiBaseAddress->INTENCLR = (1 << 6);
spiBaseAddress->INTENCLR = (1 << 1);
spiBaseAddress->INTENCLR = (1 << 19);

View File

@@ -1,10 +1,13 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <FreeRTOS.h>
#include <semphr.h>
#include <task.h>
#include "nrfx_gpiote.h"
#include "nrf_ppi.h"
namespace Pinetime {
namespace Drivers {
@@ -31,7 +34,7 @@ namespace Pinetime {
SpiMaster& operator=(SpiMaster&&) = delete;
bool Init();
bool Write(uint8_t pinCsn, const uint8_t* data, size_t size);
bool Write(uint8_t pinCsn, const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize);
bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
@@ -43,8 +46,8 @@ namespace Pinetime {
void Wakeup();
private:
void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void SetupWorkaroundForErratum58();
void DisableWorkaroundForErratum58();
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
void PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size);
@@ -56,8 +59,9 @@ namespace Pinetime {
volatile uint32_t currentBufferAddr = 0;
volatile size_t currentBufferSize = 0;
volatile TaskHandle_t taskToNotify;
SemaphoreHandle_t mutex = nullptr;
static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0;
bool workaroundActive = false;
};
}
}

View File

@@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() {
void SpiNorFlash::Sleep() {
auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
spi.Write(&cmd, sizeof(uint8_t));
spi.Write(&cmd, sizeof(uint8_t), nullptr);
NRF_LOG_INFO("[SpiNorFlash] Sleep")
}

View File

@@ -1,8 +1,8 @@
#include "drivers/St7789.h"
#include <hal/nrf_gpio.h>
#include <libraries/delay/nrf_delay.h>
#include <nrfx_log.h>
#include "drivers/Spi.h"
#include "task.h"
using namespace Pinetime::Drivers;
@@ -29,37 +29,77 @@ void St7789::Init() {
DisplayOn();
}
void St7789::WriteCommand(uint8_t cmd) {
nrf_gpio_pin_clear(pinDataCommand);
WriteSpi(&cmd, 1);
}
void St7789::WriteData(uint8_t data) {
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(&data, 1);
WriteData(&data, 1);
}
void St7789::WriteSpi(const uint8_t* data, size_t size) {
spi.Write(data, size);
void St7789::WriteData(const uint8_t* data, size_t size) {
WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
nrf_gpio_pin_set(pinDataCommand);
});
}
void St7789::WriteCommand(uint8_t data) {
WriteCommand(&data, 1);
}
void St7789::WriteCommand(const uint8_t* data, size_t size) {
WriteSpi(data, size, [pinDataCommand = pinDataCommand]() {
nrf_gpio_pin_clear(pinDataCommand);
});
}
void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
spi.Write(data, size, preTransactionHook);
}
void St7789::SoftwareReset() {
EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
nrf_delay_ms(150);
// If sleep in: must wait 120ms before sleep out can sent (see driver datasheet)
// Unconditionally wait as software reset doesn't need to be performant
sleepIn = true;
lastSleepExit = xTaskGetTickCount();
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::SleepOut() {
if (!sleepIn) {
return;
}
WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
// Wait 5ms for clocks to stabilise
// pdMS rounds down => 6 used here
vTaskDelay(pdMS_TO_TICKS(6));
// Cannot send sleep in or software reset for 120ms
lastSleepExit = xTaskGetTickCount();
sleepIn = false;
}
void St7789::EnsureSleepOutPostDelay() {
TickType_t delta = xTaskGetTickCount() - lastSleepExit;
// Due to timer wraparound, there is a chance of delaying when not necessary
// It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad
if (delta < pdMS_TO_TICKS(125)) {
vTaskDelay(pdMS_TO_TICKS(125) - delta);
}
}
void St7789::SleepIn() {
if (sleepIn) {
return;
}
EnsureSleepOutPostDelay();
WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
// Wait 5ms for clocks to stabilise
// pdMS rounds down => 6 used here
vTaskDelay(pdMS_TO_TICKS(6));
sleepIn = true;
}
void St7789::ColMod() {
WriteCommand(static_cast<uint8_t>(Commands::ColMod));
WriteData(0x55);
nrf_delay_ms(10);
}
void St7789::MemoryDataAccessControl() {
@@ -96,12 +136,10 @@ void St7789::RowAddressSet() {
void St7789::DisplayInversionOn() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
nrf_delay_ms(10);
}
void St7789::NormalModeOn() {
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
nrf_delay_ms(10);
}
void St7789::DisplayOn() {
@@ -120,12 +158,11 @@ void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
WriteData(y0 & 0xff);
WriteData(y1 >> 8);
WriteData(y1 & 0xff);
WriteToRam();
}
void St7789::WriteToRam() {
void St7789::WriteToRam(const uint8_t* data, size_t size) {
WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
WriteData(data, size);
}
void St7789::SetVdv() {
@@ -137,17 +174,6 @@ void St7789::SetVdv() {
void St7789::DisplayOff() {
WriteCommand(static_cast<uint8_t>(Commands::DisplayOff));
nrf_delay_ms(500);
}
void St7789::VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollDefinition));
WriteData(topFixedLines >> 8u);
WriteData(topFixedLines & 0x00ffu);
WriteData(scrollLines >> 8u);
WriteData(scrollLines & 0x00ffu);
WriteData(bottomFixedLines >> 8u);
WriteData(bottomFixedLines & 0x00ffu);
}
void St7789::VerticalScrollStartAddress(uint16_t line) {
@@ -160,27 +186,20 @@ void St7789::VerticalScrollStartAddress(uint16_t line) {
void St7789::Uninit() {
}
void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) {
if (x >= Width || y >= Height) {
return;
}
SetAddrWindow(x, y, x + 1, y + 1);
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(reinterpret_cast<const uint8_t*>(&color), 2);
}
void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) {
SetAddrWindow(x, y, x + width - 1, y + height - 1);
nrf_gpio_pin_set(pinDataCommand);
WriteSpi(data, size);
WriteToRam(data, size);
}
void St7789::HardwareReset() {
nrf_gpio_pin_clear(pinReset);
nrf_delay_ms(10);
vTaskDelay(pdMS_TO_TICKS(1));
nrf_gpio_pin_set(pinReset);
// If hardware reset started while sleep out, reset time may be up to 120ms
// Unconditionally wait as hardware reset doesn't need to be performant
sleepIn = true;
lastSleepExit = xTaskGetTickCount();
vTaskDelay(pdMS_TO_TICKS(125));
}
void St7789::Sleep() {

View File

@@ -1,6 +1,9 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <FreeRTOS.h>
namespace Pinetime {
namespace Drivers {
@@ -16,9 +19,7 @@ namespace Pinetime {
void Init();
void Uninit();
void DrawPixel(uint16_t x, uint16_t y, uint32_t color);
void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void VerticalScrollStartAddress(uint16_t line);
void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size);
@@ -31,23 +32,27 @@ namespace Pinetime {
uint8_t pinDataCommand;
uint8_t pinReset;
uint8_t verticalScrollingStartAddress = 0;
bool sleepIn;
TickType_t lastSleepExit;
void HardwareReset();
void SoftwareReset();
void SleepOut();
void EnsureSleepOutPostDelay();
void SleepIn();
void ColMod();
void MemoryDataAccessControl();
void DisplayInversionOn();
void NormalModeOn();
void WriteToRam();
void WriteToRam(const uint8_t* data, size_t size);
void DisplayOn();
void DisplayOff();
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void SetVdv();
void WriteCommand(uint8_t cmd);
void WriteSpi(const uint8_t* data, size_t size);
void WriteCommand(const uint8_t* data, size_t size);
void WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook);
enum class Commands : uint8_t {
SoftwareReset = 0x01,
@@ -67,6 +72,7 @@ namespace Pinetime {
VdvSet = 0xc4,
};
void WriteData(uint8_t data);
void WriteData(const uint8_t* data, size_t size);
void ColumnAddressSet();
static constexpr uint16_t Width = 240;

View File

@@ -103,10 +103,7 @@ void HeartRateTask::Work() {
void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
/* Actual macro used here is port specific. */
// TODO : should I do something here?
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void HeartRateTask::StartMeasurement() {

View File

@@ -417,8 +417,6 @@ typedef void* lv_indev_drv_user_data_t; /*Type of user data in the in
LV_FONT_DECLARE(jetbrains_mono_extrabold_compressed) \
LV_FONT_DECLARE(jetbrains_mono_42) \
LV_FONT_DECLARE(jetbrains_mono_76) \
LV_FONT_DECLARE(noto_serif_cjk_20) \
LV_FONT_DECLARE(noto_serif_cjk_15) \
LV_FONT_DECLARE(open_sans_light) \
LV_FONT_DECLARE(fontawesome_weathericons) \
LV_FONT_DECLARE(lv_font_sys_48)
@@ -731,7 +729,9 @@ typedef void* lv_obj_user_data_t;
#define LV_USE_TABLE 1
#if LV_USE_TABLE
#define LV_TABLE_COL_MAX 12
#define LV_TABLE_CELL_STYLE_CNT 5
#define LV_TABLE_CELL_STYLE_CNT 6
#define LV_TABLE_PART_CELL5 5
#define LV_TABLE_PART_CELL6 6
#endif

Some files were not shown because too many files have changed in this diff Show More