Compare commits
67 Commits
wb/fuzzy-n
...
wb/roman-n
Author | SHA1 | Date | |
---|---|---|---|
49c801b827 | |||
|
f6a5588544 | ||
|
41518cc529 | ||
|
c20db14c43 | ||
|
9520b2ec9b | ||
|
8cf38a3469 | ||
|
83cae9b5bc | ||
|
bb743689e1 | ||
|
4b854a5d09 | ||
|
9e406c70f9 | ||
|
0c87bc27b2 | ||
|
0dcfb2edb7 | ||
|
06c6935315 | ||
|
47c104643d | ||
|
7b1110187e | ||
|
7e460d3c80 | ||
|
7a9211587a | ||
|
24e6a2f8ab | ||
|
9a7ba405e1 | ||
|
940cd3459f | ||
|
869bec8f88 | ||
|
ee925200c3 | ||
|
079e676baf | ||
|
6b5235c301 | ||
|
57e625d4dc | ||
|
9a5f516c42 | ||
|
4ca2112890 | ||
|
7dbb8f54c6 | ||
|
14c6270903 | ||
|
ef38472278 | ||
|
4c9d5332c7 | ||
|
9aa0bc8ecf | ||
|
4d86a3c6c1 | ||
|
3b4b5a5163 | ||
|
f2df0c45ef | ||
|
636af4d33d | ||
|
70f6604878 | ||
|
004b2bf3a0 | ||
|
6ab512a6b6 | ||
|
68ae335a97 | ||
|
1857b02efa | ||
|
f422929d8c | ||
|
5d971690cb | ||
|
b0a0afdd4b | ||
|
93d7da1691 | ||
|
fa1af55a45 | ||
|
c2c53bc6ab | ||
|
a49dc15a6e | ||
|
4c274421d9 | ||
|
2db920599e | ||
|
44be356dc2 | ||
|
2135e12b33 | ||
|
a6cd3679eb | ||
|
074df0526f | ||
|
a40168a9d7 | ||
|
a481af06cf | ||
|
034d83fe6b | ||
|
264b5bed43 | ||
|
0503248a25 | ||
|
c634a4e3b4 | ||
|
ab8e267e28 | ||
|
ecf2f564f7 | ||
|
6505336d60 | ||
|
25b3e2461d | ||
|
72c992c84e | ||
|
22f6d4a40b | ||
|
12acef6a71 |
32
.devcontainer.json
Normal file
32
.devcontainer.json
Normal 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"
|
||||||
|
}
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
cmake --build /workspaces/Pinetime/build --config Release -- -j6 pinetime-app
|
|
|
@ -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
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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}
|
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -3,7 +3,7 @@ name: CI
|
||||||
# Run this workflow whenever the build may be affected
|
# Run this workflow whenever the build may be affected
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, wb/fuzzy, wb/fuzzy-norm ]
|
branches: [ main ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'doc/**'
|
- 'doc/**'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -50,3 +50,5 @@ src/arm-none-eabi
|
||||||
|
|
||||||
# clangd
|
# clangd
|
||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
|
nRF5_SDK/
|
||||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -10,3 +10,6 @@
|
||||||
[submodule "src/libs/arduinoFFT"]
|
[submodule "src/libs/arduinoFFT"]
|
||||||
path = src/libs/arduinoFFT
|
path = src/libs/arduinoFFT
|
||||||
url = https://github.com/kosme/arduinoFFT.git
|
url = https://github.com/kosme/arduinoFFT.git
|
||||||
|
[submodule "src/libs/sunset"]
|
||||||
|
path = src/libs/sunset
|
||||||
|
url = https://github.com/buelowp/sunset.git
|
||||||
|
|
20
.vscode/c_cpp_properties.json
vendored
20
.vscode/c_cpp_properties.json
vendored
|
@ -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": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "nrfCC",
|
"name": "nrfCC",
|
||||||
|
@ -14,6 +19,21 @@
|
||||||
"intelliSenseMode": "linux-gcc-arm",
|
"intelliSenseMode": "linux-gcc-arm",
|
||||||
"configurationProvider": "ms-vscode.cpp-tools",
|
"configurationProvider": "ms-vscode.cpp-tools",
|
||||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
|
"version": 4
|
||||||
|
|
6
.vscode/cmake-kits.json
vendored
Normal file
6
.vscode/cmake-kits.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "InfiniTime Compiler",
|
||||||
|
"environmentSetupScript": "${workspaceFolder}/docker/build.sh"
|
||||||
|
}
|
||||||
|
]
|
45
.vscode/launch.json
vendored
45
.vscode/launch.json
vendored
|
@ -1,20 +1,18 @@
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Debug - Openocd docker Remote",
|
"name": "Debug - Openocd docker Remote",
|
||||||
"type":"cortex-debug",
|
"type": "cortex-debug",
|
||||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"executable": "${command:cmake.launchTargetPath}",
|
"executable": "${command:cmake.launchTargetPath}",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"servertype": "external",
|
"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
|
// Connect to an already running OpenOCD instance
|
||||||
"gdbTarget": "host.docker.internal:3333",
|
"gdbTarget": "host.docker.internal:3333",
|
||||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||||
"runToMain": true,
|
"runToEntryPoint": "main",
|
||||||
// Work around for stopping at main on restart
|
// Work around for stopping at main on restart
|
||||||
"postRestartCommands": [
|
"postRestartCommands": [
|
||||||
"break main",
|
"break main",
|
||||||
|
@ -23,18 +21,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug - Openocd Local",
|
"name": "Debug - Openocd Local",
|
||||||
"type":"cortex-debug",
|
"type": "cortex-debug",
|
||||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"executable": "${command:cmake.launchTargetPath}",
|
"executable": "${command:cmake.launchTargetPath}",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"servertype": "openocd",
|
"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
|
// Connect to an already running OpenOCD instance
|
||||||
"gdbTarget": "localhost:3333",
|
"gdbTarget": "localhost:3333",
|
||||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||||
"runToMain": true,
|
"runToEntryPoint": "main",
|
||||||
// Work around for stopping at main on restart
|
// Work around for stopping at main on restart
|
||||||
"postRestartCommands": [
|
"postRestartCommands": [
|
||||||
"break main",
|
"break main",
|
||||||
|
@ -51,6 +47,11 @@
|
||||||
"showDevDebugOutput": false,
|
"showDevDebugOutput": false,
|
||||||
"servertype": "openocd",
|
"servertype": "openocd",
|
||||||
"runToMain": true,
|
"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)
|
// 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",
|
"armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
|
||||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||||
|
@ -58,7 +59,25 @@
|
||||||
"interface/stlink.cfg",
|
"interface/stlink.cfg",
|
||||||
"target/nrf52.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
15
.vscode/settings.json
vendored
|
@ -1,9 +1,20 @@
|
||||||
{
|
{
|
||||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||||
"cmake.configureArgs": [
|
"cmake.configureArgs": [
|
||||||
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
|
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
|
||||||
"-DNRF5_SDK_PATH=${env:NRF5_SDK_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",
|
"cmake.generator": "Unix Makefiles",
|
||||||
"clang-tidy.buildPath": "build/compile_commands.json",
|
"clang-tidy.buildPath": "build/compile_commands.json",
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
|
|
22
.vscode/tasks.json
vendored
22
.vscode/tasks.json
vendored
|
@ -1,20 +1,6 @@
|
||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"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",
|
"label": "update submodules",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
|
@ -31,14 +17,6 @@
|
||||||
"panel": "shared"
|
"panel": "shared"
|
||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "BuildInit",
|
|
||||||
"dependsOn": [
|
|
||||||
"update submodules",
|
|
||||||
"create openocd build"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
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_C_STANDARD 99)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
|
![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?
|
## New to InfiniTime?
|
||||||
|
|
||||||
|
|
|
@ -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
|
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
|
remaining bytes versus string bugs remaining in the places I didn't test
|
||||||
. I used it primarily as part of
|
. 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
|
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?
|
## What does it do?
|
||||||
|
|
||||||
|
|
|
@ -21,3 +21,5 @@ The current raw motion values. This is a 3 `int16_t` array:
|
||||||
- [0] : X
|
- [0] : X
|
||||||
- [1] : Y
|
- [1] : Y
|
||||||
- [2] : Z
|
- [2] : Z
|
||||||
|
|
||||||
|
The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024.
|
||||||
|
|
|
@ -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.
|
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
|
### DevContainer on Ubuntu
|
||||||
|
|
||||||
|
|
|
@ -35,18 +35,20 @@ that will call the method `Refresh()` periodically.
|
||||||
|
|
||||||
## App types
|
## 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.
|
**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.
|
**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.
|
**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
|
||||||
This allows to easily select which user applications 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.
|
without overflowing the system memory.
|
||||||
|
|
||||||
## Apps initialization
|
## Apps and watch faces initialization
|
||||||
|
|
||||||
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
|
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.
|
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`
|
the corresponding `AppDescription` is first retrieved from `userApps`
|
||||||
and then the function `create` is called to create an instance of the app.
|
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
|
## User application selection at build time
|
||||||
|
|
||||||
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
|
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`
|
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
|
||||||
to list all available applications.
|
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
|
## Creating your own app
|
||||||
|
|
||||||
A minimal user app could look like this:
|
A minimal user app could look like this:
|
||||||
|
@ -110,10 +140,10 @@ namespace Pinetime {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct AppTraits<Apps:MyApp> {
|
struct AppTraits<Apps::MyApp> {
|
||||||
static constexpr Apps app = Apps::MyApp;
|
static constexpr Apps app = Apps::MyApp;
|
||||||
static constexpr const char* icon = Screens::Symbol::myApp;
|
static constexpr const char* icon = Screens::Symbols::myApp;
|
||||||
static Screens::Screens* Create(AppController& controllers) {
|
static Screens::Screen* Create(AppControllers& controllers) {
|
||||||
return new Screens::MyApp();
|
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)
|
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
|
||||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
||||||
The next step to making it launch-able is to give your app an id.
|
The next step to making it launch-able is to give your app an id.
|
||||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
|
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)).
|
||||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
|
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
|
||||||
to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
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" ...
|
$ 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
|
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||||
and flash it to your PineTime. Yay!
|
and flash it to your PineTime. Yay!
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,13 @@ RUN apt-get update -qq \
|
||||||
libpangocairo-1.0-0 \
|
libpangocairo-1.0-0 \
|
||||||
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
|
&& 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
|
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
||||||
|
|
||||||
RUN pip3 install adafruit-nrfutil
|
RUN pip3 install adafruit-nrfutil
|
||||||
|
@ -55,5 +62,8 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
|
||||||
# McuBoot
|
# McuBoot
|
||||||
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
|
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
|
||||||
|
|
||||||
|
# Add the infinitime user for connecting devcontainer
|
||||||
|
RUN adduser infinitime
|
||||||
|
|
||||||
ENV SOURCES_DIR /sources
|
ENV SOURCES_DIR /sources
|
||||||
CMD ["/opt/build.sh"]
|
CMD ["/opt/build.sh"]
|
||||||
|
|
|
@ -1,25 +1,33 @@
|
||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
if clang-format --version | grep -q 'version 11\.'; then
|
|
||||||
CLANG_FORMAT_EXECUTABLE="clang-format"
|
name="clang-format"
|
||||||
else
|
|
||||||
CLANG_FORMAT_EXECUTABLE="clang-format-11"
|
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
|
fi
|
||||||
|
|
||||||
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
|
minVersion="14.0.0"
|
||||||
then
|
|
||||||
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for FILE in $(git diff --cached --name-only)
|
for file in $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'clang-format*'); do
|
||||||
do
|
curBin="$file"
|
||||||
if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then
|
curVersion="$("$curBin" --version | cut -d ' ' -f 3)"
|
||||||
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
|
|
||||||
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
|
if [ "$(printf '%s\n' "$curVersion" "$version" "$minVersion" | sort -V | tail -n 1)" = "$curVersion" ]; then
|
||||||
git add -- $FILE
|
bin="$curBin"
|
||||||
elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then
|
version="$curVersion"
|
||||||
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
|
fi
|
||||||
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
|
done
|
||||||
git add -- $FILE
|
|
||||||
fi
|
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
|
done
|
||||||
|
|
50
shell.nix
Normal file
50
shell.nix
Normal 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";
|
||||||
|
}
|
|
@ -174,6 +174,11 @@ set(LITTLEFS_SRC
|
||||||
libs/littlefs/lfs.c
|
libs/littlefs/lfs.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(SUNSET_SRC
|
||||||
|
libs/sunset/src/sunset.h
|
||||||
|
libs/sunset/src/sunset.cpp
|
||||||
|
)
|
||||||
|
|
||||||
set(LVGL_SRC
|
set(LVGL_SRC
|
||||||
libs/lv_conf.h
|
libs/lv_conf.h
|
||||||
libs/lvgl/lvgl.h
|
libs/lvgl/lvgl.h
|
||||||
|
@ -366,8 +371,6 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/DisplayApp.cpp
|
displayapp/DisplayApp.cpp
|
||||||
displayapp/screens/Screen.cpp
|
displayapp/screens/Screen.cpp
|
||||||
displayapp/screens/Tile.cpp
|
displayapp/screens/Tile.cpp
|
||||||
displayapp/screens/InfiniPaint.cpp
|
|
||||||
displayapp/screens/Paddle.cpp
|
|
||||||
displayapp/screens/StopWatch.cpp
|
displayapp/screens/StopWatch.cpp
|
||||||
displayapp/screens/BatteryIcon.cpp
|
displayapp/screens/BatteryIcon.cpp
|
||||||
displayapp/screens/BleIcon.cpp
|
displayapp/screens/BleIcon.cpp
|
||||||
|
@ -376,13 +379,9 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/Label.cpp
|
displayapp/screens/Label.cpp
|
||||||
displayapp/screens/FirmwareUpdate.cpp
|
displayapp/screens/FirmwareUpdate.cpp
|
||||||
displayapp/screens/Music.cpp
|
displayapp/screens/Music.cpp
|
||||||
displayapp/screens/Navigation.cpp
|
|
||||||
displayapp/screens/Metronome.cpp
|
|
||||||
displayapp/screens/Motion.cpp
|
|
||||||
displayapp/screens/FirmwareValidation.cpp
|
displayapp/screens/FirmwareValidation.cpp
|
||||||
displayapp/screens/ApplicationList.cpp
|
displayapp/screens/ApplicationList.cpp
|
||||||
displayapp/screens/Notifications.cpp
|
displayapp/screens/Notifications.cpp
|
||||||
displayapp/screens/Twos.cpp
|
|
||||||
displayapp/screens/HeartRate.cpp
|
displayapp/screens/HeartRate.cpp
|
||||||
displayapp/screens/FlashLight.cpp
|
displayapp/screens/FlashLight.cpp
|
||||||
displayapp/screens/List.cpp
|
displayapp/screens/List.cpp
|
||||||
|
@ -390,10 +389,12 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/BatteryInfo.cpp
|
displayapp/screens/BatteryInfo.cpp
|
||||||
displayapp/screens/Steps.cpp
|
displayapp/screens/Steps.cpp
|
||||||
displayapp/screens/Timer.cpp
|
displayapp/screens/Timer.cpp
|
||||||
|
displayapp/screens/Dice.cpp
|
||||||
displayapp/screens/PassKey.cpp
|
displayapp/screens/PassKey.cpp
|
||||||
displayapp/screens/Error.cpp
|
displayapp/screens/Error.cpp
|
||||||
displayapp/screens/Alarm.cpp
|
displayapp/screens/Alarm.cpp
|
||||||
displayapp/screens/Styles.cpp
|
displayapp/screens/Styles.cpp
|
||||||
|
displayapp/screens/WeatherSymbols.cpp
|
||||||
displayapp/Colors.cpp
|
displayapp/Colors.cpp
|
||||||
displayapp/widgets/Counter.cpp
|
displayapp/widgets/Counter.cpp
|
||||||
displayapp/widgets/PageIndicator.cpp
|
displayapp/widgets/PageIndicator.cpp
|
||||||
|
@ -415,14 +416,12 @@ list(APPEND SOURCE_FILES
|
||||||
displayapp/screens/settings/SettingChimes.cpp
|
displayapp/screens/settings/SettingChimes.cpp
|
||||||
displayapp/screens/settings/SettingShakeThreshold.cpp
|
displayapp/screens/settings/SettingShakeThreshold.cpp
|
||||||
displayapp/screens/settings/SettingBluetooth.cpp
|
displayapp/screens/settings/SettingBluetooth.cpp
|
||||||
|
displayapp/screens/settings/SettingLocation.cpp
|
||||||
|
|
||||||
## Watch faces
|
## Watch faces
|
||||||
displayapp/screens/WatchFaceAnalog.cpp
|
|
||||||
displayapp/screens/WatchFaceDigital.cpp
|
displayapp/screens/WatchFaceDigital.cpp
|
||||||
displayapp/screens/WatchFaceInfineat.cpp
|
displayapp/screens/WatchFaceFuzzy.cpp
|
||||||
displayapp/screens/WatchFaceTerminal.cpp
|
displayapp/screens/WatchFaceSundial.cpp
|
||||||
displayapp/screens/WatchFacePineTimeStyle.cpp
|
|
||||||
displayapp/screens/WatchFaceCasioStyleG7710.cpp
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@ -540,7 +539,6 @@ list(APPEND RECOVERY_SOURCE_FILES
|
||||||
systemtask/SystemTask.cpp
|
systemtask/SystemTask.cpp
|
||||||
systemtask/SystemMonitor.cpp
|
systemtask/SystemMonitor.cpp
|
||||||
drivers/TwiMaster.cpp
|
drivers/TwiMaster.cpp
|
||||||
components/gfx/Gfx.cpp
|
|
||||||
components/rle/RleDecoder.cpp
|
components/rle/RleDecoder.cpp
|
||||||
components/heartrate/HeartRateController.cpp
|
components/heartrate/HeartRateController.cpp
|
||||||
heartratetask/HeartRateTask.cpp
|
heartratetask/HeartRateTask.cpp
|
||||||
|
@ -570,7 +568,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
|
||||||
|
|
||||||
components/rle/RleDecoder.cpp
|
components/rle/RleDecoder.cpp
|
||||||
|
|
||||||
components/gfx/Gfx.cpp
|
|
||||||
drivers/St7789.cpp
|
drivers/St7789.cpp
|
||||||
components/brightness/BrightnessController.cpp
|
components/brightness/BrightnessController.cpp
|
||||||
|
|
||||||
|
@ -589,9 +586,7 @@ set(INCLUDE_FILES
|
||||||
displayapp/TouchEvents.h
|
displayapp/TouchEvents.h
|
||||||
displayapp/screens/Screen.h
|
displayapp/screens/Screen.h
|
||||||
displayapp/screens/Tile.h
|
displayapp/screens/Tile.h
|
||||||
displayapp/screens/InfiniPaint.h
|
|
||||||
displayapp/screens/StopWatch.h
|
displayapp/screens/StopWatch.h
|
||||||
displayapp/screens/Paddle.h
|
|
||||||
displayapp/screens/BatteryIcon.h
|
displayapp/screens/BatteryIcon.h
|
||||||
displayapp/screens/BleIcon.h
|
displayapp/screens/BleIcon.h
|
||||||
displayapp/screens/NotificationIcon.h
|
displayapp/screens/NotificationIcon.h
|
||||||
|
@ -605,10 +600,7 @@ set(INCLUDE_FILES
|
||||||
displayapp/Apps.h
|
displayapp/Apps.h
|
||||||
displayapp/screens/Notifications.h
|
displayapp/screens/Notifications.h
|
||||||
displayapp/screens/HeartRate.h
|
displayapp/screens/HeartRate.h
|
||||||
displayapp/screens/Metronome.h
|
|
||||||
displayapp/screens/Motion.h
|
|
||||||
displayapp/screens/Timer.h
|
displayapp/screens/Timer.h
|
||||||
displayapp/screens/Alarm.h
|
|
||||||
displayapp/Colors.h
|
displayapp/Colors.h
|
||||||
displayapp/widgets/Counter.h
|
displayapp/widgets/Counter.h
|
||||||
displayapp/widgets/PageIndicator.h
|
displayapp/widgets/PageIndicator.h
|
||||||
|
@ -897,13 +889,25 @@ target_compile_options(littlefs PRIVATE
|
||||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# SUNSET_SRC
|
||||||
|
add_library(sunset STATIC ${SUNSET_SRC})
|
||||||
|
target_include_directories(sunset SYSTEM PUBLIC . ../)
|
||||||
|
target_include_directories(sunset SYSTEM PUBLIC ${INCLUDES_FROM_LIBS})
|
||||||
|
target_compile_options(sunset PRIVATE
|
||||||
|
${COMMON_FLAGS}
|
||||||
|
$<$<CONFIG:DEBUG>: ${DEBUG_FLAGS}>
|
||||||
|
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
|
||||||
|
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||||
|
)
|
||||||
|
|
||||||
# Build autonomous binary (without support for bootloader)
|
# Build autonomous binary (without support for bootloader)
|
||||||
set(EXECUTABLE_NAME "pinetime-app")
|
set(EXECUTABLE_NAME "pinetime-app")
|
||||||
set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||||
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
|
||||||
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
|
||||||
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME})
|
||||||
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs sunset infinitime_fonts infinitime_apps)
|
||||||
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
${WARNING_FLAGS}
|
${WARNING_FLAGS}
|
||||||
|
@ -937,7 +941,7 @@ set(IMAGE_MCUBOOT_FILE_NAME_BIN ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERS
|
||||||
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||||
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
|
||||||
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
|
||||||
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs infinitime_fonts infinitime_apps)
|
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs sunset infinitime_fonts infinitime_apps)
|
||||||
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME})
|
||||||
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
|
||||||
${COMMON_FLAGS}
|
${COMMON_FLAGS}
|
||||||
|
@ -979,7 +983,7 @@ endif()
|
||||||
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
|
||||||
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
|
||||||
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
|
add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
|
||||||
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk littlefs sunset infinitime_fonts infinitime_apps)
|
||||||
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
|
set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
|
||||||
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
|
||||||
|
@ -1011,7 +1015,7 @@ set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-$
|
||||||
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
|
set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME_HEX ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}.hex)
|
||||||
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
|
||||||
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
|
add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
|
||||||
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs infinitime_fonts infinitime_apps)
|
target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk littlefs sunset infinitime_fonts infinitime_apps)
|
||||||
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
|
set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
|
||||||
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
|
||||||
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
|
||||||
|
|
|
@ -62,7 +62,8 @@
|
||||||
#define configTICK_RATE_HZ 1024
|
#define configTICK_RATE_HZ 1024
|
||||||
#define configMAX_PRIORITIES (3)
|
#define configMAX_PRIORITIES (3)
|
||||||
#define configMINIMAL_STACK_SIZE (120)
|
#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 configMAX_TASK_NAME_LEN (4)
|
||||||
#define configUSE_16_BIT_TICKS 0
|
#define configUSE_16_BIT_TICKS 0
|
||||||
#define configIDLE_SHOULD_YIELD 1
|
#define configIDLE_SHOULD_YIELD 1
|
||||||
|
@ -75,6 +76,7 @@
|
||||||
#define configUSE_TIME_SLICING 0
|
#define configUSE_TIME_SLICING 0
|
||||||
#define configUSE_NEWLIB_REENTRANT 0
|
#define configUSE_NEWLIB_REENTRANT 0
|
||||||
#define configENABLE_BACKWARD_COMPATIBILITY 1
|
#define configENABLE_BACKWARD_COMPATIBILITY 1
|
||||||
|
#define configUSE_TASK_NOTIFICATIONS 0
|
||||||
|
|
||||||
/* Hook function related definitions. */
|
/* Hook function related definitions. */
|
||||||
#define configUSE_IDLE_HOOK 0
|
#define configUSE_IDLE_HOOK 0
|
||||||
|
|
|
@ -357,6 +357,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
|
||||||
this->totalSize = totalSize;
|
this->totalSize = totalSize;
|
||||||
this->expectedCrc = expectedCrc;
|
this->expectedCrc = expectedCrc;
|
||||||
this->ready = true;
|
this->ready = true;
|
||||||
|
totalWriteIndex = 0;
|
||||||
|
bufferWriteIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
|
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||||
#define max
|
#define max
|
||||||
#include <host/ble_gap.h>
|
#include <host/ble_gap.h>
|
||||||
#include <atomic>
|
|
||||||
#undef max
|
#undef max
|
||||||
#undef min
|
#undef min
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
|
|
|
@ -120,3 +120,7 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
|
||||||
else if (attributeHandle == motionValuesHandle)
|
else if (attributeHandle == motionValuesHandle)
|
||||||
motionValuesNoficationEnabled = false;
|
motionValuesNoficationEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MotionService::IsMotionNotificationSubscribed() const {
|
||||||
|
return motionValuesNoficationEnabled;
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Pinetime {
|
||||||
|
|
||||||
void SubscribeNotification(uint16_t attributeHandle);
|
void SubscribeNotification(uint16_t attributeHandle);
|
||||||
void UnsubscribeNotification(uint16_t attributeHandle);
|
void UnsubscribeNotification(uint16_t attributeHandle);
|
||||||
|
bool IsMotionNotificationSubscribed() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NimbleController& nimble;
|
NimbleController& nimble;
|
||||||
|
|
|
@ -158,3 +158,16 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService
|
||||||
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
|
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
|
||||||
std::strcmp(this->location.data(), other.location.data()) == 0;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -96,9 +96,13 @@ namespace Pinetime {
|
||||||
int16_t minTemperature;
|
int16_t minTemperature;
|
||||||
int16_t maxTemperature;
|
int16_t maxTemperature;
|
||||||
Icons iconId;
|
Icons iconId;
|
||||||
|
|
||||||
|
bool operator==(const Day& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<Day, MaxNbForecastDays> days;
|
std::array<Day, MaxNbForecastDays> days;
|
||||||
|
|
||||||
|
bool operator==(const Forecast& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<CurrentWeather> Current() const;
|
std::optional<CurrentWeather> Current() const;
|
||||||
|
|
|
@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
|
||||||
return MonthsStringLow[static_cast<uint8_t>(month)];
|
return MonthsStringLow[static_cast<uint8_t>(month)];
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* DateTime::DayOfWeekShortToStringLow() const {
|
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
|
||||||
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
|
return DaysStringShortLow[static_cast<uint8_t>(day)];
|
||||||
}
|
}
|
||||||
|
|
||||||
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
|
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
|
||||||
|
@ -129,7 +129,7 @@ std::string DateTime::FormattedTime() {
|
||||||
auto hour = Hours();
|
auto hour = Hours();
|
||||||
auto minute = Minutes();
|
auto minute = Minutes();
|
||||||
// Return time as a string in 12- or 24-hour format
|
// Return time as a string in 12- or 24-hour format
|
||||||
char buff[9];
|
char buff[11];
|
||||||
if (settingsController.GetClockType() == ClockType::H12) {
|
if (settingsController.GetClockType() == ClockType::H12) {
|
||||||
uint8_t hour12;
|
uint8_t hour12;
|
||||||
const char* amPmStr;
|
const char* amPmStr;
|
||||||
|
|
|
@ -122,7 +122,7 @@ namespace Pinetime {
|
||||||
const char* MonthShortToString() const;
|
const char* MonthShortToString() const;
|
||||||
const char* DayOfWeekShortToString() const;
|
const char* DayOfWeekShortToString() const;
|
||||||
static const char* MonthShortToStringLow(Months month);
|
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 {
|
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
|
||||||
return currentDateTime;
|
return currentDateTime;
|
||||||
|
|
|
@ -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], ¤t_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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
|
||||||
service->OnNewStepCountValue(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);
|
service->OnNewMotionValues(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
time = xTaskGetTickCount();
|
time = xTaskGetTickCount();
|
||||||
|
|
||||||
lastX = this->x;
|
xHistory++;
|
||||||
this->x = x;
|
xHistory[0] = x;
|
||||||
yHistory++;
|
yHistory++;
|
||||||
yHistory[0] = y;
|
yHistory[0] = y;
|
||||||
zHistory++;
|
zHistory++;
|
||||||
|
@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
|
||||||
AccelStats stats;
|
AccelStats stats;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
|
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
|
||||||
|
stats.xMean += xHistory[histSize - i];
|
||||||
stats.yMean += yHistory[histSize - i];
|
stats.yMean += yHistory[histSize - i];
|
||||||
stats.zMean += zHistory[histSize - i];
|
stats.zMean += zHistory[histSize - i];
|
||||||
|
stats.prevXMean += xHistory[1 + i];
|
||||||
stats.prevYMean += yHistory[1 + i];
|
stats.prevYMean += yHistory[1 + i];
|
||||||
stats.prevZMean += zHistory[1 + i];
|
stats.prevZMean += zHistory[1 + i];
|
||||||
}
|
}
|
||||||
|
stats.xMean /= AccelStats::numHistory;
|
||||||
stats.yMean /= AccelStats::numHistory;
|
stats.yMean /= AccelStats::numHistory;
|
||||||
stats.zMean /= AccelStats::numHistory;
|
stats.zMean /= AccelStats::numHistory;
|
||||||
|
stats.prevXMean /= AccelStats::numHistory;
|
||||||
stats.prevYMean /= AccelStats::numHistory;
|
stats.prevYMean /= AccelStats::numHistory;
|
||||||
stats.prevZMean /= AccelStats::numHistory;
|
stats.prevZMean /= AccelStats::numHistory;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
|
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.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
|
||||||
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
|
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
|
||||||
}
|
}
|
||||||
|
stats.xVariance /= AccelStats::numHistory;
|
||||||
stats.yVariance /= AccelStats::numHistory;
|
stats.yVariance /= AccelStats::numHistory;
|
||||||
stats.zVariance /= AccelStats::numHistory;
|
stats.zVariance /= AccelStats::numHistory;
|
||||||
|
|
||||||
|
@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const {
|
||||||
constexpr int16_t yThresh = -64;
|
constexpr int16_t yThresh = -64;
|
||||||
constexpr int16_t rollDegreesThresh = -45;
|
constexpr int16_t rollDegreesThresh = -45;
|
||||||
|
|
||||||
if (x < -xThresh || x > xThresh) {
|
if (std::abs(stats.xMean) > xThresh) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const {
|
||||||
|
|
||||||
bool MotionController::ShouldShakeWake(uint16_t thresh) {
|
bool MotionController::ShouldShakeWake(uint16_t thresh) {
|
||||||
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
|
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
|
||||||
int32_t speed =
|
int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
|
||||||
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
|
(xHistory[0] - xHistory[histSize - 1]) / 4) *
|
||||||
|
100 / (time - lastTime);
|
||||||
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
|
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
|
||||||
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
|
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
|
||||||
|
|
||||||
|
@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MotionController::ShouldLowerSleep() const {
|
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) {
|
if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace Pinetime {
|
||||||
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
|
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
|
||||||
|
|
||||||
int16_t X() const {
|
int16_t X() const {
|
||||||
return x;
|
return xHistory[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t Y() const {
|
int16_t Y() const {
|
||||||
|
@ -62,6 +62,10 @@ namespace Pinetime {
|
||||||
this->service = service;
|
this->service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pinetime::Controllers::MotionService* GetService() const {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t nbSteps = 0;
|
uint32_t nbSteps = 0;
|
||||||
uint32_t currentTripSteps = 0;
|
uint32_t currentTripSteps = 0;
|
||||||
|
@ -72,11 +76,14 @@ namespace Pinetime {
|
||||||
struct AccelStats {
|
struct AccelStats {
|
||||||
static constexpr uint8_t numHistory = 2;
|
static constexpr uint8_t numHistory = 2;
|
||||||
|
|
||||||
|
int16_t xMean = 0;
|
||||||
int16_t yMean = 0;
|
int16_t yMean = 0;
|
||||||
int16_t zMean = 0;
|
int16_t zMean = 0;
|
||||||
|
int16_t prevXMean = 0;
|
||||||
int16_t prevYMean = 0;
|
int16_t prevYMean = 0;
|
||||||
int16_t prevZMean = 0;
|
int16_t prevZMean = 0;
|
||||||
|
|
||||||
|
uint32_t xVariance = 0;
|
||||||
uint32_t yVariance = 0;
|
uint32_t yVariance = 0;
|
||||||
uint32_t zVariance = 0;
|
uint32_t zVariance = 0;
|
||||||
};
|
};
|
||||||
|
@ -85,9 +92,8 @@ namespace Pinetime {
|
||||||
|
|
||||||
AccelStats stats = {};
|
AccelStats stats = {};
|
||||||
|
|
||||||
int16_t lastX = 0;
|
|
||||||
int16_t x = 0;
|
|
||||||
static constexpr uint8_t histSize = 8;
|
static constexpr uint8_t histSize = 8;
|
||||||
|
Utility::CircularBuffer<int16_t, histSize> xHistory = {};
|
||||||
Utility::CircularBuffer<int16_t, histSize> yHistory = {};
|
Utility::CircularBuffer<int16_t, histSize> yHistory = {};
|
||||||
Utility::CircularBuffer<int16_t, histSize> zHistory = {};
|
Utility::CircularBuffer<int16_t, histSize> zHistory = {};
|
||||||
int32_t accumulatedSpeed = 0;
|
int32_t accumulatedSpeed = 0;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace Pinetime {
|
||||||
namespace Controllers {
|
namespace Controllers {
|
||||||
class Settings {
|
class Settings {
|
||||||
public:
|
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 WeatherFormat : uint8_t { Metric, Imperial };
|
||||||
enum class Notification : uint8_t { On, Off, Sleep };
|
enum class Notification : uint8_t { On, Off, Sleep };
|
||||||
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
enum class ChimesOption : uint8_t { None, Hours, HalfHours };
|
||||||
|
@ -50,6 +50,12 @@ namespace Pinetime {
|
||||||
int colorIndex = 0;
|
int colorIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Location {
|
||||||
|
int16_t latitude;
|
||||||
|
int16_t longitude;
|
||||||
|
int8_t tzOffset;
|
||||||
|
};
|
||||||
|
|
||||||
Settings(Pinetime::Controllers::FS& fs);
|
Settings(Pinetime::Controllers::FS& fs);
|
||||||
|
|
||||||
Settings(const Settings&) = delete;
|
Settings(const Settings&) = delete;
|
||||||
|
@ -275,6 +281,21 @@ namespace Pinetime {
|
||||||
return settings.stepsGoal;
|
return settings.stepsGoal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void SetLocation(Location loc) {
|
||||||
|
if (
|
||||||
|
loc.latitude != settings.location.latitude ||
|
||||||
|
loc.longitude != settings.location.longitude ||
|
||||||
|
loc.tzOffset != settings.location.tzOffset
|
||||||
|
) {
|
||||||
|
settingsChanged = true;
|
||||||
|
}
|
||||||
|
settings.location = loc;
|
||||||
|
};
|
||||||
|
|
||||||
|
Location GetLocation() const {
|
||||||
|
return settings.location;
|
||||||
|
};
|
||||||
|
|
||||||
void SetBleRadioEnabled(bool enabled) {
|
void SetBleRadioEnabled(bool enabled) {
|
||||||
bleRadioEnabled = enabled;
|
bleRadioEnabled = enabled;
|
||||||
};
|
};
|
||||||
|
@ -308,6 +329,8 @@ namespace Pinetime {
|
||||||
uint16_t shakeWakeThreshold = 150;
|
uint16_t shakeWakeThreshold = 150;
|
||||||
|
|
||||||
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
|
Controllers::BrightnessController::Levels brightLevel = Controllers::BrightnessController::Levels::Medium;
|
||||||
|
|
||||||
|
Location location = {(int16_t)44,(int16_t)-123,(int8_t)-8};
|
||||||
};
|
};
|
||||||
|
|
||||||
SettingsData settings;
|
SettingsData settings;
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
#include "displayapp/screens/FlashLight.h"
|
#include "displayapp/screens/FlashLight.h"
|
||||||
#include "displayapp/screens/BatteryInfo.h"
|
#include "displayapp/screens/BatteryInfo.h"
|
||||||
#include "displayapp/screens/Steps.h"
|
#include "displayapp/screens/Steps.h"
|
||||||
|
#include "displayapp/screens/Dice.h"
|
||||||
|
#include "displayapp/screens/Weather.h"
|
||||||
#include "displayapp/screens/PassKey.h"
|
#include "displayapp/screens/PassKey.h"
|
||||||
#include "displayapp/screens/Error.h"
|
#include "displayapp/screens/Error.h"
|
||||||
|
|
||||||
|
@ -47,6 +49,7 @@
|
||||||
#include "displayapp/screens/settings/SettingChimes.h"
|
#include "displayapp/screens/settings/SettingChimes.h"
|
||||||
#include "displayapp/screens/settings/SettingShakeThreshold.h"
|
#include "displayapp/screens/settings/SettingShakeThreshold.h"
|
||||||
#include "displayapp/screens/settings/SettingBluetooth.h"
|
#include "displayapp/screens/settings/SettingBluetooth.h"
|
||||||
|
#include "displayapp/screens/settings/SettingLocation.h"
|
||||||
|
|
||||||
#include "libs/lv_conf.h"
|
#include "libs/lv_conf.h"
|
||||||
#include "UserApps.h"
|
#include "UserApps.h"
|
||||||
|
@ -123,6 +126,7 @@ void DisplayApp::Start(System::BootErrors error) {
|
||||||
bootError = error;
|
bootError = error;
|
||||||
|
|
||||||
lvgl.Init();
|
lvgl.Init();
|
||||||
|
motorController.Init();
|
||||||
|
|
||||||
if (error == System::BootErrors::TouchController) {
|
if (error == System::BootErrors::TouchController) {
|
||||||
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
|
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
|
||||||
|
@ -140,9 +144,6 @@ void DisplayApp::Process(void* instance) {
|
||||||
NRF_LOG_INFO("displayapp task started!");
|
NRF_LOG_INFO("displayapp task started!");
|
||||||
app->InitHw();
|
app->InitHw();
|
||||||
|
|
||||||
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
|
||||||
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
app->Refresh();
|
app->Refresh();
|
||||||
}
|
}
|
||||||
|
@ -151,7 +152,6 @@ void DisplayApp::Process(void* instance) {
|
||||||
void DisplayApp::InitHw() {
|
void DisplayApp::InitHw() {
|
||||||
brightnessController.Init();
|
brightnessController.Init();
|
||||||
ApplyBrightness();
|
ApplyBrightness();
|
||||||
motorController.Init();
|
|
||||||
lcd.Init();
|
lcd.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,6 +444,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
else {
|
else {
|
||||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||||
}
|
}
|
||||||
|
settingsController.SetAppMenu(0);
|
||||||
} break;
|
} break;
|
||||||
case Apps::Error:
|
case Apps::Error:
|
||||||
currentScreen = std::make_unique<Screens::Error>(bootError);
|
currentScreen = std::make_unique<Screens::Error>(bootError);
|
||||||
|
@ -489,10 +490,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
||||||
break;
|
break;
|
||||||
case Apps::SettingWatchFace: {
|
case Apps::SettingWatchFace: {
|
||||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
|
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const auto& userWatchFace : userWatchFaces) {
|
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);
|
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
|
||||||
} break;
|
} break;
|
||||||
|
@ -514,6 +516,9 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
||||||
case Apps::SettingSetDateTime:
|
case Apps::SettingSetDateTime:
|
||||||
currentScreen = std::make_unique<Screens::SettingSetDateTime>(this, dateTimeController, settingsController);
|
currentScreen = std::make_unique<Screens::SettingSetDateTime>(this, dateTimeController, settingsController);
|
||||||
break;
|
break;
|
||||||
|
case Apps::SettingLocation:
|
||||||
|
currentScreen = std::make_unique<Screens::SettingLocation>(settingsController);
|
||||||
|
break;
|
||||||
case Apps::SettingChimes:
|
case Apps::SettingChimes:
|
||||||
currentScreen = std::make_unique<Screens::SettingChimes>(settingsController);
|
currentScreen = std::make_unique<Screens::SettingChimes>(settingsController);
|
||||||
break;
|
break;
|
||||||
|
@ -558,9 +563,7 @@ void DisplayApp::PushMessage(Messages msg) {
|
||||||
if (in_isr()) {
|
if (in_isr()) {
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
TickType_t timeout = portMAX_DELAY;
|
TickType_t timeout = portMAX_DELAY;
|
||||||
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
|
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
|
||||||
|
|
|
@ -38,9 +38,6 @@ void DisplayApp::Process(void* instance) {
|
||||||
auto* app = static_cast<DisplayApp*>(instance);
|
auto* app = static_cast<DisplayApp*>(instance);
|
||||||
NRF_LOG_INFO("displayapp task started!");
|
NRF_LOG_INFO("displayapp task started!");
|
||||||
|
|
||||||
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
|
||||||
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
|
|
||||||
|
|
||||||
app->InitHw();
|
app->InitHw();
|
||||||
while (true) {
|
while (true) {
|
||||||
app->Refresh();
|
app->Refresh();
|
||||||
|
@ -94,7 +91,6 @@ void DisplayApp::DisplayLogo(uint16_t color) {
|
||||||
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
|
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
|
||||||
for (int i = 0; i < displayWidth; i++) {
|
for (int i = 0; i < displayWidth; i++) {
|
||||||
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
|
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
|
||||||
ulTaskNotifyTake(pdTRUE, 500);
|
|
||||||
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
|
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;
|
const uint8_t barHeight = 20;
|
||||||
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
|
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
|
||||||
for (int i = 0; i < barHeight; i++) {
|
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));
|
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);
|
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayApp::PushMessage(Display::Messages msg) {
|
void DisplayApp::PushMessage(Display::Messages msg) {
|
||||||
BaseType_t xHigherPriorityTaskWoken;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xHigherPriorityTaskWoken = pdFALSE;
|
|
||||||
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||||
if (xHigherPriorityTaskWoken) {
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
/* Actual macro used here is port specific. */
|
|
||||||
// TODO : should I do something here?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
|
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include <drivers/SpiMaster.h>
|
#include <drivers/SpiMaster.h>
|
||||||
#include <bits/unique_ptr.h>
|
#include <bits/unique_ptr.h>
|
||||||
#include <queue.h>
|
#include <queue.h>
|
||||||
#include "components/gfx/Gfx.h"
|
|
||||||
#include "drivers/Cst816s.h"
|
#include "drivers/Cst816s.h"
|
||||||
#include <drivers/Watchdog.h>
|
#include <drivers/Watchdog.h>
|
||||||
#include <components/motor/MotorController.h>
|
#include <components/motor/MotorController.h>
|
||||||
|
|
|
@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
|
||||||
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
|
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
|
||||||
uint16_t y1, y2, width, height = 0;
|
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)) {
|
if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
|
||||||
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
|
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
|
||||||
} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
|
} 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) {
|
if (height > 0) {
|
||||||
lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
|
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;
|
uint16_t pixOffset = width * height;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "Controllers.h"
|
#include "Controllers.h"
|
||||||
|
|
||||||
#include "displayapp/screens/Alarm.h"
|
#include "displayapp/screens/Alarm.h"
|
||||||
|
#include "displayapp/screens/Dice.h"
|
||||||
#include "displayapp/screens/Timer.h"
|
#include "displayapp/screens/Timer.h"
|
||||||
#include "displayapp/screens/Twos.h"
|
#include "displayapp/screens/Twos.h"
|
||||||
#include "displayapp/screens/Tile.h"
|
#include "displayapp/screens/Tile.h"
|
||||||
|
@ -13,6 +14,8 @@
|
||||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
#include "displayapp/screens/WatchFaceInfineat.h"
|
||||||
#include "displayapp/screens/WatchFacePineTimeStyle.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 Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace Pinetime {
|
||||||
Metronome,
|
Metronome,
|
||||||
Motion,
|
Motion,
|
||||||
Steps,
|
Steps,
|
||||||
|
Dice,
|
||||||
|
Weather,
|
||||||
PassKey,
|
PassKey,
|
||||||
QuickSettings,
|
QuickSettings,
|
||||||
Settings,
|
Settings,
|
||||||
|
@ -37,11 +39,11 @@ namespace Pinetime {
|
||||||
SettingWakeUp,
|
SettingWakeUp,
|
||||||
SettingSteps,
|
SettingSteps,
|
||||||
SettingSetDateTime,
|
SettingSetDateTime,
|
||||||
|
SettingLocation,
|
||||||
SettingChimes,
|
SettingChimes,
|
||||||
SettingShakeThreshold,
|
SettingShakeThreshold,
|
||||||
SettingBluetooth,
|
SettingBluetooth,
|
||||||
Error,
|
Error
|
||||||
Weather
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WatchFace : uint8_t {
|
enum class WatchFace : uint8_t {
|
||||||
|
@ -49,6 +51,8 @@ namespace Pinetime {
|
||||||
Analog,
|
Analog,
|
||||||
PineTimeStyle,
|
PineTimeStyle,
|
||||||
Terminal,
|
Terminal,
|
||||||
|
Fuzzy,
|
||||||
|
Sundial,
|
||||||
Infineat,
|
Infineat,
|
||||||
CasioStyleG7710,
|
CasioStyleG7710,
|
||||||
};
|
};
|
||||||
|
@ -71,12 +75,7 @@ namespace Pinetime {
|
||||||
static constexpr size_t Count = sizeof...(Ws);
|
static constexpr size_t Count = sizeof...(Ws);
|
||||||
};
|
};
|
||||||
|
|
||||||
using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Digital,
|
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
|
||||||
WatchFace::Analog,
|
|
||||||
WatchFace::PineTimeStyle,
|
|
||||||
WatchFace::Terminal,
|
|
||||||
WatchFace::Infineat,
|
|
||||||
WatchFace::CasioStyleG7710>;
|
|
||||||
|
|
||||||
static_assert(UserWatchFaceTypes::Count >= 1);
|
static_assert(UserWatchFaceTypes::Count >= 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
if(DEFINED ENABLE_USERAPPS)
|
if(DEFINED ENABLE_USERAPPS)
|
||||||
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
|
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
|
||||||
else ()
|
else ()
|
||||||
set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Alarm, Apps::Timer, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Twos" CACHE STRING "List of user apps to build into the firmware")
|
set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
|
||||||
#Apps::Paint,
|
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
|
||||||
#Apps::Metronome,
|
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
|
||||||
#Apps::Paddle,
|
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 ()
|
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)
|
add_library(infinitime_apps INTERFACE)
|
||||||
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
|
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
|
||||||
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
|
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20
|
set(FONTS jetbrains_mono_42 jetbrains_mono_76 jetbrains_mono_bold_20 jetbrains_mono_bold_24
|
||||||
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
||||||
open_sans_light fontawesome_weathericons)
|
open_sans_light fontawesome_weathericons)
|
||||||
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
|
- 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:
|
### the config file format:
|
||||||
|
|
|
@ -2,23 +2,36 @@
|
||||||
"jetbrains_mono_bold_20": {
|
"jetbrains_mono_bold_20": {
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Bold.ttf",
|
"file": "Vulf_Mono-Italic.woff",
|
||||||
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
|
"range": "0x20-0x7e, 0xB0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
"file": "FontAwesome5-Solid+Brands+Regular.woff",
|
||||||
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf743"
|
"range": "0xf294, 0xf242, 0xf54b, 0xf21e, 0xf1e6, 0xf017, 0xf129, 0xf03a, 0xf185, 0xf560, 0xf001, 0xf3fd, 0xf1fc, 0xf45d, 0xf59f, 0xf5a0, 0xf027, 0xf028, 0xf6a9, 0xf04b, 0xf04c, 0xf048, 0xf051, 0xf095, 0xf3dd, 0xf04d, 0xf2f2, 0xf024, 0xf252, 0xf569, 0xf06e, 0xf015, 0xf00c, 0xf0f3, 0xf522, 0xf743"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
"size": 20,
|
"size": 20
|
||||||
"patches": ["jetbrains_mono_bold_20.c_zero.patch", "jetbrains_mono_bold_20.c_M.patch"]
|
},
|
||||||
|
"jetbrains_mono_bold_24": {
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"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": 26
|
||||||
},
|
},
|
||||||
"jetbrains_mono_42": {
|
"jetbrains_mono_42": {
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Regular.ttf",
|
"file": "Vulf Mono Light Italic.ttf",
|
||||||
"range": "0x20, 0x25, 0x27, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x61-0x7a"
|
"range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
|
@ -28,7 +41,7 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"file": "JetBrainsMono-Light.ttf",
|
"file": "JetBrainsMono-Light.ttf",
|
||||||
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
|
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bpp": 1,
|
"bpp": 1,
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace Pinetime {
|
||||||
template <>
|
template <>
|
||||||
struct AppTraits<Apps::Alarm> {
|
struct AppTraits<Apps::Alarm> {
|
||||||
static constexpr Apps app = 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) {
|
static Screens::Screen* Create(AppControllers& controllers) {
|
||||||
return new Screens::Alarm(controllers.alarmController,
|
return new Screens::Alarm(controllers.alarmController,
|
||||||
|
|
199
src/displayapp/screens/Dice.cpp
Normal file
199
src/displayapp/screens/Dice.cpp
Normal 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]);
|
||||||
|
}
|
61
src/displayapp/screens/Dice.h
Normal file
61
src/displayapp/screens/Dice.h
Normal 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_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);
|
lv_obj_align(label_hr, nullptr, LV_ALIGN_CENTER, 0, -40);
|
||||||
|
|
||||||
label_bpm = lv_label_create(lv_scr_act(), nullptr);
|
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::NoTouch:
|
||||||
case Controllers::HeartRateController::States::NotEnoughData:
|
case Controllers::HeartRateController::States::NotEnoughData:
|
||||||
// case Controllers::HeartRateController::States::Stopped:
|
// case Controllers::HeartRateController::States::Stopped:
|
||||||
lv_label_set_text_static(label_hr, "000");
|
lv_label_set_text_static(label_hr, "---");
|
||||||
break;
|
break;
|
||||||
default:
|
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));
|
lv_label_set_text_static(label_status, ToString(state));
|
||||||
|
|
|
@ -53,9 +53,9 @@ void Motion::Refresh() {
|
||||||
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
|
lv_label_set_text_fmt(labelStep, "Steps %lu", motionController.NbSteps());
|
||||||
|
|
||||||
lv_label_set_text_fmt(label,
|
lv_label_set_text_fmt(label,
|
||||||
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d#",
|
"X #FF0000 %d# Y #00B000 %d# Z #FFFF00 %d# mg",
|
||||||
motionController.X() / 0x10,
|
motionController.X(),
|
||||||
motionController.Y() / 0x10,
|
motionController.Y(),
|
||||||
motionController.Z() / 0x10);
|
motionController.Z());
|
||||||
lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
|
lv_obj_align(label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Pinetime {
|
||||||
static constexpr const char* plug = "\xEF\x87\xA6";
|
static constexpr const char* plug = "\xEF\x87\xA6";
|
||||||
static constexpr const char* shoe = "\xEF\x95\x8B";
|
static constexpr const char* shoe = "\xEF\x95\x8B";
|
||||||
static constexpr const char* clock = "\xEF\x80\x97";
|
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* info = "\xEF\x84\xA9";
|
||||||
static constexpr const char* list = "\xEF\x80\xBA";
|
static constexpr const char* list = "\xEF\x80\xBA";
|
||||||
static constexpr const char* sun = "\xEF\x86\x85";
|
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* hourGlass = "\xEF\x89\x92";
|
||||||
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
|
static constexpr const char* lapsFlag = "\xEF\x80\xA4";
|
||||||
static constexpr const char* drum = "\xEF\x95\xA9";
|
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* eye = "\xEF\x81\xAE";
|
||||||
static constexpr const char* home = "\xEF\x80\x95";
|
static constexpr const char* home = "\xEF\x80\x95";
|
||||||
static constexpr const char* sleep = "\xEE\xBD\x84";
|
static constexpr const char* sleep = "\xEE\xBD\x84";
|
||||||
|
|
|
@ -256,7 +256,7 @@ void WatchFaceAnalog::Refresh() {
|
||||||
if (currentDateTime.IsUpdated()) {
|
if (currentDateTime.IsUpdated()) {
|
||||||
UpdateClock();
|
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 (currentDate.IsUpdated()) {
|
||||||
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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<bool> bleState {};
|
Utility::DirtyValue<bool> bleState {};
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
|
||||||
Utility::DirtyValue<bool> notificationState {false};
|
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, std::chrono::days>> currentDate;
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
|
||||||
|
|
||||||
lv_obj_t* minor_scales;
|
lv_obj_t* minor_scales;
|
||||||
lv_obj_t* major_scales;
|
lv_obj_t* major_scales;
|
||||||
|
|
|
@ -244,7 +244,7 @@ void WatchFaceCasioStyleG7710::Refresh() {
|
||||||
}
|
}
|
||||||
lv_obj_realign(label_time);
|
lv_obj_realign(label_time);
|
||||||
|
|
||||||
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
|
||||||
if (currentDate.IsUpdated()) {
|
if (currentDate.IsUpdated()) {
|
||||||
const char* weekNumberFormat = "%V";
|
const char* weekNumberFormat = "%V";
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<uint8_t> heartbeat {};
|
Utility::DirtyValue<uint8_t> heartbeat {};
|
||||||
Utility::DirtyValue<bool> heartbeatRunning {};
|
Utility::DirtyValue<bool> heartbeatRunning {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
|
||||||
|
|
||||||
lv_point_t line_icons_points[3] {{0, 5}, {117, 5}, {122, 0}};
|
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_week_number_points[4] {{0, 0}, {100, 0}, {95, 95}, {0, 95}};
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include "displayapp/screens/NotificationIcon.h"
|
#include "displayapp/screens/NotificationIcon.h"
|
||||||
#include "displayapp/screens/Symbols.h"
|
#include "displayapp/screens/Symbols.h"
|
||||||
|
#include "displayapp/screens/WeatherSymbols.h"
|
||||||
#include "components/battery/BatteryController.h"
|
#include "components/battery/BatteryController.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "components/ble/NotificationManager.h"
|
#include "components/ble/NotificationManager.h"
|
||||||
#include "components/heartrate/HeartRateController.h"
|
#include "components/heartrate/HeartRateController.h"
|
||||||
#include "components/motion/MotionController.h"
|
#include "components/motion/MotionController.h"
|
||||||
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/settings/Settings.h"
|
#include "components/settings/Settings.h"
|
||||||
|
|
||||||
using namespace Pinetime::Applications::Screens;
|
using namespace Pinetime::Applications::Screens;
|
||||||
|
@ -19,13 +21,15 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::HeartRateController& heartRateController,
|
Controllers::HeartRateController& heartRateController,
|
||||||
Controllers::MotionController& motionController)
|
Controllers::MotionController& motionController,
|
||||||
|
Controllers::SimpleWeatherService& weatherService)
|
||||||
: currentDateTime {{}},
|
: currentDateTime {{}},
|
||||||
dateTimeController {dateTimeController},
|
dateTimeController {dateTimeController},
|
||||||
notificationManager {notificationManager},
|
notificationManager {notificationManager},
|
||||||
settingsController {settingsController},
|
settingsController {settingsController},
|
||||||
heartRateController {heartRateController},
|
heartRateController {heartRateController},
|
||||||
motionController {motionController},
|
motionController {motionController},
|
||||||
|
weatherService {weatherService},
|
||||||
statusIcons(batteryController, bleController) {
|
statusIcons(batteryController, bleController) {
|
||||||
|
|
||||||
statusIcons.Create();
|
statusIcons.Create();
|
||||||
|
@ -35,16 +39,24 @@ WatchFaceDigital::WatchFaceDigital(Controllers::DateTime& dateTimeController,
|
||||||
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
|
lv_label_set_text_static(notificationIcon, NotificationIcon::GetIcon(false));
|
||||||
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
|
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);
|
label_date = lv_label_create(lv_scr_act(), nullptr);
|
||||||
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
|
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));
|
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
|
||||||
|
|
||||||
label_time = lv_label_create(lv_scr_act(), nullptr);
|
label_time = lv_label_create(lv_scr_act(), nullptr);
|
||||||
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
|
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
|
||||||
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
|
|
||||||
} else {
|
|
||||||
lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
|
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
|
||||||
|
|
||||||
|
@ -95,30 +107,7 @@ void WatchFaceDigital::Refresh() {
|
||||||
uint8_t hour = dateTimeController.Hours();
|
uint8_t hour = dateTimeController.Hours();
|
||||||
uint8_t minute = dateTimeController.Minutes();
|
uint8_t minute = dateTimeController.Minutes();
|
||||||
|
|
||||||
/* Begin difference from WatchFaceDigital*/
|
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
||||||
if (settingsController.GetClockType() == Controllers::Settings::ClockType::Fuzzy) {
|
|
||||||
std::string hourStr, timeStr;
|
|
||||||
hour = hour % 12; // 12 becomes 0, 13 becomes 1
|
|
||||||
auto sector = minute / 15 + (minute % 15 > 7);
|
|
||||||
// advance the hour modulo 12 and reset the minutes if we're close to the top
|
|
||||||
// so we get "quarter to $hour+1" instead of needing "three quarters past $hour"
|
|
||||||
if (sector > 3) {
|
|
||||||
hour = (hour + 1) % 12;
|
|
||||||
sector = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeStr = timeSectors[sector];
|
|
||||||
if (timeStr.find("%1") != std::string::npos) {
|
|
||||||
hour = (hour + 1) % 12;
|
|
||||||
}
|
|
||||||
//hourStr = std::string("#") + timeAccent + " " + hourNames[hour] + "#";
|
|
||||||
hourStr = hourNames[hour];
|
|
||||||
timeStr.replace(timeStr.find("%"), 2, hourStr);
|
|
||||||
|
|
||||||
lv_label_set_text(label_time, timeStr.c_str());
|
|
||||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
|
||||||
/* End difference from WatchFaceDigital*/
|
|
||||||
} else if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
|
|
||||||
char ampmChar[3] = "AM";
|
char ampmChar[3] = "AM";
|
||||||
if (hour == 0) {
|
if (hour == 0) {
|
||||||
hour = 12;
|
hour = 12;
|
||||||
|
@ -136,7 +125,7 @@ void WatchFaceDigital::Refresh() {
|
||||||
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
|
||||||
if (currentDate.IsUpdated()) {
|
if (currentDate.IsUpdated()) {
|
||||||
uint16_t year = dateTimeController.Year();
|
uint16_t year = dateTimeController.Year();
|
||||||
uint8_t day = dateTimeController.Day();
|
uint8_t day = dateTimeController.Day();
|
||||||
|
@ -180,32 +169,25 @@ void WatchFaceDigital::Refresh() {
|
||||||
lv_obj_realign(stepValue);
|
lv_obj_realign(stepValue);
|
||||||
lv_obj_realign(stepIcon);
|
lv_obj_realign(stepIcon);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Inspired by XFCE4-panel's fuzzy clock.
|
currentWeather = weatherService.Current();
|
||||||
*
|
if (currentWeather.IsUpdated()) {
|
||||||
* https://salsa.debian.org/xfce-team/desktop/xfce4-panel/-/blob/debian/master/plugins/clock/clock-fuzzy.c
|
auto optCurrentWeather = currentWeather.Get();
|
||||||
*
|
if (optCurrentWeather) {
|
||||||
* Strings contain either a `%0` or a `%1`, indicating the position of
|
int16_t temp = optCurrentWeather->temperature;
|
||||||
* the `hour` or `hour+1`, respectively.
|
char tempUnit = 'C';
|
||||||
*/
|
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
|
||||||
const char* WatchFaceDigital::timeSectors[] = {
|
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
|
||||||
"%0\no'clock",
|
tempUnit = 'F';
|
||||||
"quarter\npast\n%0",
|
}
|
||||||
"half past\n%0",
|
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||||
"quarter\nto %1",
|
lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
|
||||||
};
|
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||||
const char* WatchFaceDigital::hourNames[] = {
|
} else {
|
||||||
"twelve",
|
lv_label_set_text_static(temperature, "");
|
||||||
"one",
|
lv_label_set_text(weatherIcon, "");
|
||||||
"two",
|
}
|
||||||
"three",
|
lv_obj_realign(temperature);
|
||||||
"four",
|
lv_obj_realign(weatherIcon);
|
||||||
"five",
|
}
|
||||||
"six",
|
}
|
||||||
"seven",
|
|
||||||
"eight",
|
|
||||||
"nine",
|
|
||||||
"ten",
|
|
||||||
"eleven",
|
|
||||||
};
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "components/datetime/DateTimeController.h"
|
#include "components/datetime/DateTimeController.h"
|
||||||
|
#include "components/ble/SimpleWeatherService.h"
|
||||||
#include "components/ble/BleController.h"
|
#include "components/ble/BleController.h"
|
||||||
#include "displayapp/widgets/StatusIcons.h"
|
#include "displayapp/widgets/StatusIcons.h"
|
||||||
#include "utility/DirtyValue.h"
|
#include "utility/DirtyValue.h"
|
||||||
|
@ -32,7 +33,8 @@ namespace Pinetime {
|
||||||
Controllers::NotificationManager& notificationManager,
|
Controllers::NotificationManager& notificationManager,
|
||||||
Controllers::Settings& settingsController,
|
Controllers::Settings& settingsController,
|
||||||
Controllers::HeartRateController& heartRateController,
|
Controllers::HeartRateController& heartRateController,
|
||||||
Controllers::MotionController& motionController);
|
Controllers::MotionController& motionController,
|
||||||
|
Controllers::SimpleWeatherService& weather);
|
||||||
~WatchFaceDigital() override;
|
~WatchFaceDigital() override;
|
||||||
|
|
||||||
void Refresh() override;
|
void Refresh() override;
|
||||||
|
@ -40,20 +42,15 @@ namespace Pinetime {
|
||||||
private:
|
private:
|
||||||
uint8_t displayedHour = -1;
|
uint8_t displayedHour = -1;
|
||||||
uint8_t displayedMinute = -1;
|
uint8_t displayedMinute = -1;
|
||||||
static const char* timeSectors[4];
|
|
||||||
static const char* hourNames[12];
|
|
||||||
|
|
||||||
Utility::DirtyValue<uint8_t> batteryPercentRemaining {};
|
|
||||||
Utility::DirtyValue<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<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
|
||||||
Utility::DirtyValue<uint32_t> stepCount {};
|
Utility::DirtyValue<uint32_t> stepCount {};
|
||||||
Utility::DirtyValue<uint8_t> heartbeat {};
|
Utility::DirtyValue<uint8_t> heartbeat {};
|
||||||
Utility::DirtyValue<bool> heartbeatRunning {};
|
Utility::DirtyValue<bool> heartbeatRunning {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
Utility::DirtyValue<std::optional<Pinetime::Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
|
||||||
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* label_time;
|
lv_obj_t* label_time;
|
||||||
lv_obj_t* label_time_ampm;
|
lv_obj_t* label_time_ampm;
|
||||||
|
@ -63,12 +60,15 @@ namespace Pinetime {
|
||||||
lv_obj_t* stepIcon;
|
lv_obj_t* stepIcon;
|
||||||
lv_obj_t* stepValue;
|
lv_obj_t* stepValue;
|
||||||
lv_obj_t* notificationIcon;
|
lv_obj_t* notificationIcon;
|
||||||
|
lv_obj_t* weatherIcon;
|
||||||
|
lv_obj_t* temperature;
|
||||||
|
|
||||||
Controllers::DateTime& dateTimeController;
|
Controllers::DateTime& dateTimeController;
|
||||||
Controllers::NotificationManager& notificationManager;
|
Controllers::NotificationManager& notificationManager;
|
||||||
Controllers::Settings& settingsController;
|
Controllers::Settings& settingsController;
|
||||||
Controllers::HeartRateController& heartRateController;
|
Controllers::HeartRateController& heartRateController;
|
||||||
Controllers::MotionController& motionController;
|
Controllers::MotionController& motionController;
|
||||||
|
Controllers::SimpleWeatherService& weatherService;
|
||||||
|
|
||||||
lv_task_t* taskRefresh;
|
lv_task_t* taskRefresh;
|
||||||
Widgets::StatusIcons statusIcons;
|
Widgets::StatusIcons statusIcons;
|
||||||
|
@ -87,7 +87,8 @@ namespace Pinetime {
|
||||||
controllers.notificationManager,
|
controllers.notificationManager,
|
||||||
controllers.settingsController,
|
controllers.settingsController,
|
||||||
controllers.heartRateController,
|
controllers.heartRateController,
|
||||||
controllers.motionController);
|
controllers.motionController,
|
||||||
|
*controllers.weatherController);
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
|
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
|
||||||
|
|
189
src/displayapp/screens/WatchFaceFuzzy.cpp
Normal file
189
src/displayapp/screens/WatchFaceFuzzy.cpp
Normal 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);
|
||||||
|
}
|
100
src/displayapp/screens/WatchFaceFuzzy.h
Normal file
100
src/displayapp/screens/WatchFaceFuzzy.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -423,10 +423,11 @@ void WatchFaceInfineat::Refresh() {
|
||||||
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
lv_obj_align(labelMinutes, timeContainer, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
|
||||||
if (currentDate.IsUpdated()) {
|
if (currentDate.IsUpdated()) {
|
||||||
uint8_t day = dateTimeController.Day();
|
uint8_t day = dateTimeController.Day();
|
||||||
lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day);
|
Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
|
||||||
|
lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day);
|
||||||
lv_obj_realign(labelDate);
|
lv_obj_realign(labelDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>> currentDateTime {};
|
||||||
Utility::DirtyValue<uint32_t> stepCount {};
|
Utility::DirtyValue<uint32_t> stepCount {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
|
||||||
|
|
||||||
// Lines making up the side cover
|
// Lines making up the side cover
|
||||||
lv_obj_t* lineBattery;
|
lv_obj_t* lineBattery;
|
||||||
|
|
|
@ -540,7 +540,6 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||||
}
|
}
|
||||||
|
|
||||||
currentWeather = weatherService.Current();
|
currentWeather = weatherService.Current();
|
||||||
|
|
||||||
if (currentWeather.IsUpdated()) {
|
if (currentWeather.IsUpdated()) {
|
||||||
auto optCurrentWeather = currentWeather.Get();
|
auto optCurrentWeather = currentWeather.Get();
|
||||||
if (optCurrentWeather) {
|
if (optCurrentWeather) {
|
||||||
|
@ -551,12 +550,10 @@ void WatchFacePineTimeStyle::Refresh() {
|
||||||
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
|
||||||
lv_label_set_text_fmt(temperature, "%d°", temp);
|
lv_label_set_text_fmt(temperature, "%d°", temp);
|
||||||
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId));
|
||||||
lv_obj_realign(temperature);
|
} else {
|
||||||
lv_obj_realign(weatherIcon);
|
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(temperature);
|
||||||
lv_obj_realign(weatherIcon);
|
lv_obj_realign(weatherIcon);
|
||||||
}
|
}
|
||||||
|
|
259
src/displayapp/screens/WatchFaceSundial.cpp
Normal file
259
src/displayapp/screens/WatchFaceSundial.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
111
src/displayapp/screens/WatchFaceSundial.h
Normal file
111
src/displayapp/screens/WatchFaceSundial.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,7 +125,7 @@ void WatchFaceTerminal::Refresh() {
|
||||||
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
|
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
|
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
|
||||||
if (currentDate.IsUpdated()) {
|
if (currentDate.IsUpdated()) {
|
||||||
uint16_t year = dateTimeController.Year();
|
uint16_t year = dateTimeController.Year();
|
||||||
Controllers::DateTime::Months month = dateTimeController.Month();
|
Controllers::DateTime::Months month = dateTimeController.Month();
|
||||||
|
|
|
@ -45,8 +45,7 @@ namespace Pinetime {
|
||||||
Utility::DirtyValue<uint8_t> heartbeat {};
|
Utility::DirtyValue<uint8_t> heartbeat {};
|
||||||
Utility::DirtyValue<bool> heartbeatRunning {};
|
Utility::DirtyValue<bool> heartbeatRunning {};
|
||||||
Utility::DirtyValue<bool> notificationState {};
|
Utility::DirtyValue<bool> notificationState {};
|
||||||
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
|
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::days>> currentDate;
|
||||||
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
|
|
||||||
|
|
||||||
lv_obj_t* label_time;
|
lv_obj_t* label_time;
|
||||||
lv_obj_t* label_date;
|
lv_obj_t* label_date;
|
||||||
|
|
198
src/displayapp/screens/Weather.cpp
Normal file
198
src/displayapp/screens/Weather.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/displayapp/screens/Weather.h
Normal file
56
src/displayapp/screens/Weather.h
Normal 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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
61
src/displayapp/screens/WeatherSymbols.cpp
Normal file
61
src/displayapp/screens/WeatherSymbols.cpp
Normal 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 "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,40 +6,8 @@ namespace Pinetime {
|
||||||
namespace Applications {
|
namespace Applications {
|
||||||
namespace Screens {
|
namespace Screens {
|
||||||
namespace Symbols {
|
namespace Symbols {
|
||||||
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
|
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
|
||||||
switch (icon) {
|
const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,17 +36,19 @@ namespace {
|
||||||
|
|
||||||
SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
|
SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pinetime::Controllers::Settings& settingsController)
|
||||||
: app {app},
|
: app {app},
|
||||||
|
settings {settingsController},
|
||||||
checkboxList(
|
checkboxList(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
"Bluetooth",
|
"Bluetooth",
|
||||||
Symbols::bluetooth,
|
Symbols::bluetooth,
|
||||||
settingsController.GetBleRadioEnabled() ? 0 : 1,
|
settingsController.GetBleRadioEnabled() ? 0 : 1,
|
||||||
[&settings = settingsController](uint32_t index) {
|
[this](uint32_t index) {
|
||||||
const bool priorMode = settings.GetBleRadioEnabled();
|
const bool priorMode = settings.GetBleRadioEnabled();
|
||||||
const bool newMode = options[index].radioEnabled;
|
const bool newMode = options[index].radioEnabled;
|
||||||
if (newMode != priorMode) {
|
if (newMode != priorMode) {
|
||||||
settings.SetBleRadioEnabled(newMode);
|
settings.SetBleRadioEnabled(newMode);
|
||||||
|
this->app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CreateOptionArray()) {
|
CreateOptionArray()) {
|
||||||
|
@ -54,6 +56,4 @@ SettingBluetooth::SettingBluetooth(Pinetime::Applications::DisplayApp* app, Pine
|
||||||
|
|
||||||
SettingBluetooth::~SettingBluetooth() {
|
SettingBluetooth::~SettingBluetooth() {
|
||||||
lv_obj_clean(lv_scr_act());
|
lv_obj_clean(lv_scr_act());
|
||||||
// Pushing the message in the OnValueChanged function causes a freeze?
|
|
||||||
app->PushMessage(Pinetime::Applications::Display::Messages::BleRadioEnableToggle);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace Pinetime {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DisplayApp* app;
|
DisplayApp* app;
|
||||||
|
Pinetime::Controllers::Settings& settings;
|
||||||
CheckboxList checkboxList;
|
CheckboxList checkboxList;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
70
src/displayapp/screens/settings/SettingLocation.cpp
Normal file
70
src/displayapp/screens/settings/SettingLocation.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#include "displayapp/screens/settings/SettingLocation.h"
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
|
#include <nrf_log.h>
|
||||||
|
#include "displayapp/DisplayApp.h"
|
||||||
|
#include "displayapp/screens/Symbols.h"
|
||||||
|
#include "components/settings/Settings.h"
|
||||||
|
#include "displayapp/InfiniTimeTheme.h"
|
||||||
|
|
||||||
|
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) {
|
||||||
|
auto* screen = static_cast<SettingLocation*>(userData);
|
||||||
|
screen->UpdateScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingLocation::SettingLocation(Pinetime::Controllers::Settings& settingsController)
|
||||||
|
: settingsController {settingsController} {
|
||||||
|
|
||||||
|
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
|
||||||
|
lv_label_set_text_static(title, "Set location\n(lat/long/tz)");
|
||||||
|
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
|
||||||
|
lv_obj_align(title, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 15, 15);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Controllers::Settings::Location loc = settingsController.GetLocation();
|
||||||
|
|
||||||
|
latCounter.Create();
|
||||||
|
latCounter.SetValue(loc.latitude);
|
||||||
|
lv_obj_align(latCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LAT, POS_Y_TEXT);
|
||||||
|
latCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
|
||||||
|
|
||||||
|
longCounter.Create();
|
||||||
|
longCounter.SetValue(loc.longitude);
|
||||||
|
lv_obj_align(longCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_LONG, POS_Y_TEXT);
|
||||||
|
longCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
|
||||||
|
|
||||||
|
tzCounter.Create();
|
||||||
|
tzCounter.SetValue(loc.tzOffset);
|
||||||
|
lv_obj_align(tzCounter.GetObject(), nullptr, LV_ALIGN_CENTER, POS_X_TZ, POS_Y_TEXT);
|
||||||
|
tzCounter.SetValueChangedEventCallback(this, ValueChangedHandler);
|
||||||
|
|
||||||
|
UpdateScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingLocation::~SettingLocation() {
|
||||||
|
lv_obj_clean(lv_scr_act());
|
||||||
|
settingsController.SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingLocation::UpdateScreen() {
|
||||||
|
Controllers::Settings::Location loc = {
|
||||||
|
latitude: (int16_t)latCounter.GetValue(),
|
||||||
|
longitude: (int16_t)longCounter.GetValue(),
|
||||||
|
tzOffset: (int8_t)tzCounter.GetValue(),
|
||||||
|
};
|
||||||
|
settingsController.SetLocation(loc);
|
||||||
|
}
|
31
src/displayapp/screens/settings/SettingLocation.h
Normal file
31
src/displayapp/screens/settings/SettingLocation.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
|
#include "components/datetime/DateTimeController.h"
|
||||||
|
#include "components/settings/Settings.h"
|
||||||
|
#include "displayapp/widgets/Counter.h"
|
||||||
|
#include "displayapp/screens/Screen.h"
|
||||||
|
#include "displayapp/widgets/DotIndicator.h"
|
||||||
|
#include "displayapp/screens/settings/SettingSetDateTime.h"
|
||||||
|
|
||||||
|
namespace Pinetime {
|
||||||
|
namespace Applications {
|
||||||
|
namespace Screens {
|
||||||
|
class SettingLocation : public Screen {
|
||||||
|
public:
|
||||||
|
SettingLocation(Pinetime::Controllers::Settings& settingsController);
|
||||||
|
~SettingLocation() override;
|
||||||
|
|
||||||
|
void UpdateScreen();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Controllers::Settings& settingsController;
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,9 @@ namespace {
|
||||||
const char* name;
|
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::H12, "12-hour"},
|
||||||
{Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
|
{Pinetime::Controllers::Settings::ClockType::H24, "24-hour"},
|
||||||
{Pinetime::Controllers::Settings::ClockType::Fuzzy, "Fuzzy"},
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
|
std::array<CheckboxList::Item, CheckboxList::MaxItems> CreateOptionArray() {
|
||||||
|
|
|
@ -9,6 +9,37 @@ using namespace Pinetime::Applications::Screens;
|
||||||
constexpr const char* SettingWatchFace::title;
|
constexpr const char* SettingWatchFace::title;
|
||||||
constexpr const char* SettingWatchFace::symbol;
|
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 {
|
auto SettingWatchFace::CreateScreenList() const {
|
||||||
std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
|
std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
|
||||||
for (size_t i = 0; i < screens.size(); i++) {
|
for (size_t i = 0; i < screens.size(); i++) {
|
||||||
|
@ -20,7 +51,7 @@ auto SettingWatchFace::CreateScreenList() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
|
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::Settings& settingsController,
|
||||||
Pinetime::Controllers::FS& filesystem)
|
Pinetime::Controllers::FS& filesystem)
|
||||||
: app {app},
|
: app {app},
|
||||||
|
@ -44,7 +75,8 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
|
||||||
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
|
if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) {
|
||||||
watchfacesOnThisScreen[i] = {"", false};
|
watchfacesOnThisScreen[i] = {"", false};
|
||||||
} else {
|
} 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,
|
nScreens,
|
||||||
title,
|
title,
|
||||||
symbol,
|
symbol,
|
||||||
static_cast<uint32_t>(settingsController.GetWatchFace()),
|
static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
|
||||||
[&settings = settingsController](uint32_t index) {
|
[this, &settings = settingsController](uint32_t index) {
|
||||||
settings.SetWatchFace(static_cast<WatchFace>(index));
|
settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
|
||||||
settings.SaveSettings();
|
settings.SaveSettings();
|
||||||
},
|
},
|
||||||
watchfacesOnThisScreen);
|
watchfacesOnThisScreen);
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
#include "displayapp/screens/Screen.h"
|
#include "displayapp/screens/Screen.h"
|
||||||
#include "displayapp/screens/Symbols.h"
|
#include "displayapp/screens/Symbols.h"
|
||||||
#include "displayapp/screens/CheckboxList.h"
|
#include "displayapp/screens/CheckboxList.h"
|
||||||
#include "displayapp/screens/WatchFaceInfineat.h"
|
// #include "displayapp/screens/WatchFaceInfineat.h"
|
||||||
#include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
// #include "displayapp/screens/WatchFaceCasioStyleG7710.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
|
||||||
|
@ -19,8 +19,14 @@ namespace Pinetime {
|
||||||
|
|
||||||
class SettingWatchFace : public Screen {
|
class SettingWatchFace : public Screen {
|
||||||
public:
|
public:
|
||||||
|
struct Item {
|
||||||
|
const char* name;
|
||||||
|
WatchFace watchface;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
SettingWatchFace(DisplayApp* app,
|
SettingWatchFace(DisplayApp* app,
|
||||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
|
||||||
Pinetime::Controllers::Settings& settingsController,
|
Pinetime::Controllers::Settings& settingsController,
|
||||||
Pinetime::Controllers::FS& filesystem);
|
Pinetime::Controllers::FS& filesystem);
|
||||||
~SettingWatchFace() override;
|
~SettingWatchFace() override;
|
||||||
|
@ -33,7 +39,7 @@ namespace Pinetime {
|
||||||
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
|
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;
|
||||||
|
|
||||||
static constexpr int settingsPerScreen = 4;
|
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;
|
static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;
|
||||||
|
|
||||||
Controllers::Settings& settingsController;
|
Controllers::Settings& settingsController;
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace Pinetime {
|
||||||
|
|
||||||
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
{Symbols::shoe, "Steps", Apps::SettingSteps},
|
||||||
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
{Symbols::clock, "Date&Time", Apps::SettingSetDateTime},
|
||||||
|
{Symbols::map, "Location", Apps::SettingLocation},
|
||||||
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
|
{Symbols::cloudSunRain, "Weather", Apps::SettingWeatherFormat},
|
||||||
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
{Symbols::batteryHalf, "Battery", Apps::BatteryInfo},
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,8 @@ namespace Pinetime {
|
||||||
int max;
|
int max;
|
||||||
int value;
|
int value;
|
||||||
const int leadingZeroCount;
|
const int leadingZeroCount;
|
||||||
|
uint8_t containerHeight;
|
||||||
|
static constexpr uint8_t btnHeight = 50;
|
||||||
bool twelveHourMode = false;
|
bool twelveHourMode = false;
|
||||||
bool monthMode = false;
|
bool monthMode = false;
|
||||||
lv_font_t& font;
|
lv_font_t& font;
|
||||||
|
|
|
@ -22,6 +22,16 @@ namespace {
|
||||||
void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
|
void user_delay(uint32_t period_us, void* /*intf_ptr*/) {
|
||||||
nrf_delay_us(period_us);
|
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} {
|
Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
|
||||||
|
@ -74,7 +84,6 @@ void Bma421::Init() {
|
||||||
if (ret != BMA4_OK)
|
if (ret != BMA4_OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
struct bma4_accel_config accel_conf;
|
|
||||||
accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
|
accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
|
||||||
accel_conf.range = BMA4_ACCEL_RANGE_2G;
|
accel_conf.range = BMA4_ACCEL_RANGE_2G;
|
||||||
accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
|
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() {
|
Bma421::Values Bma421::Process() {
|
||||||
if (not isOk)
|
if (not isOk)
|
||||||
return {};
|
return {};
|
||||||
|
struct bma4_accel rawData;
|
||||||
struct bma4_accel data;
|
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;
|
uint32_t steps = 0;
|
||||||
bma423_step_counter_output(&steps, &bma);
|
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
|
// 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};
|
return {steps, data.y, data.x, data.z};
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace Pinetime {
|
||||||
TwiMaster& twiMaster;
|
TwiMaster& twiMaster;
|
||||||
uint8_t deviceAddress = 0x18;
|
uint8_t deviceAddress = 0x18;
|
||||||
struct bma4_dev bma;
|
struct bma4_dev bma;
|
||||||
|
struct bma4_accel_config accel_conf; // Store the device configuration for later reference.
|
||||||
bool isOk = false;
|
bool isOk = false;
|
||||||
bool isResetOk = false;
|
bool isResetOk = false;
|
||||||
DeviceTypes deviceType = DeviceTypes::Unknown;
|
DeviceTypes deviceType = DeviceTypes::Unknown;
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Driver for the HRS3300 heart rate sensor.
|
/** 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
|
* Experimentaly derived changes to improve signal/noise (see comments below) - Ceimour
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,8 +9,8 @@ Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) : spiMaster {spiMaster}, pinCsn {
|
||||||
nrf_gpio_pin_set(pinCsn);
|
nrf_gpio_pin_set(pinCsn);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Spi::Write(const uint8_t* data, size_t size) {
|
bool Spi::Write(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) {
|
||||||
return spiMaster.Write(pinCsn, data, size);
|
return spiMaster.Write(pinCsn, data, size, preTransactionHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
|
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
#include "drivers/SpiMaster.h"
|
#include "drivers/SpiMaster.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
|
@ -14,7 +15,7 @@ namespace Pinetime {
|
||||||
Spi& operator=(Spi&&) = delete;
|
Spi& operator=(Spi&&) = delete;
|
||||||
|
|
||||||
bool Init();
|
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 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);
|
bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize);
|
||||||
void Sleep();
|
void Sleep();
|
||||||
|
|
|
@ -94,32 +94,45 @@ bool SpiMaster::Init() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiMaster::SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
|
void SpiMaster::SetupWorkaroundForErratum58() {
|
||||||
// Create an event when SCK toggles.
|
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
|
||||||
NRF_GPIOTE->CONFIG[gpiote_channel] = (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos) | (spim->PSEL.SCK << GPIOTE_CONFIG_PSEL_Pos) |
|
nrfx_gpiote_in_config_t gpioteCfg = {.sense = NRF_GPIOTE_POLARITY_TOGGLE,
|
||||||
(GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos);
|
.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;
|
spiBaseAddress->EVENTS_END = 0;
|
||||||
|
|
||||||
// Disable IRQ
|
// Disable IRQ
|
||||||
spim->INTENCLR = (1 << 6);
|
spiBaseAddress->INTENCLR = (1 << 6);
|
||||||
spim->INTENCLR = (1 << 1);
|
spiBaseAddress->INTENCLR = (1 << 1);
|
||||||
spim->INTENCLR = (1 << 19);
|
spiBaseAddress->INTENCLR = (1 << 19);
|
||||||
|
workaroundActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiMaster::DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel) {
|
void SpiMaster::DisableWorkaroundForErratum58() {
|
||||||
NRF_GPIOTE->CONFIG[gpiote_channel] = 0;
|
nrfx_gpiote_pin_t pin = spiBaseAddress->PSEL.SCK;
|
||||||
NRF_PPI->CH[ppi_channel].EEP = 0;
|
if (workaroundActive) {
|
||||||
NRF_PPI->CH[ppi_channel].TEP = 0;
|
nrfx_gpiote_in_uninit(pin);
|
||||||
NRF_PPI->CHENSET = ppi_channel;
|
nrf_ppi_channel_disable(workaroundPpi);
|
||||||
|
}
|
||||||
spiBaseAddress->EVENTS_END = 0;
|
spiBaseAddress->EVENTS_END = 0;
|
||||||
spim->INTENSET = (1 << 6);
|
|
||||||
spim->INTENSET = (1 << 1);
|
// Enable IRQ
|
||||||
spim->INTENSET = (1 << 19);
|
spiBaseAddress->INTENSET = (1 << 6);
|
||||||
|
spiBaseAddress->INTENSET = (1 << 1);
|
||||||
|
spiBaseAddress->INTENSET = (1 << 19);
|
||||||
|
workaroundActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpiMaster::OnEndEvent() {
|
void SpiMaster::OnEndEvent() {
|
||||||
|
@ -136,17 +149,11 @@ void SpiMaster::OnEndEvent() {
|
||||||
|
|
||||||
spiBaseAddress->TASKS_START = 1;
|
spiBaseAddress->TASKS_START = 1;
|
||||||
} else {
|
} else {
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
||||||
if (taskToNotify != nullptr) {
|
|
||||||
vTaskNotifyGiveFromISR(taskToNotify, &xHigherPriorityTaskWoken);
|
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
||||||
}
|
|
||||||
|
|
||||||
nrf_gpio_pin_set(this->pinCsn);
|
nrf_gpio_pin_set(this->pinCsn);
|
||||||
currentBufferAddr = 0;
|
currentBufferAddr = 0;
|
||||||
BaseType_t xHigherPriorityTaskWoken2 = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken2);
|
xSemaphoreGiveFromISR(mutex, &xHigherPriorityTaskWoken);
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken | xHigherPriorityTaskWoken2);
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,21 +180,23 @@ void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
|
||||||
spiBaseAddress->EVENTS_END = 0;
|
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)
|
if (data == nullptr)
|
||||||
return false;
|
return false;
|
||||||
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
|
auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
|
||||||
ASSERT(ok == true);
|
ASSERT(ok == true);
|
||||||
taskToNotify = xTaskGetCurrentTaskHandle();
|
|
||||||
|
|
||||||
this->pinCsn = pinCsn;
|
this->pinCsn = pinCsn;
|
||||||
|
|
||||||
if (size == 1) {
|
if (size == 1) {
|
||||||
SetupWorkaroundForFtpan58(spiBaseAddress, 0, 0);
|
SetupWorkaroundForErratum58();
|
||||||
} else {
|
} else {
|
||||||
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
|
DisableWorkaroundForErratum58();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preTransactionHook != nullptr) {
|
||||||
|
preTransactionHook();
|
||||||
|
}
|
||||||
nrf_gpio_pin_clear(this->pinCsn);
|
nrf_gpio_pin_clear(this->pinCsn);
|
||||||
|
|
||||||
currentBufferAddr = (uint32_t) data;
|
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);
|
nrf_gpio_pin_set(this->pinCsn);
|
||||||
currentBufferAddr = 0;
|
currentBufferAddr = 0;
|
||||||
|
|
||||||
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
|
DisableWorkaroundForErratum58();
|
||||||
|
|
||||||
xSemaphoreGive(mutex);
|
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) {
|
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t* data, size_t dataSize) {
|
||||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||||
|
|
||||||
taskToNotify = nullptr;
|
|
||||||
|
|
||||||
this->pinCsn = pinCsn;
|
this->pinCsn = pinCsn;
|
||||||
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
|
DisableWorkaroundForErratum58();
|
||||||
spiBaseAddress->INTENCLR = (1 << 6);
|
spiBaseAddress->INTENCLR = (1 << 6);
|
||||||
spiBaseAddress->INTENCLR = (1 << 1);
|
spiBaseAddress->INTENCLR = (1 << 1);
|
||||||
spiBaseAddress->INTENCLR = (1 << 19);
|
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) {
|
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t* data, size_t dataSize) {
|
||||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||||
|
|
||||||
taskToNotify = nullptr;
|
|
||||||
|
|
||||||
this->pinCsn = pinCsn;
|
this->pinCsn = pinCsn;
|
||||||
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
|
DisableWorkaroundForErratum58();
|
||||||
spiBaseAddress->INTENCLR = (1 << 6);
|
spiBaseAddress->INTENCLR = (1 << 6);
|
||||||
spiBaseAddress->INTENCLR = (1 << 1);
|
spiBaseAddress->INTENCLR = (1 << 1);
|
||||||
spiBaseAddress->INTENCLR = (1 << 19);
|
spiBaseAddress->INTENCLR = (1 << 19);
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <FreeRTOS.h>
|
#include <FreeRTOS.h>
|
||||||
#include <semphr.h>
|
#include <semphr.h>
|
||||||
#include <task.h>
|
#include <task.h>
|
||||||
|
#include "nrfx_gpiote.h"
|
||||||
|
#include "nrf_ppi.h"
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Drivers {
|
namespace Drivers {
|
||||||
|
@ -31,7 +34,7 @@ namespace Pinetime {
|
||||||
SpiMaster& operator=(SpiMaster&&) = delete;
|
SpiMaster& operator=(SpiMaster&&) = delete;
|
||||||
|
|
||||||
bool Init();
|
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 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);
|
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();
|
void Wakeup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetupWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
|
void SetupWorkaroundForErratum58();
|
||||||
void DisableWorkaroundForFtpan58(NRF_SPIM_Type* spim, uint32_t ppi_channel, uint32_t gpiote_channel);
|
void DisableWorkaroundForErratum58();
|
||||||
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
|
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
|
||||||
void PrepareRx(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 uint32_t currentBufferAddr = 0;
|
||||||
volatile size_t currentBufferSize = 0;
|
volatile size_t currentBufferSize = 0;
|
||||||
volatile TaskHandle_t taskToNotify;
|
|
||||||
SemaphoreHandle_t mutex = nullptr;
|
SemaphoreHandle_t mutex = nullptr;
|
||||||
|
static constexpr nrf_ppi_channel_t workaroundPpi = NRF_PPI_CHANNEL0;
|
||||||
|
bool workaroundActive = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ void SpiNorFlash::Uninit() {
|
||||||
|
|
||||||
void SpiNorFlash::Sleep() {
|
void SpiNorFlash::Sleep() {
|
||||||
auto cmd = static_cast<uint8_t>(Commands::DeepPowerDown);
|
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")
|
NRF_LOG_INFO("[SpiNorFlash] Sleep")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "drivers/St7789.h"
|
#include "drivers/St7789.h"
|
||||||
#include <hal/nrf_gpio.h>
|
#include <hal/nrf_gpio.h>
|
||||||
#include <libraries/delay/nrf_delay.h>
|
|
||||||
#include <nrfx_log.h>
|
#include <nrfx_log.h>
|
||||||
#include "drivers/Spi.h"
|
#include "drivers/Spi.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
using namespace Pinetime::Drivers;
|
using namespace Pinetime::Drivers;
|
||||||
|
|
||||||
|
@ -29,37 +29,77 @@ void St7789::Init() {
|
||||||
DisplayOn();
|
DisplayOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::WriteCommand(uint8_t cmd) {
|
|
||||||
nrf_gpio_pin_clear(pinDataCommand);
|
|
||||||
WriteSpi(&cmd, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void St7789::WriteData(uint8_t data) {
|
void St7789::WriteData(uint8_t data) {
|
||||||
nrf_gpio_pin_set(pinDataCommand);
|
WriteData(&data, 1);
|
||||||
WriteSpi(&data, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::WriteSpi(const uint8_t* data, size_t size) {
|
void St7789::WriteData(const uint8_t* data, size_t size) {
|
||||||
spi.Write(data, 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() {
|
void St7789::SoftwareReset() {
|
||||||
|
EnsureSleepOutPostDelay();
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset));
|
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() {
|
void St7789::SleepOut() {
|
||||||
|
if (!sleepIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::SleepOut));
|
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() {
|
void St7789::SleepIn() {
|
||||||
|
if (sleepIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EnsureSleepOutPostDelay();
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::SleepIn));
|
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() {
|
void St7789::ColMod() {
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::ColMod));
|
WriteCommand(static_cast<uint8_t>(Commands::ColMod));
|
||||||
WriteData(0x55);
|
WriteData(0x55);
|
||||||
nrf_delay_ms(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::MemoryDataAccessControl() {
|
void St7789::MemoryDataAccessControl() {
|
||||||
|
@ -96,12 +136,10 @@ void St7789::RowAddressSet() {
|
||||||
|
|
||||||
void St7789::DisplayInversionOn() {
|
void St7789::DisplayInversionOn() {
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
|
WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn));
|
||||||
nrf_delay_ms(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::NormalModeOn() {
|
void St7789::NormalModeOn() {
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
|
WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn));
|
||||||
nrf_delay_ms(10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::DisplayOn() {
|
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(y0 & 0xff);
|
||||||
WriteData(y1 >> 8);
|
WriteData(y1 >> 8);
|
||||||
WriteData(y1 & 0xff);
|
WriteData(y1 & 0xff);
|
||||||
|
|
||||||
WriteToRam();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::WriteToRam() {
|
void St7789::WriteToRam(const uint8_t* data, size_t size) {
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
|
WriteCommand(static_cast<uint8_t>(Commands::WriteToRam));
|
||||||
|
WriteData(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::SetVdv() {
|
void St7789::SetVdv() {
|
||||||
|
@ -137,17 +174,6 @@ void St7789::SetVdv() {
|
||||||
|
|
||||||
void St7789::DisplayOff() {
|
void St7789::DisplayOff() {
|
||||||
WriteCommand(static_cast<uint8_t>(Commands::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) {
|
void St7789::VerticalScrollStartAddress(uint16_t line) {
|
||||||
|
@ -160,27 +186,20 @@ void St7789::VerticalScrollStartAddress(uint16_t line) {
|
||||||
void St7789::Uninit() {
|
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) {
|
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);
|
SetAddrWindow(x, y, x + width - 1, y + height - 1);
|
||||||
nrf_gpio_pin_set(pinDataCommand);
|
WriteToRam(data, size);
|
||||||
WriteSpi(data, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void St7789::HardwareReset() {
|
void St7789::HardwareReset() {
|
||||||
nrf_gpio_pin_clear(pinReset);
|
nrf_gpio_pin_clear(pinReset);
|
||||||
nrf_delay_ms(10);
|
vTaskDelay(pdMS_TO_TICKS(1));
|
||||||
nrf_gpio_pin_set(pinReset);
|
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() {
|
void St7789::Sleep() {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <FreeRTOS.h>
|
||||||
|
|
||||||
namespace Pinetime {
|
namespace Pinetime {
|
||||||
namespace Drivers {
|
namespace Drivers {
|
||||||
|
@ -16,9 +19,7 @@ namespace Pinetime {
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void Uninit();
|
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 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);
|
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 pinDataCommand;
|
||||||
uint8_t pinReset;
|
uint8_t pinReset;
|
||||||
uint8_t verticalScrollingStartAddress = 0;
|
uint8_t verticalScrollingStartAddress = 0;
|
||||||
|
bool sleepIn;
|
||||||
|
TickType_t lastSleepExit;
|
||||||
|
|
||||||
void HardwareReset();
|
void HardwareReset();
|
||||||
void SoftwareReset();
|
void SoftwareReset();
|
||||||
void SleepOut();
|
void SleepOut();
|
||||||
|
void EnsureSleepOutPostDelay();
|
||||||
void SleepIn();
|
void SleepIn();
|
||||||
void ColMod();
|
void ColMod();
|
||||||
void MemoryDataAccessControl();
|
void MemoryDataAccessControl();
|
||||||
void DisplayInversionOn();
|
void DisplayInversionOn();
|
||||||
void NormalModeOn();
|
void NormalModeOn();
|
||||||
void WriteToRam();
|
void WriteToRam(const uint8_t* data, size_t size);
|
||||||
void DisplayOn();
|
void DisplayOn();
|
||||||
void DisplayOff();
|
void DisplayOff();
|
||||||
|
|
||||||
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
void SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||||
void SetVdv();
|
void SetVdv();
|
||||||
void WriteCommand(uint8_t cmd);
|
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 {
|
enum class Commands : uint8_t {
|
||||||
SoftwareReset = 0x01,
|
SoftwareReset = 0x01,
|
||||||
|
@ -67,6 +72,7 @@ namespace Pinetime {
|
||||||
VdvSet = 0xc4,
|
VdvSet = 0xc4,
|
||||||
};
|
};
|
||||||
void WriteData(uint8_t data);
|
void WriteData(uint8_t data);
|
||||||
|
void WriteData(const uint8_t* data, size_t size);
|
||||||
void ColumnAddressSet();
|
void ColumnAddressSet();
|
||||||
|
|
||||||
static constexpr uint16_t Width = 240;
|
static constexpr uint16_t Width = 240;
|
||||||
|
|
|
@ -103,10 +103,7 @@ void HeartRateTask::Work() {
|
||||||
void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
|
void HeartRateTask::PushMessage(HeartRateTask::Messages msg) {
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
|
xQueueSendFromISR(messageQueue, &msg, &xHigherPriorityTaskWoken);
|
||||||
if (xHigherPriorityTaskWoken) {
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
/* Actual macro used here is port specific. */
|
|
||||||
// TODO : should I do something here?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeartRateTask::StartMeasurement() {
|
void HeartRateTask::StartMeasurement() {
|
||||||
|
|
|
@ -729,7 +729,9 @@ typedef void* lv_obj_user_data_t;
|
||||||
#define LV_USE_TABLE 1
|
#define LV_USE_TABLE 1
|
||||||
#if LV_USE_TABLE
|
#if LV_USE_TABLE
|
||||||
#define LV_TABLE_COL_MAX 12
|
#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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
1
src/libs/sunset
Submodule
1
src/libs/sunset
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 4b08734eb8856154d7226bfdced91a571fb6a64b
|
|
@ -10,7 +10,6 @@
|
||||||
#include <libraries/gpiote/app_gpiote.h>
|
#include <libraries/gpiote/app_gpiote.h>
|
||||||
#include <hal/nrf_wdt.h>
|
#include <hal/nrf_wdt.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <components/gfx/Gfx.h>
|
|
||||||
#include <drivers/St7789.h>
|
#include <drivers/St7789.h>
|
||||||
#include <components/brightness/BrightnessController.h>
|
#include <components/brightness/BrightnessController.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -48,7 +47,6 @@ Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
|
||||||
Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
|
Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
|
||||||
Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset};
|
Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand, Pinetime::PinMap::LcdReset};
|
||||||
|
|
||||||
Pinetime::Components::Gfx gfx {lcd};
|
|
||||||
Pinetime::Controllers::BrightnessController brightnessController;
|
Pinetime::Controllers::BrightnessController brightnessController;
|
||||||
|
|
||||||
void DisplayProgressBar(uint8_t percent, uint16_t color);
|
void DisplayProgressBar(uint8_t percent, uint16_t color);
|
||||||
|
@ -92,7 +90,6 @@ void Process(void* /*instance*/) {
|
||||||
spiNorFlash.Wakeup();
|
spiNorFlash.Wakeup();
|
||||||
brightnessController.Init();
|
brightnessController.Init();
|
||||||
lcd.Init();
|
lcd.Init();
|
||||||
gfx.Init();
|
|
||||||
|
|
||||||
NRF_LOG_INFO("Display logo")
|
NRF_LOG_INFO("Display logo")
|
||||||
DisplayLogo();
|
DisplayLogo();
|
||||||
|
@ -124,7 +121,6 @@ void DisplayLogo() {
|
||||||
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
|
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
|
||||||
for (int i = 0; i < displayWidth; i++) {
|
for (int i = 0; i < displayWidth; i++) {
|
||||||
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
|
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
|
||||||
ulTaskNotifyTake(pdTRUE, 500);
|
|
||||||
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
|
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +129,6 @@ void DisplayProgressBar(uint8_t percent, uint16_t color) {
|
||||||
static constexpr uint8_t barHeight = 20;
|
static constexpr uint8_t barHeight = 20;
|
||||||
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
|
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
|
||||||
for (int i = 0; i < barHeight; i++) {
|
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));
|
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);
|
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,13 @@ def main():
|
||||||
img = Image.open(img_path)
|
img = Image.open(img_path)
|
||||||
img_height = img.height
|
img_height = img.height
|
||||||
img_width = img.width
|
img_width = img.width
|
||||||
|
if args.color_format == "CF_TRUE_COLOR_ALPHA" and img.mode != "RGBA":
|
||||||
|
# support pictures stored in other formats like with a color palette 'P'
|
||||||
|
# see: https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
|
||||||
|
img = img.convert(mode="RGBA")
|
||||||
|
elif args.color_format == "CF_INDEXED_1_BIT" and img.mode != "L":
|
||||||
|
# for CF_INDEXED_1_BIT we need just a grayscale value per pixel
|
||||||
|
img = img.convert(mode="L")
|
||||||
if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
|
if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
|
||||||
buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
|
buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
|
||||||
for y in range(img_height):
|
for y in range(img_height):
|
||||||
|
@ -140,7 +147,7 @@ def main():
|
||||||
|
|
||||||
for y in range(img_height):
|
for y in range(img_height):
|
||||||
for x in range(img_width):
|
for x in range(img_width):
|
||||||
c, a = img.getpixel((x,y))
|
c = img.getpixel((x,y))
|
||||||
p = w * y + (x >> 3) + 8 # +8 for the palette
|
p = w * y + (x >> 3) + 8 # +8 for the palette
|
||||||
buf[p] |= (c & 0x1) << (7 - (x & 0x7))
|
buf[p] |= (c & 0x1) << (7 - (x & 0x7))
|
||||||
# write palette information, for indexed-1-bit we need palette with two values
|
# write palette information, for indexed-1-bit we need palette with two values
|
||||||
|
|
|
@ -342,8 +342,12 @@ void SystemTask::Work() {
|
||||||
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
||||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
|
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::Hours &&
|
||||||
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
||||||
|
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
|
||||||
|
// request running first and repush the chime message
|
||||||
if (state == SystemTaskState::Sleeping) {
|
if (state == SystemTaskState::Sleeping) {
|
||||||
GoToRunning();
|
GoToRunning();
|
||||||
|
PushMessage(msg);
|
||||||
|
} else {
|
||||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,8 +357,12 @@ void SystemTask::Work() {
|
||||||
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
if (settingsController.GetNotificationStatus() != Controllers::Settings::Notification::Sleep &&
|
||||||
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
|
settingsController.GetChimeOption() == Controllers::Settings::ChimesOption::HalfHours &&
|
||||||
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
alarmController.State() != AlarmController::AlarmState::Alerting) {
|
||||||
|
// if sleeping, we can't send a chime to displayApp yet (SPI flash switched off)
|
||||||
|
// request running first and repush the chime message
|
||||||
if (state == SystemTaskState::Sleeping) {
|
if (state == SystemTaskState::Sleeping) {
|
||||||
GoToRunning();
|
GoToRunning();
|
||||||
|
PushMessage(msg);
|
||||||
|
} else {
|
||||||
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
displayApp.PushMessage(Pinetime::Applications::Display::Messages::Chime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,7 +426,8 @@ void SystemTask::UpdateMotion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
|
if (state == SystemTaskState::Sleeping && !(settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::RaiseWrist) ||
|
||||||
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake))) {
|
settingsController.isWakeUpModeOn(Pinetime::Controllers::Settings::WakeUpMode::Shake) ||
|
||||||
|
motionController.GetService()->IsMotionNotificationSubscribed())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,10 +512,7 @@ void SystemTask::PushMessage(System::Messages msg) {
|
||||||
if (in_isr()) {
|
if (in_isr()) {
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
|
xQueueSendFromISR(systemTasksMsgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
/* Actual macro used here is port specific. */
|
|
||||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
|
xQueueSend(systemTasksMsgQueue, &msg, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user