Compare commits
125 Commits
wb/fuzzy-n
...
wb/fucktim
Author | SHA1 | Date | |
---|---|---|---|
7f52473337 | |||
0cae5609ae | |||
|
2105a7b63d | ||
|
79ee886904 | ||
|
b1d70ae2ed | ||
|
a77a3dcb8b | ||
|
8aefa3b9a6 | ||
|
6c7eb6630e | ||
|
4dd0d60eeb | ||
|
a2ced5659d | ||
|
5ea9c5537e | ||
|
f7c87a700d | ||
|
e247bd7019 | ||
|
29ad09f4ef | ||
|
afeded0126 | ||
|
57b6db8b2a | ||
|
0076962588 | ||
|
e6ee548536 | ||
|
1808634f0e | ||
|
cfaad261dc | ||
|
f1651c8000 | ||
|
8a2ee437f5 | ||
|
06b721a71f | ||
|
771008495e | ||
|
f032847ae1 | ||
|
97ba39988b | ||
|
879bdccd92 | ||
|
8598142c27 | ||
|
a2356f2f4a | ||
|
3db4e012ce | ||
|
a0cd439efc | ||
|
997e4cee8c | ||
|
ad3bf49c7b | ||
|
7ca0418c82 | ||
|
c3d05901a0 | ||
|
b3756e45fa | ||
|
a266202831 | ||
|
c8236afbef | ||
|
5040733a97 | ||
|
fd019c7aad | ||
|
975bfc5420 | ||
|
2625ed39e5 | ||
|
a3dbcd62f6 | ||
|
4fddf93114 | ||
|
83922fb3de | ||
|
95917c65a5 | ||
|
3a0d673df4 | ||
|
53dc9dafe7 | ||
|
a407902b06 | ||
|
3e8accde69 | ||
|
2bb611db8e | ||
|
ef88e8165c | ||
|
da9ab4a7b4 | ||
|
0bcd7e0009 | ||
|
bf69e0dcc5 | ||
|
947c4f5067 | ||
|
0960d67001 | ||
|
5385f7e275 | ||
|
e884b053d3 | ||
|
85a2181b64 | ||
|
3dca742b65 | ||
|
20ac7e8df3 | ||
|
f8f8993fac | ||
|
fdc3b8badb | ||
|
f9a16feeaf | ||
|
4930c0cab7 | ||
|
a449b272f7 | ||
|
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
|
@ -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}
|
18
.github/workflows/main.yml
vendored
|
@ -46,20 +46,30 @@ jobs:
|
|||
# Unzip the package because Upload Artifact will zip up the files
|
||||
- name: Unzip DFU package
|
||||
run: unzip ./build/output/pinetime-mcuboot-app-dfu-*.zip -d ./build/output/pinetime-mcuboot-app-dfu
|
||||
- name: Set ref_name, but replace slashes with dashes.
|
||||
shell: bash
|
||||
env:
|
||||
ref_name: ${{ github.head_ref || github.ref_name }}
|
||||
run: echo "REF_NAME=${ref_name//\//-}" >> $GITHUB_ENV
|
||||
- name: Upload DFU artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: InfiniTime DFU ${{ github.head_ref }}
|
||||
name: InfiniTime DFU ${{ env.REF_NAME }}
|
||||
path: ./build/output/pinetime-mcuboot-app-dfu/*
|
||||
- name: Upload MCUBoot image artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: InfiniTime MCUBoot image ${{ github.head_ref }}
|
||||
name: InfiniTime MCUBoot image ${{ env.REF_NAME }}
|
||||
path: ./build/output/pinetime-mcuboot-app-image-*.bin
|
||||
- name: Upload standalone ELF artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: InfiniTime image ${{ env.REF_NAME }}
|
||||
path: ./build/output/src/pinetime-app-*.out
|
||||
- name: Upload resources artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: InfiniTime resources ${{ github.head_ref }}
|
||||
name: InfiniTime resources ${{ env.REF_NAME }}
|
||||
path: ./build/output/infinitime-resources-*.zip
|
||||
|
||||
build-simulator:
|
||||
|
@ -100,7 +110,7 @@ jobs:
|
|||
- name: Upload simulator executable
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: infinisim-${{ github.head_ref }}
|
||||
name: infinisim-${{ env.REF_NAME }}
|
||||
path: build_lv_sim/infinisim
|
||||
|
||||
get-base-ref-size:
|
||||
|
|
3
.gitmodules
vendored
|
@ -4,9 +4,6 @@
|
|||
[submodule "src/libs/littlefs"]
|
||||
path = src/libs/littlefs
|
||||
url = https://github.com/littlefs-project/littlefs.git
|
||||
[submodule "src/libs/QCBOR"]
|
||||
path = src/libs/QCBOR
|
||||
url = https://github.com/laurencelundblade/QCBOR.git
|
||||
[submodule "src/libs/arduinoFFT"]
|
||||
path = src/libs/arduinoFFT
|
||||
url = https://github.com/kosme/arduinoFFT.git
|
||||
|
|
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": [
|
||||
{
|
||||
"name": "nrfCC",
|
||||
|
@ -14,6 +19,21 @@
|
|||
"intelliSenseMode": "linux-gcc-arm",
|
||||
"configurationProvider": "ms-vscode.cpp-tools",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||
},
|
||||
{
|
||||
"name": "nrfCC Devcontainer",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/src/**",
|
||||
"${workspaceFolder}/src"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "${TOOLS_DIR}/${GCC_ARM_PATH}/bin/arm-none-eabi-gcc",
|
||||
"cStandard": "c99",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "linux-gcc-arm",
|
||||
"configurationProvider": "ms-vscode.cpp-tools",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
|
6
.vscode/cmake-kits.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name": "InfiniTime Compiler",
|
||||
"environmentSetupScript": "${workspaceFolder}/docker/build.sh"
|
||||
}
|
||||
]
|
45
.vscode/launch.json
vendored
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug - Openocd docker Remote",
|
||||
"type":"cortex-debug",
|
||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
||||
"type": "cortex-debug",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"executable": "${command:cmake.launchTargetPath}",
|
||||
"request": "launch",
|
||||
"servertype": "external",
|
||||
// This may need to be arm-none-eabi-gdb depending on your system
|
||||
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
|
||||
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
|
||||
// Connect to an already running OpenOCD instance
|
||||
"gdbTarget": "host.docker.internal:3333",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
"runToMain": true,
|
||||
"runToEntryPoint": "main",
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
|
@ -23,18 +21,16 @@
|
|||
},
|
||||
{
|
||||
"name": "Debug - Openocd Local",
|
||||
"type":"cortex-debug",
|
||||
"cortex-debug.armToolchainPath":"${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin",
|
||||
"type": "cortex-debug",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"executable": "${command:cmake.launchTargetPath}",
|
||||
"request": "launch",
|
||||
"servertype": "openocd",
|
||||
// This may need to be arm-none-eabi-gdb depending on your system
|
||||
"gdbPath" : "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
|
||||
"gdbPath": "${env:ARM_NONE_EABI_TOOLCHAIN_PATH}/bin/arm-none-eabi-gdb",
|
||||
// Connect to an already running OpenOCD instance
|
||||
"gdbTarget": "localhost:3333",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
"runToMain": true,
|
||||
"runToEntryPoint": "main",
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
|
@ -51,6 +47,11 @@
|
|||
"showDevDebugOutput": false,
|
||||
"servertype": "openocd",
|
||||
"runToMain": true,
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
"continue"
|
||||
],
|
||||
// Only use armToolchainPath if your arm-none-eabi-gdb is not in your path (some GCC packages does not contain arm-none-eabi-gdb)
|
||||
"armToolchainPath": "${workspaceRoot}/../gcc-arm-none-eabi-10.3-2021.10/bin",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
|
@ -58,7 +59,25 @@
|
|||
"interface/stlink.cfg",
|
||||
"target/nrf52.cfg"
|
||||
],
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Debug - Openocd Devcontainer",
|
||||
"type": "cortex-debug",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"executable": "${command:cmake.launchTargetPath}",
|
||||
"request": "launch",
|
||||
"servertype": "external",
|
||||
// FIXME: This is hardcoded. I have no idea how to use the values set in build.sh here
|
||||
"gdbPath": "/opt/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gdb",
|
||||
// Connect to an already running OpenOCD instance
|
||||
"gdbTarget": "host.docker.internal:3333",
|
||||
"svdFile": "${workspaceRoot}/nrf52.svd",
|
||||
"runToEntryPoint": "main",
|
||||
// Work around for stopping at main on restart
|
||||
"postRestartCommands": [
|
||||
"break main",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
15
.vscode/settings.json
vendored
|
@ -1,9 +1,20 @@
|
|||
{
|
||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||
"cmake.configureArgs": [
|
||||
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:ARM_NONE_EABI_TOOLCHAIN_PATH}",
|
||||
"-DNRF5_SDK_PATH=${env:NRF5_SDK_PATH}",
|
||||
"-DARM_NONE_EABI_TOOLCHAIN_PATH=${env:TOOLS_DIR}/${env:GCC_ARM_PATH}",
|
||||
"-DNRF5_SDK_PATH=${env:TOOLS_DIR}/${env:NRF_SDK_VER}",
|
||||
],
|
||||
"cmake.statusbar.advanced": {
|
||||
"launch": {
|
||||
"visibility": "hidden"
|
||||
},
|
||||
"launchTarget": {
|
||||
"visibility": "hidden"
|
||||
},
|
||||
"debug": {
|
||||
"visibility": "hidden"
|
||||
}
|
||||
},
|
||||
"cmake.generator": "Unix Makefiles",
|
||||
"clang-tidy.buildPath": "build/compile_commands.json",
|
||||
"files.associations": {
|
||||
|
|
22
.vscode/tasks.json
vendored
|
@ -1,20 +1,6 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "create openocd build",
|
||||
"type": "shell",
|
||||
"command": "/opt/create_build_openocd.sh",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "update submodules",
|
||||
"type": "shell",
|
||||
|
@ -31,14 +17,6 @@
|
|||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "BuildInit",
|
||||
"dependsOn": [
|
||||
"update submodules",
|
||||
"create openocd build"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
|||
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
|
||||
|
||||
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
|
||||
project(pinetime VERSION 1.15.0 LANGUAGES C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
|
39
README.md
|
@ -1,27 +1,50 @@
|
|||
# [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime)
|
||||
<div align="center">
|
||||
|
||||
![InfiniTime logo](doc/logo/infinitime-logo-small.jpg "InfiniTime Logo")
|
||||
![Header Image](doc/logo/watchface_collage.png)
|
||||
|
||||
Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/pinetime/) with many features, written in modern C++.
|
||||
<br>
|
||||
|
||||
[![GitHub tag](https://img.shields.io/github/tag/InfiniTimeOrg/InfiniTime?include_prereleases=&sort=semver&color=blue)](https://github.com/InfiniTimeOrg/InfiniTime/releases)
|
||||
[![GitHub License](https://img.shields.io/github/license/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniLink/blob/main/LICENSE)
|
||||
[![Issues - InfiniTime](https://img.shields.io/github/issues/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/issues)
|
||||
[![Pull Requests - InfiniTime](https://img.shields.io/github/issues-pr/InfiniTimeOrg/InfiniTime)](https://github.com/InfiniTimeOrg/InfiniTime/pulls)
|
||||
[![Downloads - InfiniTime](https://img.shields.io/github/downloads/InfiniTimeOrg/InfiniTime/total)](https://github.com/InfiniTimeOrg/InfiniTime)
|
||||
[![Stars - InfiniTime](https://img.shields.io/github/stars/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/stargazers)
|
||||
[![Forks - InfiniTime](https://img.shields.io/github/forks/InfiniTimeOrg/InfiniTime?style=social)](https://github.com/InfiniTimeOrg/InfiniTime/network/members)
|
||||
|
||||
# InfiniTime
|
||||
|
||||
*Fast open-source firmware for the [PineTime smartwatch](https://pine64.org/devices/pinetime/) with many features, written in modern C++.*
|
||||
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
## New to InfiniTime?
|
||||
|
||||
- [Getting started with InfiniTime](doc/gettingStarted/gettingStarted-1.0.md)
|
||||
- [Updating the software](doc/gettingStarted/updating-software.md)
|
||||
- [About the firmware and bootloader](doc/gettingStarted/about-software.md)
|
||||
- [PineTimeStyle Watch face](https://wiki.pine64.org/wiki/PineTimeStyle)
|
||||
- [Weather integration](https://wiki.pine64.org/wiki/Infinitime-Weather)
|
||||
- [Available apps](doc/gettingStarted/Applications.md)
|
||||
- [Available watch faces](/doc/gettingStarted/Watchfaces.md)
|
||||
- [PineTimeStyle Watch face](https://pine64.org/documentation/PineTime/Watchfaces/PineTimeStyle)
|
||||
- [Weather integration](https://pine64.org/documentation/PineTime/Software/InfiniTime_weather/)
|
||||
|
||||
### Companion apps
|
||||
|
||||
- [Gadgetbridge](https://gadgetbridge.org/) (Android)
|
||||
- [AmazFish](https://openrepos.net/content/piggz/amazfish/) (SailfishOS)
|
||||
- [Amazfish](https://github.com/piggz/harbour-amazfish/) ([SailfishOS](https://sailfishos-chum.github.io/apps/harbour-amazfish/), [Ubuntu Touch](https://open-store.io/app/uk.co.piggz.amazfish), [Flatpak](https://flathub.org/apps/uk.co.piggz.amazfish))
|
||||
- [Siglo](https://github.com/alexr4535/siglo) (Linux)
|
||||
- [InfiniLink](https://github.com/InfiniTimeOrg/InfiniLink) (iOS)
|
||||
- [ITD](https://gitea.elara.ws/Elara6331/itd) (Linux)
|
||||
- [WatchMate](https://github.com/azymohliad/watchmate) (Linux)
|
||||
- [InfiniTimeExplorer](https://infinitimeexplorer.netlify.app) (Web)
|
||||
|
||||
***Note**: We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
<br>
|
||||
|
||||
> *InfiniTimeExplorer is only compatible with web browsers that support Web BLE. Current fully supported browsers include Chrome and Microsoft Edge.*
|
||||
>
|
||||
> *We removed mentions to NRFConnect as this app is closed source and recent versions do not work anymore with InfiniTime (the last version known to work is 4.24.3). If you used NRFConnect in the past, we recommend you switch to [Gadgetbridge](https://gadgetbridge.org/).*
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -35,7 +58,7 @@ Fast open-source firmware for the [PineTime smartwatch](https://www.pine64.org/p
|
|||
|
||||
### Contributing
|
||||
|
||||
- [How to contribute?](CONTRIBUTING.md)
|
||||
- [How to contribute](CONTRIBUTING.md)
|
||||
- [Coding conventions](doc/coding-convention.md)
|
||||
|
||||
### Build, flash and debug
|
||||
|
|
|
@ -7,9 +7,9 @@ My own contribution is little more than a brute force conversion to
|
|||
python3. It is sparsely tested so there are likely to be a few
|
||||
remaining bytes versus string bugs remaining in the places I didn't test
|
||||
. I used it primarily as part of
|
||||
[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to
|
||||
[wasp-os](https://github.com/wasp-os/wasp-os) as a way to
|
||||
deliver OTA updates to nRF52-based smart watches, especially the
|
||||
[Pine64 PineTime](https://www.pine64.org/pinetime/).
|
||||
[Pine64 PineTime](https://pine64.org/devices/pinetime/).
|
||||
|
||||
## What does it do?
|
||||
|
||||
|
|
|
@ -21,3 +21,5 @@ The current raw motion values. This is a 3 `int16_t` array:
|
|||
- [0] : X
|
||||
- [1] : Y
|
||||
- [2] : Z
|
||||
|
||||
The three motion values are in units of "binary milli-g", where 1g is represented by a value of 1024.
|
||||
|
|
|
@ -32,7 +32,7 @@ The .devcontainer folder contains the configuration and scripts for using a Dock
|
|||
|
||||
Using the [Remote-Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension is recommended. It will handle configuring the Docker virtual machine and setting everything up.
|
||||
|
||||
More documentation is available in the [readme in .devcontainer](../.devcontainer/README.md)
|
||||
More documentation is available in the [readme in .devcontainer](usingDevcontainers.md)
|
||||
|
||||
### DevContainer on Ubuntu
|
||||
|
||||
|
|
|
@ -35,18 +35,20 @@ that will call the method `Refresh()` periodically.
|
|||
|
||||
## App types
|
||||
|
||||
There are basically 2 types of applications : **system** apps and **user** apps.
|
||||
There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**.
|
||||
|
||||
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
|
||||
The watchfaces, settings, notifications and the application launcher are examples of such system applications.
|
||||
Settings, notifications and the application launcher are examples of such system applications.
|
||||
|
||||
**User** applications are optionally built into the firmware. They extend the functionalities of the system.
|
||||
|
||||
The distinction between **system** and **user** applications allows for more flexibility and customization.
|
||||
This allows to easily select which user applications must be built into the firmware
|
||||
**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware.
|
||||
|
||||
The distinction between **system** apps, **user** apps and watch faces allows for more flexibility and customization.
|
||||
This allows to easily select which user applications and watch faces must be built into the firmware
|
||||
without overflowing the system memory.
|
||||
|
||||
## Apps initialization
|
||||
## Apps and watch faces initialization
|
||||
|
||||
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
|
||||
This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
|
||||
|
@ -55,6 +57,8 @@ The constructor of **system** apps is called directly. If the application is a *
|
|||
the corresponding `AppDescription` is first retrieved from `userApps`
|
||||
and then the function `create` is called to create an instance of the app.
|
||||
|
||||
Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`.
|
||||
|
||||
## User application selection at build time
|
||||
|
||||
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
|
||||
|
@ -85,6 +89,32 @@ struct AppTraits<Apps::Alarm> {
|
|||
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
|
||||
to list all available applications.
|
||||
|
||||
## Watch face selection at build time
|
||||
|
||||
The list of available watch faces is also generated at build time by the `consteval`
|
||||
function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps.
|
||||
Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
|
||||
Here is an example of `WatchFaceTraits`:
|
||||
```c++
|
||||
template <>
|
||||
struct WatchFaceTraits<WatchFace::Analog> {
|
||||
static constexpr WatchFace watchFace = WatchFace::Analog;
|
||||
static constexpr const char* name = "Analog face";
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::WatchFaceAnalog(controllers.dateTimeController,
|
||||
controllers.batteryController,
|
||||
controllers.bleController,
|
||||
controllers.notificationManager,
|
||||
controllers.settingsController);
|
||||
};
|
||||
|
||||
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Creating your own app
|
||||
|
||||
A minimal user app could look like this:
|
||||
|
@ -110,10 +140,10 @@ namespace Pinetime {
|
|||
}
|
||||
|
||||
template <>
|
||||
struct AppTraits<Apps:MyApp> {
|
||||
struct AppTraits<Apps::MyApp> {
|
||||
static constexpr Apps app = Apps::MyApp;
|
||||
static constexpr const char* icon = Screens::Symbol::myApp;
|
||||
static Screens::Screens* Create(AppController& controllers) {
|
||||
static constexpr const char* icon = Screens::Symbols::myApp;
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::MyApp();
|
||||
}
|
||||
};
|
||||
|
@ -146,7 +176,7 @@ Now we have our very own app, but InfiniTime does not know about it yet.
|
|||
The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
|
||||
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
|
||||
The next step to making it launch-able is to give your app an id.
|
||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
|
||||
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/apps/Apps.h](/src/displayapp/apps/Apps.h.in)).
|
||||
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
|
||||
to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
|
||||
|
||||
|
@ -168,6 +198,15 @@ Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the ap
|
|||
$ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ...
|
||||
```
|
||||
|
||||
Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake.
|
||||
It must be set with the comma separated list of watch faces that will be built into the firmware.
|
||||
|
||||
Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat:
|
||||
|
||||
```cmake
|
||||
$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ...
|
||||
```
|
||||
|
||||
You should now be able to [build](../buildAndProgram.md) the firmware
|
||||
and flash it to your PineTime. Yay!
|
||||
|
||||
|
|
99
doc/gettingStarted/Applications.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Applications
|
||||
|
||||
InfiniTime has 13 apps on the `main` branch at the time of writing.
|
||||
|
||||
## List of apps
|
||||
- Stopwatch
|
||||
- Alarm
|
||||
- Timer
|
||||
- Steps
|
||||
- Heartrate
|
||||
- Music
|
||||
- InfiniPaint
|
||||
- Paddle
|
||||
- 2
|
||||
- InfiniDice
|
||||
- Metronome
|
||||
- Maps
|
||||
- Weather
|
||||
|
||||
### Stopwatch
|
||||
![Stopwatch UI](/doc/gettingStarted/AppsScreenshots/stopwatch.png)
|
||||
- Press the Start button (bottom right) to start or stop the timer.
|
||||
- You can also press the side button while the timer is running to pause the timer.
|
||||
- Press the Flag button (bottom left) to add a lap.
|
||||
- The stopwatch will not yet continue counting time while the app is closed.
|
||||
|
||||
### Alarm
|
||||
![Alarm UI](/doc/gettingStarted/AppsScreenshots/alarm.png)
|
||||
- Ajust the time with the time picker.
|
||||
- Press the Info button in the top middle to see time remaning.
|
||||
- Use the toggle in the bottom left to turn the alarm on/off.
|
||||
- Use the button in the bottom right to change the alarm frequency.
|
||||
- You can choose between once, daily, or Monday - Friday.
|
||||
|
||||
### Timer
|
||||
![Timer UI](/doc/gettingStarted/AppsScreenshots/timer.png)
|
||||
- Ajust how long the timer should go for with the time picker.
|
||||
- Press the Start button at the bottom to start/stop the timer.
|
||||
|
||||
### Steps
|
||||
![Steps UI](/doc/gettingStarted/AppsScreenshots/steps.png)
|
||||
- The total count of steps for the current display will show in the middle of the screen.
|
||||
- The Reset button in the bottom middle resets the Trip counter. (Total of all steps taken.)
|
||||
- The progress circle shows the percentage of your daily goal completed.
|
||||
|
||||
### Heartrate
|
||||
![Heartrate UI](/doc/gettingStarted/AppsScreenshots/Heartrate.png)
|
||||
- Press Start to start measuring your heartrate.
|
||||
- It may take a bit to get the first measurement.
|
||||
|
||||
### Music
|
||||
![Music UI](/doc/gettingStarted/AppsScreenshots/Music.png)
|
||||
- This app shows currently playing music.
|
||||
- Please note that this app is not very useful without a device connected.
|
||||
- Press the button in the center to play/pause, and the buttons on the left and right to go to the previous and next tracks, respectively.
|
||||
- Swipe up to get to volume controls.
|
||||
|
||||
### InfiniPaint
|
||||
![InfiniPaint UI](/doc/gettingStarted/AppsScreenshots/Paint.png)
|
||||
- This app does not allow you to swipe from the top to exit, use the side button instead.
|
||||
- Draw on the screen to add lines.
|
||||
- Hold down in one spot to change paint colors.
|
||||
|
||||
### Paddle
|
||||
![Paddle UI](/doc/gettingStarted/AppsScreenshots/Pong.png)
|
||||
- This app does not allow you to swipe from the top to exit, use the side button instead.
|
||||
- Drag your finger to move the paddle.
|
||||
- Goal: Don't let the ball go off the left side of the screen.
|
||||
|
||||
### 2
|
||||
![2 UI](/doc/gettingStarted/AppsScreenshots/2048.png)
|
||||
- This app does not allow you to swipe from the top to exit, use the side button instead.
|
||||
- Play a game of 2048.
|
||||
- Swipe up, down, left, or right tomove the tiles.
|
||||
- When two tiles with the same number run into each other, they will add together.
|
||||
- Goal: Don't let the screen fill up with tiles, and get to the 2048 tile to win.
|
||||
|
||||
### InfiniDice
|
||||
![InfiniDice UI](/doc/gettingStarted/AppsScreenshots/Dice.png)
|
||||
- Ajust the count to change the number of dice.
|
||||
- Ajust the sides to change the number of sides.
|
||||
- Press the button at the bottom to roll.
|
||||
- The result will be on the right side of the screen.
|
||||
|
||||
### Metronome
|
||||
![Metronome UI](/doc/gettingStarted/AppsScreenshots/Metronome.png)
|
||||
- Ajust the BPM with the circular slider.
|
||||
- A bug currently makes it always snap to 98 BPM.
|
||||
- Use the button in the bottom left to start the metronome.
|
||||
|
||||
### Maps
|
||||
![Maps UI](/doc/gettingStarted/AppsScreenshots/Maps.png)
|
||||
- This app shows info from a navigation app.
|
||||
- Please note that this app is not very useful without a device connected.
|
||||
|
||||
### Weather
|
||||
![Weather UI](/doc/gettingStarted/AppsScreenshots/Weather.png)
|
||||
- This app shows weather info.
|
||||
- Please note that this app is not very useful without a device connected.
|
BIN
doc/gettingStarted/AppsScreenshots/2048.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Dice.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Heartrate.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Maps.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Metronome.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Music.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Paint.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Pong.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
doc/gettingStarted/AppsScreenshots/Weather.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
doc/gettingStarted/AppsScreenshots/alarm.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
doc/gettingStarted/AppsScreenshots/steps.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
doc/gettingStarted/AppsScreenshots/stopwatch.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
doc/gettingStarted/AppsScreenshots/timer.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
31
doc/gettingStarted/Watchfaces.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Watchfaces
|
||||
|
||||
InfiniTime has 6 apps on the `main` branch at the time of writing.
|
||||
|
||||
## List of apps
|
||||
- Digital
|
||||
- Analog
|
||||
- PineTimeStyle
|
||||
- Terminal
|
||||
- Infinineat
|
||||
- Casio G7710
|
||||
|
||||
### Digital
|
||||
![Digital face](/doc/gettingStarted/Watchfaces/Digital.png)
|
||||
|
||||
### Analog
|
||||
![Analog face](/doc/gettingStarted/Watchfaces/Analog.png)
|
||||
|
||||
### PineTimeStyle
|
||||
![PineTimeStyle face](/doc/gettingStarted/Watchfaces/PineTimeStyle.png)
|
||||
- You can long-press on the display to change colors, step style, and weather.
|
||||
|
||||
### Terminal
|
||||
![Terminal face](/doc/gettingStarted/Watchfaces/Terminal.png)
|
||||
|
||||
### Infinineat
|
||||
![Infinineat face](/doc/gettingStarted/Watchfaces/Infinineat.png)
|
||||
- You can long-press on the display to change colors.
|
||||
|
||||
### Casio G7710
|
||||
![Casio G7710 face](/doc/gettingStarted/Watchfaces/CasioG7710.png)
|
BIN
doc/gettingStarted/Watchfaces/Analog.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
doc/gettingStarted/Watchfaces/CasioG7710.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
doc/gettingStarted/Watchfaces/Digital.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
doc/gettingStarted/Watchfaces/Infinineat.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
doc/gettingStarted/Watchfaces/PineTimeStyle.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
doc/gettingStarted/Watchfaces/Terminal.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 256 KiB |
|
@ -14,7 +14,7 @@ You can sync the time using companion apps.
|
|||
|
||||
- Gadgetbridge automatically synchronizes the time when you connect it to your watch. More information on Gadgetbridge [here](/doc/gettingStarted/ota-gadgetbridge.md)
|
||||
- [Sync the time with NRFConnect](/doc/gettingStarted/time-nrfconnect.md)
|
||||
- Sync the time with your browser https://hubmartin.github.io/WebBLEWatch/
|
||||
- [Sync the time with your browser](https://hubmartin.github.io/WebBLEWatch/)
|
||||
|
||||
You can also set the time in the settings without a companion app. (version >1.7.0)
|
||||
|
||||
|
@ -46,7 +46,7 @@ On the bottom right, you can see how many steps you have taken today.
|
|||
![Settings](ui/settings.jpg)
|
||||
|
||||
- Swipe **up** to display the application menus. Apps (stopwatch, music, step, games,...) can be started from this menu.
|
||||
- Swipe **down** to display the notification panel. Notification sent by your companion app will be displayed here.
|
||||
- Swipe **down** to display the notification panel. Notifications sent by your companion app will be displayed here.
|
||||
- Swipe **right** to display the Quick Actions menu. This menu allows you to
|
||||
- Set the brightness of the display
|
||||
- Start the **flashlight** app
|
||||
|
|
|
@ -1,29 +1,35 @@
|
|||
# Connecting to Gadgetbridge
|
||||
|
||||
Launch Gadgetbridge and tap on the **"+"** button on the bottom right to add a new device:
|
||||
Launch Gadgetbridge and tap on the menu button in the top left:
|
||||
|
||||
![Gadgetbridge 0](gadgetbridge0.jpg)
|
||||
|
||||
Wait for the scan to complete, your PineTime should be detected:
|
||||
Press the "Connect new device" button:
|
||||
|
||||
![Gadgetbridge 1](gadgetbridge1.jpg)
|
||||
|
||||
Your PineTime should appear on the list. Tap on it.
|
||||
|
||||
Tap on it. Gadgdetbridge will pair and connect to your device:
|
||||
|
||||
![Gadgetbridge 2](gadgetbridge2.jpg)
|
||||
|
||||
# Updating with Gadgetbridge
|
||||
|
||||
Now that Gadgetbridge is connected to your PineTime, use a file browser application and find the DFU file (`pinetime-mcuboot-app-dfu-x.x.x.zip`) you downloaded previously. Tap on it and open it using the Gadgetbridge application/firmware installer:
|
||||
Now that Gadgetbridge is connected to your PineTime, press the three dots on the device card:
|
||||
|
||||
![Gadgetbridge 3](gadgetbridge3.jpg)
|
||||
|
||||
Read the warning carefully and tap **Install**:
|
||||
Now press the "File Installer" button:
|
||||
|
||||
![Gadgetbridge 4](gadgetbridge4.jpg)
|
||||
|
||||
Wait for the transfer to finish. Your PineTime should reset and reboot with the new version of InfiniTime!
|
||||
|
||||
Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and Firmware option and click **validate**. Otherwise after reboot the previous firmware will be used.
|
||||
Select the firmware you downloaded (`pinetime-mcuboot-app-dfu-x.x.x.zip`) from the [Releases tab](https://github.com/InfiniTimeOrg/InfiniTime/releases/latest):
|
||||
|
||||
![Gadgetbridge 5](gadgetbridge5.jpg)
|
||||
|
||||
Wait for the transfer to finish. There will be a progress bar on both the watch and the phone. Your PineTime should reboot with the new version of InfiniTime!
|
||||
|
||||
Don't forget to **validate** your firmware. In the InfiniTime go to the settings (swipe right, select gear icon) and scroll to the Firmware option and click **validate**. Otherwise, after reboot the previous firmware will be used.
|
||||
|
||||
![Validate](validate.png)
|
||||
|
|
|
@ -6,7 +6,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f
|
|||
|
||||
You can check the InfiniTime version by first swiping right on the watch face to open quick settings, tapping the cogwheel to open settings, swipe up until you find an entry named "About" and tap on it.
|
||||
|
||||
![InfiniTime 1.0 version](version-1.0.jpg)
|
||||
![InfiniTime 1.14 version](version.png)
|
||||
|
||||
PineTimes shipped after June 2021 will ship with the latest version of [the bootloader](https://github.com/JF002/pinetime-mcuboot-bootloader/releases/tag/1.0.0) and [recovery firmware](https://github.com/InfiniTimeOrg/InfiniTime/releases/tag/0.14.1)
|
||||
|
||||
|
@ -49,7 +49,7 @@ Since those resources are not part of the firmware, they need to be flashed and
|
|||
Resources are packaged into a single .zip file named `infinitime-resources-x.y.z.zip` (where `x`, `y` and `z` are the version numbers of InfiniTime).
|
||||
You can use the companion app of your choice to flash the resources.
|
||||
|
||||
**Note : at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!*
|
||||
**Note: at the time of writing this page, [Amazfish](https://github.com/piggz/harbour-amazfish) and [ITD](https://gitea.arsenm.dev/Arsen6331/itd) have already integrated this functionality. Other companion apps will hopefully implement it soon!*
|
||||
|
||||
## Amazfish
|
||||
Use the `Download file` functionality of Amazfish.
|
||||
|
|
BIN
doc/gettingStarted/validate.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
doc/gettingStarted/version.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
doc/logo/watchface_collage.png
Normal file
After Width: | Height: | Size: 846 KiB |
|
@ -37,6 +37,13 @@ RUN apt-get update -qq \
|
|||
libpangocairo-1.0-0 \
|
||||
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
|
||||
|
||||
# Add the necessary apt-gets for the devcontainer
|
||||
RUN apt-get update -qq \
|
||||
&& apt-get install -y \
|
||||
clang-format-14 \
|
||||
clang-tidy \
|
||||
libncurses5
|
||||
|
||||
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting
|
||||
|
||||
RUN pip3 install adafruit-nrfutil
|
||||
|
@ -55,5 +62,11 @@ RUN bash -c "source /opt/build.sh; GetNrfSdk;"
|
|||
# McuBoot
|
||||
RUN bash -c "source /opt/build.sh; GetMcuBoot;"
|
||||
|
||||
# Add the infinitime user for connecting devcontainer
|
||||
RUN adduser infinitime
|
||||
|
||||
# Configure Git to accept the /sources directory as safe
|
||||
RUN git config --global --add safe.directory /sources
|
||||
|
||||
ENV SOURCES_DIR /sources
|
||||
CMD ["/opt/build.sh"]
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
#!/bin/bash
|
||||
if clang-format --version | grep -q 'version 11\.'; then
|
||||
CLANG_FORMAT_EXECUTABLE="clang-format"
|
||||
else
|
||||
CLANG_FORMAT_EXECUTABLE="clang-format-11"
|
||||
#!/bin/sh
|
||||
|
||||
name="clang-format"
|
||||
|
||||
if [ -z "$(command -v "git-$name")" ]; then
|
||||
name="$(basename -a $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'git-clang-format*') | sort | tail -n 1 | sed 's/^git-//')"
|
||||
fi
|
||||
|
||||
if ! command -v $CLANG_FORMAT_EXECUTABLE &> /dev/null
|
||||
then
|
||||
echo $CLANG_FORMAT_EXECUTABLE does not exist, make sure to install it
|
||||
minVersion="14.0.0"
|
||||
|
||||
for file in $(find $(echo "$PATH" | tr ':' ' ') -maxdepth 1 -type f -executable -name 'clang-format*'); do
|
||||
curBin="$file"
|
||||
curVersion="$("$curBin" --version | cut -d ' ' -f 3)"
|
||||
|
||||
if [ "$(printf '%s\n' "$curVersion" "$version" "$minVersion" | sort -V | tail -n 1)" = "$curVersion" ]; then
|
||||
bin="$curBin"
|
||||
version="$curVersion"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$name" ] || [ -z "$bin" ]; then
|
||||
echo "Could not find a suitable clang-format installation. Install clang-format that includes the git-clang-format script, with at least version $minVersion"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for FILE in $(git diff --cached --name-only)
|
||||
do
|
||||
if [[ "$FILE" =~ src/[A-Za-z0-9\ \-]+*\.(c|h|cpp|cc)$ ]]; then
|
||||
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
|
||||
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
|
||||
git add -- $FILE
|
||||
elif [[ "$FILE" =~ src/(components|displayapp|drivers|heartratetask|logging|systemtask)/.*\.(c|h|cpp|cc)$ ]]; then
|
||||
echo Autoformatting $FILE with $CLANG_FORMAT_EXECUTABLE
|
||||
$CLANG_FORMAT_EXECUTABLE -style=file -i -- $FILE
|
||||
git add -- $FILE
|
||||
fi
|
||||
args="--binary $bin -q --extensions cpp,h --style file --staged -- :!src/FreeRTOS :!src/libs"
|
||||
|
||||
changedFiles="$(git "$name" --diffstat $args)"
|
||||
git "$name" $args
|
||||
|
||||
echo "$changedFiles" | head -n -1 | cut -d ' ' -f 2 | while read -r file; do
|
||||
git add -- "$file"
|
||||
done
|
||||
|
|
50
shell.nix
Normal file
|
@ -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";
|
||||
}
|
|
@ -379,6 +379,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/Navigation.cpp
|
||||
displayapp/screens/Metronome.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
displayapp/screens/Weather.cpp
|
||||
displayapp/screens/FirmwareValidation.cpp
|
||||
displayapp/screens/ApplicationList.cpp
|
||||
displayapp/screens/Notifications.cpp
|
||||
|
@ -390,10 +391,12 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/BatteryInfo.cpp
|
||||
displayapp/screens/Steps.cpp
|
||||
displayapp/screens/Timer.cpp
|
||||
displayapp/screens/Dice.cpp
|
||||
displayapp/screens/PassKey.cpp
|
||||
displayapp/screens/Error.cpp
|
||||
displayapp/screens/Alarm.cpp
|
||||
displayapp/screens/Styles.cpp
|
||||
displayapp/screens/WeatherSymbols.cpp
|
||||
displayapp/Colors.cpp
|
||||
displayapp/widgets/Counter.cpp
|
||||
displayapp/widgets/PageIndicator.cpp
|
||||
|
@ -475,6 +478,7 @@ list(APPEND SOURCE_FILES
|
|||
|
||||
systemtask/SystemTask.cpp
|
||||
systemtask/SystemMonitor.cpp
|
||||
systemtask/WakeLock.cpp
|
||||
drivers/TwiMaster.cpp
|
||||
|
||||
heartratetask/HeartRateTask.cpp
|
||||
|
@ -539,8 +543,8 @@ list(APPEND RECOVERY_SOURCE_FILES
|
|||
|
||||
systemtask/SystemTask.cpp
|
||||
systemtask/SystemMonitor.cpp
|
||||
systemtask/WakeLock.cpp
|
||||
drivers/TwiMaster.cpp
|
||||
components/gfx/Gfx.cpp
|
||||
components/rle/RleDecoder.cpp
|
||||
components/heartrate/HeartRateController.cpp
|
||||
heartratetask/HeartRateTask.cpp
|
||||
|
@ -570,7 +574,6 @@ list(APPEND RECOVERYLOADER_SOURCE_FILES
|
|||
|
||||
components/rle/RleDecoder.cpp
|
||||
|
||||
components/gfx/Gfx.cpp
|
||||
drivers/St7789.cpp
|
||||
components/brightness/BrightnessController.cpp
|
||||
|
||||
|
@ -608,6 +611,7 @@ set(INCLUDE_FILES
|
|||
displayapp/screens/Metronome.h
|
||||
displayapp/screens/Motion.h
|
||||
displayapp/screens/Timer.h
|
||||
displayapp/screens/Dice.h
|
||||
displayapp/screens/Alarm.h
|
||||
displayapp/Colors.h
|
||||
displayapp/widgets/Counter.h
|
||||
|
@ -658,6 +662,7 @@ set(INCLUDE_FILES
|
|||
displayapp/InfiniTimeTheme.h
|
||||
systemtask/SystemTask.h
|
||||
systemtask/SystemMonitor.h
|
||||
systemtask/WakeLock.h
|
||||
displayapp/screens/Symbols.h
|
||||
drivers/TwiMaster.h
|
||||
heartratetask/HeartRateTask.h
|
||||
|
@ -858,7 +863,6 @@ target_compile_options(nrf-sdk PRIVATE
|
|||
$<$<CONFIG:RELEASE>: ${RELEASE_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:CXX>: ${CXX_FLAGS}>
|
||||
$<$<COMPILE_LANGUAGE:ASM>: ${ASM_FLAGS}>
|
||||
-O3
|
||||
)
|
||||
|
||||
# NimBLE
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#define configUSE_TIME_SLICING 0
|
||||
#define configUSE_NEWLIB_REENTRANT 0
|
||||
#define configENABLE_BACKWARD_COMPATIBILITY 1
|
||||
#define configUSE_TASK_NOTIFICATIONS 0
|
||||
|
||||
/* Hook function related definitions. */
|
||||
#define configUSE_IDLE_HOOK 0
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
#include "systemtask/SystemTask.h"
|
||||
#include "task.h"
|
||||
#include <chrono>
|
||||
#include <libraries/log/nrf_log.h>
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
AlarmController::AlarmController(Controllers::DateTime& dateTimeController) : dateTimeController {dateTimeController} {
|
||||
AlarmController::AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs)
|
||||
: dateTimeController {dateTimeController}, fs {fs} {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -36,11 +38,28 @@ namespace {
|
|||
void AlarmController::Init(System::SystemTask* systemTask) {
|
||||
this->systemTask = systemTask;
|
||||
alarmTimer = xTimerCreate("Alarm", 1, pdFALSE, this, SetOffAlarm);
|
||||
LoadSettingsFromFile();
|
||||
if (alarm.isEnabled) {
|
||||
NRF_LOG_INFO("[AlarmController] Loaded alarm was enabled, scheduling");
|
||||
ScheduleAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmController::SaveAlarm() {
|
||||
// verify if it is necessary to save
|
||||
if (alarmChanged) {
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
alarmChanged = false;
|
||||
}
|
||||
|
||||
void AlarmController::SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin) {
|
||||
hours = alarmHr;
|
||||
minutes = alarmMin;
|
||||
if (alarm.hours == alarmHr && alarm.minutes == alarmMin) {
|
||||
return;
|
||||
}
|
||||
alarm.hours = alarmHr;
|
||||
alarm.minutes = alarmMin;
|
||||
alarmChanged = true;
|
||||
}
|
||||
|
||||
void AlarmController::ScheduleAlarm() {
|
||||
|
@ -53,18 +72,19 @@ void AlarmController::ScheduleAlarm() {
|
|||
tm* tmAlarmTime = std::localtime(&ttAlarmTime);
|
||||
|
||||
// If the time being set has already passed today,the alarm should be set for tomorrow
|
||||
if (hours < dateTimeController.Hours() || (hours == dateTimeController.Hours() && minutes <= dateTimeController.Minutes())) {
|
||||
if (alarm.hours < dateTimeController.Hours() ||
|
||||
(alarm.hours == dateTimeController.Hours() && alarm.minutes <= dateTimeController.Minutes())) {
|
||||
tmAlarmTime->tm_mday += 1;
|
||||
// tm_wday doesn't update automatically
|
||||
tmAlarmTime->tm_wday = (tmAlarmTime->tm_wday + 1) % 7;
|
||||
}
|
||||
|
||||
tmAlarmTime->tm_hour = hours;
|
||||
tmAlarmTime->tm_min = minutes;
|
||||
tmAlarmTime->tm_hour = alarm.hours;
|
||||
tmAlarmTime->tm_min = alarm.minutes;
|
||||
tmAlarmTime->tm_sec = 0;
|
||||
|
||||
// if alarm is in weekday-only mode, make sure it shifts to the next weekday
|
||||
if (recurrence == RecurType::Weekdays) {
|
||||
if (alarm.recurrence == RecurType::Weekdays) {
|
||||
if (tmAlarmTime->tm_wday == 0) { // Sunday, shift 1 day
|
||||
tmAlarmTime->tm_mday += 1;
|
||||
} else if (tmAlarmTime->tm_wday == 6) { // Saturday, shift 2 days
|
||||
|
@ -79,7 +99,10 @@ void AlarmController::ScheduleAlarm() {
|
|||
xTimerChangePeriod(alarmTimer, secondsToAlarm * configTICK_RATE_HZ, 0);
|
||||
xTimerStart(alarmTimer, 0);
|
||||
|
||||
state = AlarmState::Set;
|
||||
if (!alarm.isEnabled) {
|
||||
alarm.isEnabled = true;
|
||||
alarmChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AlarmController::SecondsToAlarm() const {
|
||||
|
@ -88,20 +111,72 @@ uint32_t AlarmController::SecondsToAlarm() const {
|
|||
|
||||
void AlarmController::DisableAlarm() {
|
||||
xTimerStop(alarmTimer, 0);
|
||||
state = AlarmState::Not_Set;
|
||||
isAlerting = false;
|
||||
if (alarm.isEnabled) {
|
||||
alarm.isEnabled = false;
|
||||
alarmChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmController::SetOffAlarmNow() {
|
||||
state = AlarmState::Alerting;
|
||||
isAlerting = true;
|
||||
systemTask->PushMessage(System::Messages::SetOffAlarm);
|
||||
}
|
||||
|
||||
void AlarmController::StopAlerting() {
|
||||
// Alarm state is off unless this is a recurring alarm
|
||||
if (recurrence == RecurType::None) {
|
||||
state = AlarmState::Not_Set;
|
||||
isAlerting = false;
|
||||
// Disable alarm unless it is recurring
|
||||
if (alarm.recurrence == RecurType::None) {
|
||||
alarm.isEnabled = false;
|
||||
alarmChanged = true;
|
||||
} else {
|
||||
// set next instance
|
||||
ScheduleAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmController::SetRecurrence(RecurType recurrence) {
|
||||
if (alarm.recurrence != recurrence) {
|
||||
alarm.recurrence = recurrence;
|
||||
alarmChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmController::LoadSettingsFromFile() {
|
||||
lfs_file_t alarmFile;
|
||||
AlarmSettings alarmBuffer;
|
||||
|
||||
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_RDONLY) != LFS_ERR_OK) {
|
||||
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.FileRead(&alarmFile, reinterpret_cast<uint8_t*>(&alarmBuffer), sizeof(alarmBuffer));
|
||||
fs.FileClose(&alarmFile);
|
||||
if (alarmBuffer.version != alarmFormatVersion) {
|
||||
NRF_LOG_WARNING("[AlarmController] Loaded alarm settings has version %u instead of %u, discarding",
|
||||
alarmBuffer.version,
|
||||
alarmFormatVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
alarm = alarmBuffer;
|
||||
NRF_LOG_INFO("[AlarmController] Loaded alarm settings from file");
|
||||
}
|
||||
|
||||
void AlarmController::SaveSettingsToFile() const {
|
||||
lfs_dir systemDir;
|
||||
if (fs.DirOpen("/.system", &systemDir) != LFS_ERR_OK) {
|
||||
fs.DirCreate("/.system");
|
||||
}
|
||||
fs.DirClose(&systemDir);
|
||||
lfs_file_t alarmFile;
|
||||
if (fs.FileOpen(&alarmFile, "/.system/alarm.dat", LFS_O_WRONLY | LFS_O_CREAT) != LFS_ERR_OK) {
|
||||
NRF_LOG_WARNING("[AlarmController] Failed to open alarm data file for saving");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.FileWrite(&alarmFile, reinterpret_cast<const uint8_t*>(&alarm), sizeof(alarm));
|
||||
fs.FileClose(&alarmFile);
|
||||
NRF_LOG_INFO("[AlarmController] Saved alarm settings with format version %u to file", alarm.version);
|
||||
}
|
||||
|
|
|
@ -30,47 +30,65 @@ namespace Pinetime {
|
|||
namespace Controllers {
|
||||
class AlarmController {
|
||||
public:
|
||||
AlarmController(Controllers::DateTime& dateTimeController);
|
||||
AlarmController(Controllers::DateTime& dateTimeController, Controllers::FS& fs);
|
||||
|
||||
void Init(System::SystemTask* systemTask);
|
||||
void SaveAlarm();
|
||||
void SetAlarmTime(uint8_t alarmHr, uint8_t alarmMin);
|
||||
void ScheduleAlarm();
|
||||
void DisableAlarm();
|
||||
void SetOffAlarmNow();
|
||||
uint32_t SecondsToAlarm() const;
|
||||
void StopAlerting();
|
||||
enum class AlarmState { Not_Set, Set, Alerting };
|
||||
enum class RecurType { None, Daily, Weekdays };
|
||||
|
||||
uint8_t Hours() const {
|
||||
return hours;
|
||||
return alarm.hours;
|
||||
}
|
||||
|
||||
uint8_t Minutes() const {
|
||||
return minutes;
|
||||
return alarm.minutes;
|
||||
}
|
||||
|
||||
AlarmState State() const {
|
||||
return state;
|
||||
bool IsAlerting() const {
|
||||
return isAlerting;
|
||||
}
|
||||
|
||||
bool IsEnabled() const {
|
||||
return alarm.isEnabled;
|
||||
}
|
||||
|
||||
RecurType Recurrence() const {
|
||||
return recurrence;
|
||||
return alarm.recurrence;
|
||||
}
|
||||
|
||||
void SetRecurrence(RecurType recurType) {
|
||||
recurrence = recurType;
|
||||
}
|
||||
void SetRecurrence(RecurType recurrence);
|
||||
|
||||
private:
|
||||
Controllers::DateTime& dateTimeController;
|
||||
System::SystemTask* systemTask = nullptr;
|
||||
TimerHandle_t alarmTimer;
|
||||
// Versions 255 is reserved for now, so the version field can be made
|
||||
// bigger, should it ever be needed.
|
||||
static constexpr uint8_t alarmFormatVersion = 1;
|
||||
|
||||
struct AlarmSettings {
|
||||
uint8_t version = alarmFormatVersion;
|
||||
uint8_t hours = 7;
|
||||
uint8_t minutes = 0;
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
|
||||
AlarmState state = AlarmState::Not_Set;
|
||||
RecurType recurrence = RecurType::None;
|
||||
bool isEnabled = false;
|
||||
};
|
||||
|
||||
bool isAlerting = false;
|
||||
bool alarmChanged = false;
|
||||
|
||||
Controllers::DateTime& dateTimeController;
|
||||
Controllers::FS& fs;
|
||||
System::SystemTask* systemTask = nullptr;
|
||||
TimerHandle_t alarmTimer;
|
||||
AlarmSettings alarm;
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> alarmTime;
|
||||
|
||||
void LoadSettingsFromFile();
|
||||
void SaveSettingsToFile() const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,9 +124,11 @@ int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf* om) {
|
|||
bootloaderSize,
|
||||
applicationSize);
|
||||
|
||||
// wait until SystemTask has finished waking up all devices
|
||||
while (systemTask.IsSleeping()) {
|
||||
vTaskDelay(50); // 50ms
|
||||
// Wait until SystemTask has disabled sleeping
|
||||
// This isn't quite correct, as we don't actually know
|
||||
// if BleFirmwareUpdateStarted has been received yet
|
||||
while (!systemTask.IsSleepDisabled()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
}
|
||||
|
||||
dfuImage.Erase();
|
||||
|
@ -357,6 +359,8 @@ void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t exp
|
|||
this->totalSize = totalSize;
|
||||
this->expectedCrc = expectedCrc;
|
||||
this->ready = true;
|
||||
totalWriteIndex = 0;
|
||||
bufferWriteIndex = 0;
|
||||
}
|
||||
|
||||
void DfuService::DfuImage::Append(uint8_t* data, size_t size) {
|
||||
|
|
|
@ -77,6 +77,10 @@ namespace Pinetime {
|
|||
uint16_t ComputeCrc(uint8_t const* p_data, uint32_t size, uint16_t const* p_crc);
|
||||
};
|
||||
|
||||
static constexpr ble_uuid128_t serviceUuid {
|
||||
.u {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
|
||||
|
||||
private:
|
||||
Pinetime::System::SystemTask& systemTask;
|
||||
Pinetime::Controllers::Ble& bleController;
|
||||
|
@ -90,10 +94,6 @@ namespace Pinetime {
|
|||
|
||||
uint16_t revision {0x0008};
|
||||
|
||||
static constexpr ble_uuid128_t serviceUuid {
|
||||
.u {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
|
||||
|
||||
static constexpr ble_uuid128_t packetCharacteristicUuid {
|
||||
.u {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}};
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
#define max
|
||||
#include <host/ble_gap.h>
|
||||
#include <atomic>
|
||||
#undef max
|
||||
#undef min
|
||||
#include <atomic>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
@ -21,14 +21,14 @@ namespace Pinetime {
|
|||
void SubscribeNotification(uint16_t attributeHandle);
|
||||
void UnsubscribeNotification(uint16_t attributeHandle);
|
||||
|
||||
static constexpr uint16_t heartRateServiceId {0x180D};
|
||||
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
|
||||
|
||||
private:
|
||||
NimbleController& nimble;
|
||||
Controllers::HeartRateController& heartRateController;
|
||||
static constexpr uint16_t heartRateServiceId {0x180D};
|
||||
static constexpr uint16_t heartRateMeasurementId {0x2A37};
|
||||
|
||||
static constexpr ble_uuid16_t heartRateServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateServiceId};
|
||||
|
||||
static constexpr ble_uuid16_t heartRateMeasurementUuid {.u {.type = BLE_UUID_TYPE_16}, .value = heartRateMeasurementId};
|
||||
|
||||
struct ble_gatt_chr_def characteristicDefinition[2];
|
||||
|
|
|
@ -120,3 +120,7 @@ void MotionService::UnsubscribeNotification(uint16_t attributeHandle) {
|
|||
else if (attributeHandle == motionValuesHandle)
|
||||
motionValuesNoficationEnabled = false;
|
||||
}
|
||||
|
||||
bool MotionService::IsMotionNotificationSubscribed() const {
|
||||
return motionValuesNoficationEnabled;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Pinetime {
|
|||
|
||||
void SubscribeNotification(uint16_t attributeHandle);
|
||||
void UnsubscribeNotification(uint16_t attributeHandle);
|
||||
bool IsMotionNotificationSubscribed() const;
|
||||
|
||||
private:
|
||||
NimbleController& nimble;
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "components/ble/MusicService.h"
|
||||
#include "components/ble/NimbleController.h"
|
||||
#include <cstring>
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
|
||||
namespace {
|
||||
// 0000yyxx-78fc-48fe-8e23-433b3a1942d0
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <host/ble_uuid.h>
|
||||
#undef max
|
||||
#undef min
|
||||
#include <FreeRTOS.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
|
|
|
@ -158,7 +158,10 @@ void NimbleController::StartAdvertising() {
|
|||
}
|
||||
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
fields.uuids128 = &dfuServiceUuid;
|
||||
fields.uuids16 = &HeartRateService::heartRateServiceUuid;
|
||||
fields.num_uuids16 = 1;
|
||||
fields.uuids16_is_complete = 1;
|
||||
fields.uuids128 = &DfuService::serviceUuid;
|
||||
fields.num_uuids128 = 1;
|
||||
fields.uuids128_is_complete = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
|
@ -451,9 +454,15 @@ void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
|
|||
/* Wakeup Spi and SpiNorFlash before accessing the file system
|
||||
* This should be fixed in the FS driver
|
||||
*/
|
||||
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
|
||||
systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
|
||||
vTaskDelay(10);
|
||||
|
||||
// This isn't quite correct
|
||||
// SystemTask could receive EnableSleeping right after passing this check
|
||||
// We need some guarantee that the SystemTask has processed the above message
|
||||
// before we can continue
|
||||
while (!systemTask.IsSleepDisabled()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5));
|
||||
}
|
||||
|
||||
lfs_file_t file_p;
|
||||
|
||||
|
|
|
@ -112,10 +112,6 @@ namespace Pinetime {
|
|||
uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||
uint8_t fastAdvCount = 0;
|
||||
uint8_t bondId[16] = {0};
|
||||
|
||||
ble_uuid128_t dfuServiceUuid {
|
||||
.u {.type = BLE_UUID_TYPE_128},
|
||||
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
|
||||
};
|
||||
|
||||
static NimbleController* nptr;
|
||||
|
|
|
@ -42,9 +42,9 @@ namespace {
|
|||
std::memcpy(cityName.data(), &dataBuffer[16], 32);
|
||||
cityName[32] = '\0';
|
||||
return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]),
|
||||
ToInt16(&dataBuffer[10]),
|
||||
ToInt16(&dataBuffer[12]),
|
||||
ToInt16(&dataBuffer[14]),
|
||||
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])),
|
||||
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])),
|
||||
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])),
|
||||
SimpleWeatherService::Icons {dataBuffer[16 + 32]},
|
||||
std::move(cityName));
|
||||
}
|
||||
|
@ -52,12 +52,12 @@ namespace {
|
|||
SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) {
|
||||
auto timestamp = static_cast<uint64_t>(ToUInt64(&dataBuffer[2]));
|
||||
|
||||
std::array<SimpleWeatherService::Forecast::Day, SimpleWeatherService::MaxNbForecastDays> days;
|
||||
std::array<std::optional<SimpleWeatherService::Forecast::Day>, SimpleWeatherService::MaxNbForecastDays> days;
|
||||
const uint8_t nbDaysInBuffer = dataBuffer[10];
|
||||
const uint8_t nbDays = std::min(SimpleWeatherService::MaxNbForecastDays, nbDaysInBuffer);
|
||||
for (int i = 0; i < nbDays; i++) {
|
||||
days[i] = SimpleWeatherService::Forecast::Day {ToInt16(&dataBuffer[11 + (i * 5)]),
|
||||
ToInt16(&dataBuffer[13 + (i * 5)]),
|
||||
days[i] = SimpleWeatherService::Forecast::Day {SimpleWeatherService::Temperature(ToInt16(&dataBuffer[11 + (i * 5)])),
|
||||
SimpleWeatherService::Temperature(ToInt16(&dataBuffer[13 + (i * 5)])),
|
||||
SimpleWeatherService::Icons {dataBuffer[15 + (i * 5)]}};
|
||||
}
|
||||
return SimpleWeatherService::Forecast {timestamp, nbDays, days};
|
||||
|
@ -80,7 +80,7 @@ int WeatherCallback(uint16_t /*connHandle*/, uint16_t /*attrHandle*/, struct ble
|
|||
return static_cast<Pinetime::Controllers::SimpleWeatherService*>(arg)->OnCommand(ctxt);
|
||||
}
|
||||
|
||||
SimpleWeatherService::SimpleWeatherService(const DateTime& dateTimeController) : dateTimeController(dateTimeController) {
|
||||
SimpleWeatherService::SimpleWeatherService(DateTime& dateTimeController) : dateTimeController(dateTimeController) {
|
||||
}
|
||||
|
||||
void SimpleWeatherService::Init() {
|
||||
|
@ -98,9 +98,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
|||
currentWeather = CreateCurrentWeather(dataBuffer);
|
||||
NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s",
|
||||
currentWeather->timestamp,
|
||||
currentWeather->temperature,
|
||||
currentWeather->minTemperature,
|
||||
currentWeather->maxTemperature,
|
||||
currentWeather->temperature.PreciseCelsius(),
|
||||
currentWeather->minTemperature.PreciseCelsius(),
|
||||
currentWeather->maxTemperature.PreciseCelsius(),
|
||||
currentWeather->iconId,
|
||||
currentWeather->location.data());
|
||||
}
|
||||
|
@ -112,9 +112,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) {
|
|||
for (int i = 0; i < 5; i++) {
|
||||
NRF_LOG_INFO("\t[%d] Min: %d - Max : %d - Icon : %d",
|
||||
i,
|
||||
forecast->days[i].minTemperature,
|
||||
forecast->days[i].maxTemperature,
|
||||
forecast->days[i].iconId);
|
||||
forecast->days[i]->minTemperature.PreciseCelsius(),
|
||||
forecast->days[i]->maxTemperature.PreciseCelsius(),
|
||||
forecast->days[i]->iconId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -158,3 +158,16 @@ bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService
|
|||
this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature &&
|
||||
std::strcmp(this->location.data(), other.location.data()) == 0;
|
||||
}
|
||||
|
||||
bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const {
|
||||
return this->iconId == other.iconId && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature;
|
||||
}
|
||||
|
||||
bool SimpleWeatherService::Forecast::operator==(const SimpleWeatherService::Forecast& other) const {
|
||||
for (int i = 0; i < this->nbDays; i++) {
|
||||
if (this->days[i] != other.days[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this->timestamp == other.timestamp && this->nbDays == other.nbDays;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#define min // workaround: nimble's min/max macros conflict with libstdc++
|
||||
|
@ -40,7 +40,7 @@ namespace Pinetime {
|
|||
|
||||
class SimpleWeatherService {
|
||||
public:
|
||||
explicit SimpleWeatherService(const DateTime& dateTimeController);
|
||||
explicit SimpleWeatherService(DateTime& dateTimeController);
|
||||
|
||||
void Init();
|
||||
|
||||
|
@ -61,13 +61,42 @@ namespace Pinetime {
|
|||
Unknown = 255
|
||||
};
|
||||
|
||||
class Temperature {
|
||||
public:
|
||||
explicit Temperature(int16_t raw) : raw {raw} {
|
||||
}
|
||||
|
||||
[[nodiscard]] int16_t PreciseCelsius() const {
|
||||
return raw;
|
||||
}
|
||||
|
||||
[[nodiscard]] int16_t PreciseFahrenheit() const {
|
||||
return raw * 9 / 5 + 3200;
|
||||
}
|
||||
|
||||
[[nodiscard]] int16_t Celsius() const {
|
||||
return (PreciseCelsius() + 50) / 100;
|
||||
}
|
||||
|
||||
[[nodiscard]] int16_t Fahrenheit() const {
|
||||
return (PreciseFahrenheit() + 50) / 100;
|
||||
}
|
||||
|
||||
bool operator==(const Temperature& other) const {
|
||||
return raw == other.raw;
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t raw;
|
||||
};
|
||||
|
||||
using Location = std::array<char, 33>; // 32 char + \0 (end of string)
|
||||
|
||||
struct CurrentWeather {
|
||||
CurrentWeather(uint64_t timestamp,
|
||||
int16_t temperature,
|
||||
int16_t minTemperature,
|
||||
int16_t maxTemperature,
|
||||
Temperature temperature,
|
||||
Temperature minTemperature,
|
||||
Temperature maxTemperature,
|
||||
Icons iconId,
|
||||
Location&& location)
|
||||
: timestamp {timestamp},
|
||||
|
@ -79,9 +108,9 @@ namespace Pinetime {
|
|||
}
|
||||
|
||||
uint64_t timestamp;
|
||||
int16_t temperature;
|
||||
int16_t minTemperature;
|
||||
int16_t maxTemperature;
|
||||
Temperature temperature;
|
||||
Temperature minTemperature;
|
||||
Temperature maxTemperature;
|
||||
Icons iconId;
|
||||
Location location;
|
||||
|
||||
|
@ -93,21 +122,21 @@ namespace Pinetime {
|
|||
uint8_t nbDays;
|
||||
|
||||
struct Day {
|
||||
int16_t minTemperature;
|
||||
int16_t maxTemperature;
|
||||
Temperature minTemperature;
|
||||
Temperature maxTemperature;
|
||||
Icons iconId;
|
||||
|
||||
bool operator==(const Day& other) const;
|
||||
};
|
||||
|
||||
std::array<Day, MaxNbForecastDays> days;
|
||||
std::array<std::optional<Day>, MaxNbForecastDays> days;
|
||||
|
||||
bool operator==(const Forecast& other) const;
|
||||
};
|
||||
|
||||
std::optional<CurrentWeather> Current() const;
|
||||
std::optional<Forecast> GetForecast() const;
|
||||
|
||||
static int16_t CelsiusToFahrenheit(int16_t celsius) {
|
||||
return celsius * 9 / 5 + 3200;
|
||||
}
|
||||
|
||||
private:
|
||||
// 00050000-78fc-48fe-8e23-433b3a1942d0
|
||||
static constexpr ble_uuid128_t BaseUuid() {
|
||||
|
@ -136,7 +165,7 @@ namespace Pinetime {
|
|||
|
||||
uint16_t eventHandle {};
|
||||
|
||||
const Pinetime::Controllers::DateTime& dateTimeController;
|
||||
Pinetime::Controllers::DateTime& dateTimeController;
|
||||
|
||||
std::optional<CurrentWeather> currentWeather;
|
||||
std::optional<Forecast> forecast;
|
||||
|
|
|
@ -2,38 +2,138 @@
|
|||
#include <hal/nrf_gpio.h>
|
||||
#include "displayapp/screens/Symbols.h"
|
||||
#include "drivers/PinMap.h"
|
||||
#include <libraries/delay/nrf_delay.h>
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
// reinterpret_cast is not constexpr so this is the best we can do
|
||||
static NRF_RTC_Type* const RTC = reinterpret_cast<NRF_RTC_Type*>(NRF_RTC2_BASE);
|
||||
}
|
||||
|
||||
void BrightnessController::Init() {
|
||||
nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
|
||||
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
|
||||
|
||||
static_assert(timerFrequency == 32768, "Change the prescaler below");
|
||||
RTC->PRESCALER = 0;
|
||||
// CC1 switches the backlight on (pin transitions from high to low) and resets the counter to 0
|
||||
RTC->CC[1] = timerPeriod;
|
||||
// Enable compare events for CC0,CC1
|
||||
RTC->EVTEN = 0b0000'0000'0000'0011'0000'0000'0000'0000;
|
||||
// Disable all interrupts
|
||||
RTC->INTENCLR = 0b0000'0000'0000'1111'0000'0000'0000'0011;
|
||||
Set(level);
|
||||
}
|
||||
|
||||
void BrightnessController::ApplyBrightness(uint16_t rawBrightness) {
|
||||
// The classic off, low, medium, high brightnesses are at {0, timerPeriod, timerPeriod*2, timerPeriod*3}
|
||||
// These brightness levels do not use PWM: they only set/clear the corresponding pins
|
||||
// Any brightness level between the above levels is achieved with efficient RTC based PWM on the next pin up
|
||||
// E.g 2.5*timerPeriod corresponds to medium brightness with 50% PWM on the high pin
|
||||
// Note: Raw brightness does not necessarily correspond to a linear perceived brightness
|
||||
|
||||
uint8_t pin;
|
||||
if (rawBrightness > 2 * timerPeriod) {
|
||||
rawBrightness -= 2 * timerPeriod;
|
||||
pin = PinMap::LcdBacklightHigh;
|
||||
} else if (rawBrightness > timerPeriod) {
|
||||
rawBrightness -= timerPeriod;
|
||||
pin = PinMap::LcdBacklightMedium;
|
||||
} else {
|
||||
pin = PinMap::LcdBacklightLow;
|
||||
}
|
||||
if (rawBrightness == timerPeriod || rawBrightness == 0) {
|
||||
if (lastPin != UNSET) {
|
||||
RTC->TASKS_STOP = 1;
|
||||
nrf_delay_us(rtcStopTime);
|
||||
nrf_ppi_channel_disable(ppiBacklightOff);
|
||||
nrf_ppi_channel_disable(ppiBacklightOn);
|
||||
nrfx_gpiote_out_uninit(lastPin);
|
||||
nrf_gpio_cfg_output(lastPin);
|
||||
}
|
||||
lastPin = UNSET;
|
||||
if (rawBrightness == 0) {
|
||||
nrf_gpio_pin_set(pin);
|
||||
} else {
|
||||
nrf_gpio_pin_clear(pin);
|
||||
}
|
||||
} else {
|
||||
// If the pin on which we are doing PWM is changing
|
||||
// Disable old PWM channel (if exists) and set up new one
|
||||
if (lastPin != pin) {
|
||||
if (lastPin != UNSET) {
|
||||
RTC->TASKS_STOP = 1;
|
||||
nrf_delay_us(rtcStopTime);
|
||||
nrf_ppi_channel_disable(ppiBacklightOff);
|
||||
nrf_ppi_channel_disable(ppiBacklightOn);
|
||||
nrfx_gpiote_out_uninit(lastPin);
|
||||
nrf_gpio_cfg_output(lastPin);
|
||||
}
|
||||
nrfx_gpiote_out_config_t gpioteCfg = {.action = NRF_GPIOTE_POLARITY_TOGGLE,
|
||||
.init_state = NRF_GPIOTE_INITIAL_VALUE_LOW,
|
||||
.task_pin = true};
|
||||
APP_ERROR_CHECK(nrfx_gpiote_out_init(pin, &gpioteCfg));
|
||||
nrfx_gpiote_out_task_enable(pin);
|
||||
nrf_ppi_channel_endpoint_setup(ppiBacklightOff,
|
||||
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[0]),
|
||||
nrfx_gpiote_out_task_addr_get(pin));
|
||||
nrf_ppi_channel_endpoint_setup(ppiBacklightOn,
|
||||
reinterpret_cast<uint32_t>(&RTC->EVENTS_COMPARE[1]),
|
||||
nrfx_gpiote_out_task_addr_get(pin));
|
||||
nrf_ppi_fork_endpoint_setup(ppiBacklightOn, reinterpret_cast<uint32_t>(&RTC->TASKS_CLEAR));
|
||||
nrf_ppi_channel_enable(ppiBacklightOff);
|
||||
nrf_ppi_channel_enable(ppiBacklightOn);
|
||||
} else {
|
||||
// If the pin used for PWM isn't changing, we only need to set the pin state to the initial value (low)
|
||||
RTC->TASKS_STOP = 1;
|
||||
nrf_delay_us(rtcStopTime);
|
||||
// Due to errata 20,179 and the intricacies of RTC timing, keep it simple: override the pin state
|
||||
nrfx_gpiote_out_task_force(pin, false);
|
||||
}
|
||||
// CC0 switches the backlight off (pin transitions from low to high)
|
||||
RTC->CC[0] = rawBrightness;
|
||||
RTC->TASKS_CLEAR = 1;
|
||||
RTC->TASKS_START = 1;
|
||||
lastPin = pin;
|
||||
}
|
||||
switch (pin) {
|
||||
case PinMap::LcdBacklightHigh:
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
||||
break;
|
||||
case PinMap::LcdBacklightMedium:
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
||||
break;
|
||||
case PinMap::LcdBacklightLow:
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
||||
}
|
||||
}
|
||||
|
||||
void BrightnessController::Set(BrightnessController::Levels level) {
|
||||
this->level = level;
|
||||
switch (level) {
|
||||
default:
|
||||
case Levels::High:
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
|
||||
ApplyBrightness(3 * timerPeriod);
|
||||
break;
|
||||
case Levels::Medium:
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
||||
ApplyBrightness(2 * timerPeriod);
|
||||
break;
|
||||
case Levels::Low:
|
||||
nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
||||
ApplyBrightness(timerPeriod);
|
||||
break;
|
||||
case Levels::AlwaysOn:
|
||||
ApplyBrightness(timerPeriod / 10);
|
||||
break;
|
||||
case Levels::Off:
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightLow);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
|
||||
nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
|
||||
ApplyBrightness(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
#include "nrf_ppi.h"
|
||||
#include "nrfx_gpiote.h"
|
||||
|
||||
namespace Pinetime {
|
||||
namespace Controllers {
|
||||
class BrightnessController {
|
||||
public:
|
||||
enum class Levels { Off, Low, Medium, High };
|
||||
enum class Levels { Off, AlwaysOn, Low, Medium, High };
|
||||
void Init();
|
||||
|
||||
void Set(Levels level);
|
||||
|
@ -20,6 +23,25 @@ namespace Pinetime {
|
|||
|
||||
private:
|
||||
Levels level = Levels::High;
|
||||
static constexpr uint8_t UNSET = UINT8_MAX;
|
||||
uint8_t lastPin = UNSET;
|
||||
// Maximum time (μs) it takes for the RTC to fully stop
|
||||
static constexpr uint8_t rtcStopTime = 46;
|
||||
// Frequency of timer used for PWM (Hz)
|
||||
static constexpr uint16_t timerFrequency = 32768;
|
||||
// Backlight PWM frequency (Hz)
|
||||
static constexpr uint16_t pwmFreq = 1000;
|
||||
// Wraparound point in timer ticks
|
||||
// Defines the number of brightness levels between each pin
|
||||
static constexpr uint16_t timerPeriod = timerFrequency / pwmFreq;
|
||||
// Warning: nimble reserves some PPIs
|
||||
// https://github.com/InfiniTimeOrg/InfiniTime/blob/034d83fe6baf1ab3875a34f8cee387e24410a824/src/libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c#L53
|
||||
// SpiMaster uses PPI 0 for an erratum workaround
|
||||
// Channel 1, 2 should be free to use
|
||||
static constexpr nrf_ppi_channel_t ppiBacklightOn = NRF_PPI_CHANNEL1;
|
||||
static constexpr nrf_ppi_channel_t ppiBacklightOff = NRF_PPI_CHANNEL2;
|
||||
|
||||
void ApplyBrightness(uint16_t val);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,42 @@
|
|||
#include "components/datetime/DateTimeController.h"
|
||||
#include <libraries/log/nrf_log.h>
|
||||
#include <systemtask/SystemTask.h>
|
||||
#include <hal/nrf_rtc.h>
|
||||
#include "nrf_assert.h"
|
||||
|
||||
using namespace Pinetime::Controllers;
|
||||
|
||||
namespace {
|
||||
char const* DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
char const* DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
char const* MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
|
||||
char const* MonthsStringLow[] = {"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
constexpr const char* const DaysStringShort[] = {"--", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
constexpr const char* const DaysStringShortLow[] = {"--", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
constexpr const char* const MonthsString[] = {"--", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
|
||||
constexpr const char* const MonthsStringLow[] =
|
||||
{"--", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
constexpr int compileTimeAtoi(const char* str) {
|
||||
int result = 0;
|
||||
while (*str >= '0' && *str <= '9') {
|
||||
result = result * 10 + *str - '0';
|
||||
str++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime::DateTime(Controllers::Settings& settingsController) : settingsController {settingsController} {
|
||||
mutex = xSemaphoreCreateMutex();
|
||||
ASSERT(mutex != nullptr);
|
||||
xSemaphoreGive(mutex);
|
||||
|
||||
// __DATE__ is a string of the format "MMM DD YYYY", so an offset of 7 gives the start of the year
|
||||
SetTime(compileTimeAtoi(&__DATE__[7]), 1, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
void DateTime::SetCurrentTime(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> t) {
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
this->currentDateTime = t;
|
||||
UpdateTime(previousSystickCounter); // Update internal state without updating the time
|
||||
UpdateTime(previousSystickCounter, true); // Update internal state without updating the time
|
||||
xSemaphoreGive(mutex);
|
||||
}
|
||||
|
||||
void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
|
@ -29,15 +49,19 @@ void DateTime::SetTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour,
|
|||
/* .tm_year = */ year - 1900,
|
||||
};
|
||||
|
||||
tm.tm_isdst = -1; // Use DST value from local time zone
|
||||
currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
|
||||
|
||||
NRF_LOG_INFO("%d %d %d ", day, month, year);
|
||||
NRF_LOG_INFO("%d %d %d ", hour, minute, second);
|
||||
|
||||
UpdateTime(previousSystickCounter);
|
||||
tm.tm_isdst = -1; // Use DST value from local time zone
|
||||
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
currentDateTime = std::chrono::system_clock::from_time_t(std::mktime(&tm));
|
||||
UpdateTime(previousSystickCounter, true);
|
||||
xSemaphoreGive(mutex);
|
||||
|
||||
if (systemTask != nullptr) {
|
||||
systemTask->PushMessage(System::Messages::OnNewTime);
|
||||
}
|
||||
}
|
||||
|
||||
void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
|
||||
|
@ -45,25 +69,34 @@ void DateTime::SetTimeZone(int8_t timezone, int8_t dst) {
|
|||
dstOffset = dst;
|
||||
}
|
||||
|
||||
void DateTime::UpdateTime(uint32_t systickCounter) {
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> DateTime::CurrentDateTime() {
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
UpdateTime(nrf_rtc_counter_get(portNRF_RTC_REG), false);
|
||||
xSemaphoreGive(mutex);
|
||||
return currentDateTime;
|
||||
}
|
||||
|
||||
void DateTime::UpdateTime(uint32_t systickCounter, bool forceUpdate) {
|
||||
// Handle systick counter overflow
|
||||
uint32_t systickDelta = 0;
|
||||
if (systickCounter < previousSystickCounter) {
|
||||
systickDelta = 0xffffff - previousSystickCounter;
|
||||
systickDelta = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - previousSystickCounter;
|
||||
systickDelta += systickCounter + 1;
|
||||
} else {
|
||||
systickDelta = systickCounter - previousSystickCounter;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1000 ms = 1024 ticks
|
||||
*/
|
||||
auto correctedDelta = systickDelta / 1024;
|
||||
auto rest = systickDelta % 1024;
|
||||
auto correctedDelta = systickDelta / configTICK_RATE_HZ;
|
||||
// If a second hasn't passed, there is nothing to do
|
||||
// If the time has been changed, set forceUpdate to trigger internal state updates
|
||||
if (correctedDelta == 0 && !forceUpdate) {
|
||||
return;
|
||||
}
|
||||
auto rest = systickDelta % configTICK_RATE_HZ;
|
||||
if (systickCounter >= rest) {
|
||||
previousSystickCounter = systickCounter - rest;
|
||||
} else {
|
||||
previousSystickCounter = 0xffffff - (rest - systickCounter);
|
||||
previousSystickCounter = static_cast<uint32_t>(portNRF_RTC_MAXTICKS) - (rest - systickCounter - 1);
|
||||
}
|
||||
|
||||
currentDateTime += std::chrono::seconds(correctedDelta);
|
||||
|
@ -115,8 +148,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
|
|||
return MonthsStringLow[static_cast<uint8_t>(month)];
|
||||
}
|
||||
|
||||
const char* DateTime::DayOfWeekShortToStringLow() const {
|
||||
return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
|
||||
const char* DateTime::DayOfWeekShortToStringLow(Days day) {
|
||||
return DaysStringShortLow[static_cast<uint8_t>(day)];
|
||||
}
|
||||
|
||||
void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <ctime>
|
||||
#include <string>
|
||||
#include "components/settings/Settings.h"
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
|
||||
namespace Pinetime {
|
||||
namespace System {
|
||||
|
@ -39,14 +41,12 @@ namespace Pinetime {
|
|||
*
|
||||
* used to update difference between utc and local time (see UtcOffset())
|
||||
*
|
||||
* parameters are in quarters of an our. Following the BLE CTS specification,
|
||||
* parameters are in quarters of an hour. Following the BLE CTS specification,
|
||||
* timezone is expected to be constant over DST which will be reported in
|
||||
* dst field.
|
||||
*/
|
||||
void SetTimeZone(int8_t timezone, int8_t dst);
|
||||
|
||||
void UpdateTime(uint32_t systickCounter);
|
||||
|
||||
uint16_t Year() const {
|
||||
return 1900 + localTime.tm_year;
|
||||
}
|
||||
|
@ -122,14 +122,12 @@ namespace Pinetime {
|
|||
const char* MonthShortToString() const;
|
||||
const char* DayOfWeekShortToString() const;
|
||||
static const char* MonthShortToStringLow(Months month);
|
||||
const char* DayOfWeekShortToStringLow() const;
|
||||
static const char* DayOfWeekShortToStringLow(Days day);
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
|
||||
return currentDateTime;
|
||||
}
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime();
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() const {
|
||||
return currentDateTime - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60);
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> UTCDateTime() {
|
||||
return CurrentDateTime() - std::chrono::seconds((tzOffset + dstOffset) * 15 * 60);
|
||||
}
|
||||
|
||||
std::chrono::seconds Uptime() const {
|
||||
|
@ -141,10 +139,14 @@ namespace Pinetime {
|
|||
std::string FormattedTime();
|
||||
|
||||
private:
|
||||
void UpdateTime(uint32_t systickCounter, bool forceUpdate);
|
||||
|
||||
std::tm localTime;
|
||||
int8_t tzOffset = 0;
|
||||
int8_t dstOffset = 0;
|
||||
|
||||
SemaphoreHandle_t mutex = nullptr;
|
||||
|
||||
uint32_t previousSystickCounter = 0;
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> currentDateTime;
|
||||
std::chrono::seconds uptime {0};
|
||||
|
|
41
src/components/datetime/TODO.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Refactoring needed
|
||||
|
||||
## Context
|
||||
|
||||
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) highlighted some
|
||||
limitations in the design of DateTimeController: the granularity of the time returned by `DateTime::CurrentDateTime()`
|
||||
is limited by the frequency at which SystemTask calls `DateTime::UpdateTime()`, which is currently set to 100ms.
|
||||
|
||||
@mark9064 provided more details
|
||||
in [this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041#issuecomment-2048528967).
|
||||
|
||||
The [PR #2041 - Continuous time updates](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041) provided some changes
|
||||
to `DateTime` controller that improves the granularity of the time returned by `DateTime::CurrentDateTime()`.
|
||||
|
||||
However, the review showed that `DateTime` cannot be `const` anymore, even when it's only used to get the current time,
|
||||
since `DateTime::CurrentDateTime()` changes the internal state of the instance.
|
||||
|
||||
We tried to identify alternative implementation that would have maintained the "const correctness" but we eventually
|
||||
figured that this would lead to a re-design of `DateTime` which was out of scope of the initial PR (Continuous time
|
||||
updates and always on display).
|
||||
|
||||
So we decided to merge this PR #2041 and agree to fix/improve `DateTime` later on.
|
||||
|
||||
## What needs to be done?
|
||||
|
||||
Improve/redesign `DateTime` so that it
|
||||
|
||||
* provides a very granular (ideally down to the millisecond) date and time via `CurrentDateTime()`.
|
||||
* can be declared/passed as `const` when it's only used to **get** the time.
|
||||
* limits the use of mutex as much as possible (an ideal implementation would not use any mutex, but this might not be
|
||||
possible).
|
||||
* improves the design of `DateTime::Seconds()`, `DateTime::Minutes()`, `DateTime::Hours()`, etc as
|
||||
explained [in this comment](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054#pullrequestreview-2037033105).
|
||||
|
||||
Once this redesign is implemented, all instances/references to `DateTime` should be reviewed and updated to use `const`
|
||||
where appropriate.
|
||||
|
||||
Please check the following PR to get more context about this redesign:
|
||||
|
||||
* [#2041 - Continuous time updates by @mark9064](https://github.com/InfiniTimeOrg/InfiniTime/pull/2041)
|
||||
* [#2054 - Continuous time update - Alternative implementation to #2041 by @JF002](https://github.com/InfiniTimeOrg/InfiniTime/pull/2054)
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -142,7 +142,7 @@ Ppg::Ppg() {
|
|||
spectrum.fill(0.0f);
|
||||
}
|
||||
|
||||
int8_t Ppg::Preprocess(uint32_t hrs, uint32_t als) {
|
||||
int8_t Ppg::Preprocess(uint16_t hrs, uint16_t als) {
|
||||
if (dataIndex < dataLength) {
|
||||
dataHRS[dataIndex++] = hrs;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Pinetime {
|
|||
class Ppg {
|
||||
public:
|
||||
Ppg();
|
||||
int8_t Preprocess(uint32_t hrs, uint32_t als);
|
||||
int8_t Preprocess(uint16_t hrs, uint16_t als);
|
||||
int HeartRate();
|
||||
void Reset(bool resetDaqBuffer);
|
||||
static constexpr int deltaTms = 100;
|
||||
|
|
|
@ -40,15 +40,15 @@ void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps)
|
|||
service->OnNewStepCountValue(nbSteps);
|
||||
}
|
||||
|
||||
if (service != nullptr && (this->x != x || yHistory[0] != y || zHistory[0] != z)) {
|
||||
if (service != nullptr && (xHistory[0] != x || yHistory[0] != y || zHistory[0] != z)) {
|
||||
service->OnNewMotionValues(x, y, z);
|
||||
}
|
||||
|
||||
lastTime = time;
|
||||
time = xTaskGetTickCount();
|
||||
|
||||
lastX = this->x;
|
||||
this->x = x;
|
||||
xHistory++;
|
||||
xHistory[0] = x;
|
||||
yHistory++;
|
||||
yHistory[0] = y;
|
||||
zHistory++;
|
||||
|
@ -67,20 +67,26 @@ MotionController::AccelStats MotionController::GetAccelStats() const {
|
|||
AccelStats stats;
|
||||
|
||||
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
|
||||
stats.xMean += xHistory[histSize - i];
|
||||
stats.yMean += yHistory[histSize - i];
|
||||
stats.zMean += zHistory[histSize - i];
|
||||
stats.prevXMean += xHistory[1 + i];
|
||||
stats.prevYMean += yHistory[1 + i];
|
||||
stats.prevZMean += zHistory[1 + i];
|
||||
}
|
||||
stats.xMean /= AccelStats::numHistory;
|
||||
stats.yMean /= AccelStats::numHistory;
|
||||
stats.zMean /= AccelStats::numHistory;
|
||||
stats.prevXMean /= AccelStats::numHistory;
|
||||
stats.prevYMean /= AccelStats::numHistory;
|
||||
stats.prevZMean /= AccelStats::numHistory;
|
||||
|
||||
for (uint8_t i = 0; i < AccelStats::numHistory; i++) {
|
||||
stats.xVariance += (xHistory[histSize - i] - stats.xMean) * (xHistory[histSize - i] - stats.xMean);
|
||||
stats.yVariance += (yHistory[histSize - i] - stats.yMean) * (yHistory[histSize - i] - stats.yMean);
|
||||
stats.zVariance += (zHistory[histSize - i] - stats.zMean) * (zHistory[histSize - i] - stats.zMean);
|
||||
}
|
||||
stats.xVariance /= AccelStats::numHistory;
|
||||
stats.yVariance /= AccelStats::numHistory;
|
||||
stats.zVariance /= AccelStats::numHistory;
|
||||
|
||||
|
@ -93,7 +99,7 @@ bool MotionController::ShouldRaiseWake() const {
|
|||
constexpr int16_t yThresh = -64;
|
||||
constexpr int16_t rollDegreesThresh = -45;
|
||||
|
||||
if (x < -xThresh || x > xThresh) {
|
||||
if (std::abs(stats.xMean) > xThresh) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -107,8 +113,9 @@ bool MotionController::ShouldRaiseWake() const {
|
|||
|
||||
bool MotionController::ShouldShakeWake(uint16_t thresh) {
|
||||
/* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
|
||||
int32_t speed =
|
||||
std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 + (x - lastX) / 4) * 100 / (time - lastTime);
|
||||
int32_t speed = std::abs(zHistory[0] - zHistory[histSize - 1] + (yHistory[0] - yHistory[histSize - 1]) / 2 +
|
||||
(xHistory[0] - xHistory[histSize - 1]) / 4) *
|
||||
100 / (time - lastTime);
|
||||
// (.2 * speed) + ((1 - .2) * accumulatedSpeed);
|
||||
accumulatedSpeed = speed / 5 + accumulatedSpeed * 4 / 5;
|
||||
|
||||
|
@ -116,6 +123,11 @@ bool MotionController::ShouldShakeWake(uint16_t thresh) {
|
|||
}
|
||||
|
||||
bool MotionController::ShouldLowerSleep() const {
|
||||
if ((stats.xMean > 887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) > 30) ||
|
||||
(stats.xMean < -887 && DegreesRolled(stats.xMean, stats.zMean, stats.prevXMean, stats.prevZMean) < -30)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stats.yMean < 724 || DegreesRolled(stats.yMean, stats.zMean, stats.prevYMean, stats.prevZMean) < 30) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Pinetime {
|
|||
void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
|
||||
|
||||
int16_t X() const {
|
||||
return x;
|
||||
return xHistory[0];
|
||||
}
|
||||
|
||||
int16_t Y() const {
|
||||
|
@ -62,6 +62,10 @@ namespace Pinetime {
|
|||
this->service = service;
|
||||
}
|
||||
|
||||
Pinetime::Controllers::MotionService* GetService() const {
|
||||
return service;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t nbSteps = 0;
|
||||
uint32_t currentTripSteps = 0;
|
||||
|
@ -72,11 +76,14 @@ namespace Pinetime {
|
|||
struct AccelStats {
|
||||
static constexpr uint8_t numHistory = 2;
|
||||
|
||||
int16_t xMean = 0;
|
||||
int16_t yMean = 0;
|
||||
int16_t zMean = 0;
|
||||
int16_t prevXMean = 0;
|
||||
int16_t prevYMean = 0;
|
||||
int16_t prevZMean = 0;
|
||||
|
||||
uint32_t xVariance = 0;
|
||||
uint32_t yVariance = 0;
|
||||
uint32_t zVariance = 0;
|
||||
};
|
||||
|
@ -85,9 +92,8 @@ namespace Pinetime {
|
|||
|
||||
AccelStats stats = {};
|
||||
|
||||
int16_t lastX = 0;
|
||||
int16_t x = 0;
|
||||
static constexpr uint8_t histSize = 8;
|
||||
Utility::CircularBuffer<int16_t, histSize> xHistory = {};
|
||||
Utility::CircularBuffer<int16_t, histSize> yHistory = {};
|
||||
Utility::CircularBuffer<int16_t, histSize> zHistory = {};
|
||||
int32_t accumulatedSpeed = 0;
|
||||
|
|
|
@ -214,6 +214,21 @@ namespace Pinetime {
|
|||
return settings.screenTimeOut;
|
||||
};
|
||||
|
||||
bool GetAlwaysOnDisplay() const {
|
||||
return settings.alwaysOnDisplay && GetNotificationStatus() != Notification::Sleep;
|
||||
};
|
||||
|
||||
void SetAlwaysOnDisplaySetting(bool state) {
|
||||
if (state != settings.alwaysOnDisplay) {
|
||||
settingsChanged = true;
|
||||
}
|
||||
settings.alwaysOnDisplay = state;
|
||||
}
|
||||
|
||||
bool GetAlwaysOnDisplaySetting() const {
|
||||
return settings.alwaysOnDisplay;
|
||||
}
|
||||
|
||||
void SetShakeThreshold(uint16_t thresh) {
|
||||
if (settings.shakeWakeThreshold != thresh) {
|
||||
settings.shakeWakeThreshold = thresh;
|
||||
|
@ -286,13 +301,15 @@ namespace Pinetime {
|
|||
private:
|
||||
Pinetime::Controllers::FS& fs;
|
||||
|
||||
static constexpr uint32_t settingsVersion = 0x0007;
|
||||
static constexpr uint32_t settingsVersion = 0x0008;
|
||||
|
||||
struct SettingsData {
|
||||
uint32_t version = settingsVersion;
|
||||
uint32_t stepsGoal = 10000;
|
||||
uint32_t screenTimeOut = 15000;
|
||||
|
||||
bool alwaysOnDisplay = false;
|
||||
|
||||
ClockType clockType = ClockType::H24;
|
||||
WeatherFormat weatherFormat = WeatherFormat::Metric;
|
||||
Notification notificationStatus = Notification::On;
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include "displayapp/screens/FlashLight.h"
|
||||
#include "displayapp/screens/BatteryInfo.h"
|
||||
#include "displayapp/screens/Steps.h"
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/Weather.h"
|
||||
#include "displayapp/screens/PassKey.h"
|
||||
#include "displayapp/screens/Error.h"
|
||||
|
||||
|
@ -79,7 +81,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
|||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem)
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash)
|
||||
: lcd {lcd},
|
||||
touchPanel {touchPanel},
|
||||
batteryController {batteryController},
|
||||
|
@ -95,6 +98,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
|||
brightnessController {brightnessController},
|
||||
touchHandler {touchHandler},
|
||||
filesystem {filesystem},
|
||||
spiNorFlash {spiNorFlash},
|
||||
lvgl {lcd, filesystem},
|
||||
timer(this, TimerCallback),
|
||||
controllers {batteryController,
|
||||
|
@ -123,6 +127,7 @@ void DisplayApp::Start(System::BootErrors error) {
|
|||
bootError = error;
|
||||
|
||||
lvgl.Init();
|
||||
motorController.Init();
|
||||
|
||||
if (error == System::BootErrors::TouchController) {
|
||||
LoadNewScreen(Apps::Error, DisplayApp::FullRefreshDirections::None);
|
||||
|
@ -140,9 +145,6 @@ void DisplayApp::Process(void* instance) {
|
|||
NRF_LOG_INFO("displayapp task started!");
|
||||
app->InitHw();
|
||||
|
||||
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
||||
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
|
||||
|
||||
while (true) {
|
||||
app->Refresh();
|
||||
}
|
||||
|
@ -151,10 +153,47 @@ void DisplayApp::Process(void* instance) {
|
|||
void DisplayApp::InitHw() {
|
||||
brightnessController.Init();
|
||||
ApplyBrightness();
|
||||
motorController.Init();
|
||||
lcd.Init();
|
||||
}
|
||||
|
||||
TickType_t DisplayApp::CalculateSleepTime() {
|
||||
// Calculates how many system ticks DisplayApp should sleep before rendering the next AOD frame
|
||||
// Next frame time is frame count * refresh period (ms) * tick rate
|
||||
|
||||
auto RoundedDiv = [](uint32_t a, uint32_t b) {
|
||||
return ((a + (b / 2)) / b);
|
||||
};
|
||||
// RoundedDiv overflows when numerator + (denominator floordiv 2) > uint32 max
|
||||
// in this case around 9 hours (=overflow frame count / always on refresh period)
|
||||
constexpr TickType_t overflowFrameCount = (UINT32_MAX - (1000 / 16)) / ((configTICK_RATE_HZ / 8) * alwaysOnRefreshPeriod);
|
||||
|
||||
TickType_t ticksElapsed = xTaskGetTickCount() - alwaysOnStartTime;
|
||||
// Divide both the numerator and denominator by 8 (=GCD(1000,1024))
|
||||
// to increase the number of ticks (frames) before the overflow tick is reached
|
||||
TickType_t targetRenderTick = RoundedDiv((configTICK_RATE_HZ / 8) * alwaysOnFrameCount * alwaysOnRefreshPeriod, 1000 / 8);
|
||||
|
||||
// Assumptions
|
||||
|
||||
// Tick rate is multiple of 8
|
||||
// Needed for division trick above
|
||||
static_assert(configTICK_RATE_HZ % 8 == 0);
|
||||
|
||||
// Frame count must always wraparound more often than the system tick count does
|
||||
// Always on overflow time (ms) < system tick overflow time (ms)
|
||||
// Using 64bit ints here to avoid overflow
|
||||
static_assert((uint64_t) overflowFrameCount * (uint64_t) alwaysOnRefreshPeriod < (uint64_t) UINT32_MAX * 1000ULL / configTICK_RATE_HZ);
|
||||
|
||||
if (alwaysOnFrameCount == overflowFrameCount) {
|
||||
alwaysOnFrameCount = 0;
|
||||
alwaysOnStartTime = xTaskGetTickCount();
|
||||
}
|
||||
if (targetRenderTick > ticksElapsed) {
|
||||
return targetRenderTick - ticksElapsed;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayApp::Refresh() {
|
||||
auto LoadPreviousScreen = [this]() {
|
||||
FullRefreshDirections returnDirection;
|
||||
|
@ -178,21 +217,6 @@ void DisplayApp::Refresh() {
|
|||
LoadScreen(returnAppStack.Pop(), returnDirection);
|
||||
};
|
||||
|
||||
auto DimScreen = [this]() {
|
||||
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
isDimmed = true;
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
}
|
||||
};
|
||||
|
||||
auto RestoreBrightness = [this]() {
|
||||
if (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
isDimmed = false;
|
||||
lv_disp_trig_activity(nullptr);
|
||||
ApplyBrightness();
|
||||
}
|
||||
};
|
||||
|
||||
auto IsPastDimTime = [this]() -> bool {
|
||||
return lv_disp_get_inactive_time(nullptr) >= pdMS_TO_TICKS(settingsController.GetScreenTimeOut() - 2000);
|
||||
};
|
||||
|
@ -206,6 +230,27 @@ void DisplayApp::Refresh() {
|
|||
case States::Idle:
|
||||
queueTimeout = portMAX_DELAY;
|
||||
break;
|
||||
case States::AOD:
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadPreviousScreen();
|
||||
}
|
||||
// Check we've slept long enough
|
||||
// Might not be true if the loop received an event
|
||||
// If not true, then wait that amount of time
|
||||
queueTimeout = CalculateSleepTime();
|
||||
if (queueTimeout == 0) {
|
||||
// Only advance the tick count when LVGL is done
|
||||
// Otherwise keep running the task handler while it still has things to draw
|
||||
// Note: under high graphics load, LVGL will always have more work to do
|
||||
if (lv_task_handler() > 0) {
|
||||
// Drop frames that we've missed if drawing/event handling took way longer than expected
|
||||
while (queueTimeout == 0) {
|
||||
alwaysOnFrameCount += 1;
|
||||
queueTimeout = CalculateSleepTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case States::Running:
|
||||
if (!currentScreen->IsRunning()) {
|
||||
LoadPreviousScreen();
|
||||
|
@ -214,14 +259,27 @@ void DisplayApp::Refresh() {
|
|||
|
||||
if (!systemTask->IsSleepDisabled() && IsPastDimTime()) {
|
||||
if (!isDimmed) {
|
||||
DimScreen();
|
||||
isDimmed = true;
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Low);
|
||||
}
|
||||
if (IsPastSleepTime()) {
|
||||
systemTask->PushMessage(System::Messages::GoToSleep);
|
||||
state = States::Idle;
|
||||
if (IsPastSleepTime() && uxQueueMessagesWaiting(msgQueue) == 0) {
|
||||
PushMessageToSystemTask(System::Messages::GoToSleep);
|
||||
// Can't set state to Idle here, something may send
|
||||
// DisableSleeping before this GoToSleep arrives
|
||||
// Instead we check we have no messages queued before sending GoToSleep
|
||||
// This works as the SystemTask is higher priority than DisplayApp
|
||||
// As soon as we send GoToSleep, SystemTask pre-empts DisplayApp
|
||||
// Whenever DisplayApp is running again, it is guaranteed that
|
||||
// SystemTask has handled the message
|
||||
// If it responded, we will have a GoToSleep waiting in the queue
|
||||
// By checking that there are no messages in the queue, we avoid
|
||||
// resending GoToSleep when we already have a response
|
||||
// SystemTask is resilient to duplicate messages, this is an
|
||||
// optimisation to reduce pressure on the message queues
|
||||
}
|
||||
} else if (isDimmed) {
|
||||
RestoreBrightness();
|
||||
isDimmed = false;
|
||||
ApplyBrightness();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -232,30 +290,73 @@ void DisplayApp::Refresh() {
|
|||
Messages msg;
|
||||
if (xQueueReceive(msgQueue, &msg, queueTimeout) == pdTRUE) {
|
||||
switch (msg) {
|
||||
case Messages::DimScreen:
|
||||
DimScreen();
|
||||
break;
|
||||
case Messages::RestoreBrightness:
|
||||
RestoreBrightness();
|
||||
break;
|
||||
case Messages::GoToSleep:
|
||||
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Off) {
|
||||
case Messages::GoToAOD:
|
||||
// Checking if SystemTask is sleeping is purely an optimisation.
|
||||
// If it's no longer sleeping since it sent GoToSleep, it has
|
||||
// cancelled the sleep and transitioned directly from
|
||||
// GoingToSleep->Running, so we are about to receive GoToRunning
|
||||
// and can ignore this message. If it wasn't ignored, DisplayApp
|
||||
// would go to sleep and then immediately re-wake
|
||||
if (state != States::Running || !systemTask->IsSleeping()) {
|
||||
break;
|
||||
}
|
||||
while (brightnessController.Level() != Controllers::BrightnessController::Levels::Low) {
|
||||
brightnessController.Lower();
|
||||
vTaskDelay(100);
|
||||
}
|
||||
// Turn brightness down (or set to AlwaysOn mode)
|
||||
if (msg == Messages::GoToAOD) {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::AlwaysOn);
|
||||
} else {
|
||||
brightnessController.Set(Controllers::BrightnessController::Levels::Off);
|
||||
}
|
||||
// Since the active screen is not really an app, go back to Clock.
|
||||
if (currentApp == Apps::Launcher || currentApp == Apps::Notifications || currentApp == Apps::QuickSettings ||
|
||||
currentApp == Apps::Settings) {
|
||||
LoadScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
// Wait for the clock app to load before moving on.
|
||||
while (!lv_task_handler()) {
|
||||
};
|
||||
}
|
||||
// Clear any ongoing touch pressed events
|
||||
// Without this LVGL gets stuck in the pressed state and will keep refreshing the
|
||||
// display activity timer causing the screen to never sleep after timeout
|
||||
lvgl.ClearTouchState();
|
||||
if (msg == Messages::GoToAOD) {
|
||||
lcd.LowPowerOn();
|
||||
// Record idle entry time
|
||||
alwaysOnFrameCount = 0;
|
||||
alwaysOnStartTime = xTaskGetTickCount();
|
||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskAOD);
|
||||
state = States::AOD;
|
||||
} else {
|
||||
lcd.Sleep();
|
||||
PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
|
||||
state = States::Idle;
|
||||
}
|
||||
break;
|
||||
case Messages::NotifyDeviceActivity:
|
||||
lv_disp_trig_activity(nullptr);
|
||||
break;
|
||||
case Messages::GoToRunning:
|
||||
// If SystemTask is sleeping, the GoToRunning message is old
|
||||
// and must be ignored. Otherwise DisplayApp will use SPI
|
||||
// that is powered down and cause bad behaviour
|
||||
if (state == States::Running || systemTask->IsSleeping()) {
|
||||
break;
|
||||
}
|
||||
if (state == States::AOD) {
|
||||
lcd.LowPowerOff();
|
||||
} else {
|
||||
lcd.Wakeup();
|
||||
}
|
||||
lv_disp_trig_activity(nullptr);
|
||||
ApplyBrightness();
|
||||
state = States::Running;
|
||||
break;
|
||||
case Messages::UpdateBleConnection:
|
||||
// clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected :
|
||||
// Screens::Clock::BleConnectionStates::NotConnected);
|
||||
// Only used for recovery firmware
|
||||
break;
|
||||
case Messages::NewNotification:
|
||||
LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down);
|
||||
|
@ -370,22 +471,17 @@ void DisplayApp::Refresh() {
|
|||
case Messages::BleRadioEnableToggle:
|
||||
PushMessageToSystemTask(System::Messages::BleRadioEnableToggle);
|
||||
break;
|
||||
case Messages::UpdateDateTime:
|
||||
// Added to remove warning
|
||||
// What should happen here?
|
||||
break;
|
||||
case Messages::Chime:
|
||||
LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
|
||||
motorController.RunForDuration(35);
|
||||
break;
|
||||
case Messages::OnChargingEvent:
|
||||
RestoreBrightness();
|
||||
motorController.RunForDuration(15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (touchHandler.IsTouching()) {
|
||||
if (state == States::Running && touchHandler.IsTouching()) {
|
||||
currentScreen->OnTouchEvent(touchHandler.GetX(), touchHandler.GetY());
|
||||
}
|
||||
|
||||
|
@ -444,6 +540,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
else {
|
||||
currentScreen.reset(userWatchFaces[0].create(controllers));
|
||||
}
|
||||
settingsController.SetAppMenu(0);
|
||||
} break;
|
||||
case Apps::Error:
|
||||
currentScreen = std::make_unique<Screens::Error>(bootError);
|
||||
|
@ -489,10 +586,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
|
||||
break;
|
||||
case Apps::SettingWatchFace: {
|
||||
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
|
||||
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> items;
|
||||
int i = 0;
|
||||
for (const auto& userWatchFace : userWatchFaces) {
|
||||
items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)};
|
||||
items[i++] =
|
||||
Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)};
|
||||
}
|
||||
currentScreen = std::make_unique<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
|
||||
} break;
|
||||
|
@ -506,7 +604,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
currentScreen = std::make_unique<Screens::SettingWakeUp>(settingsController);
|
||||
break;
|
||||
case Apps::SettingDisplay:
|
||||
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
|
||||
currentScreen = std::make_unique<Screens::SettingDisplay>(settingsController);
|
||||
break;
|
||||
case Apps::SettingSteps:
|
||||
currentScreen = std::make_unique<Screens::SettingSteps>(settingsController);
|
||||
|
@ -534,7 +632,8 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
|
|||
bleController,
|
||||
watchdog,
|
||||
motionController,
|
||||
touchPanel);
|
||||
touchPanel,
|
||||
spiNorFlash);
|
||||
break;
|
||||
case Apps::FlashLight:
|
||||
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
|
||||
|
@ -558,9 +657,7 @@ void DisplayApp::PushMessage(Messages msg) {
|
|||
if (in_isr()) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
} else {
|
||||
TickType_t timeout = portMAX_DELAY;
|
||||
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace Pinetime {
|
|||
namespace Applications {
|
||||
class DisplayApp {
|
||||
public:
|
||||
enum class States { Idle, Running };
|
||||
enum class States { Idle, Running, AOD };
|
||||
enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
|
||||
|
||||
DisplayApp(Drivers::St7789& lcd,
|
||||
|
@ -66,7 +66,8 @@ namespace Pinetime {
|
|||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
void Start(System::BootErrors error);
|
||||
void PushMessage(Display::Messages msg);
|
||||
|
||||
|
@ -96,6 +97,7 @@ namespace Pinetime {
|
|||
Pinetime::Controllers::BrightnessController& brightnessController;
|
||||
Pinetime::Controllers::TouchHandler& touchHandler;
|
||||
Pinetime::Controllers::FS& filesystem;
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
|
||||
|
||||
Pinetime::Controllers::FirmwareValidator validator;
|
||||
Pinetime::Components::LittleVgl lvgl;
|
||||
|
@ -135,6 +137,13 @@ namespace Pinetime {
|
|||
Utility::StaticStack<FullRefreshDirections, returnAppStackSize> appStackDirections;
|
||||
|
||||
bool isDimmed = false;
|
||||
|
||||
TickType_t CalculateSleepTime();
|
||||
TickType_t alwaysOnFrameCount;
|
||||
TickType_t alwaysOnStartTime;
|
||||
// If this is to be changed, make sure the actual always on refresh rate is changed
|
||||
// by configuring the LCD refresh timings
|
||||
static constexpr uint32_t alwaysOnRefreshPeriod = 500;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
|
|||
Pinetime::Controllers::AlarmController& /*alarmController*/,
|
||||
Pinetime::Controllers::BrightnessController& /*brightnessController*/,
|
||||
Pinetime::Controllers::TouchHandler& /*touchHandler*/,
|
||||
Pinetime::Controllers::FS& /*filesystem*/)
|
||||
Pinetime::Controllers::FS& /*filesystem*/,
|
||||
Pinetime::Drivers::SpiNorFlash& /*spiNorFlash*/)
|
||||
: lcd {lcd}, bleController {bleController} {
|
||||
}
|
||||
|
||||
|
@ -38,9 +39,6 @@ void DisplayApp::Process(void* instance) {
|
|||
auto* app = static_cast<DisplayApp*>(instance);
|
||||
NRF_LOG_INFO("displayapp task started!");
|
||||
|
||||
// Send a dummy notification to unlock the lvgl display driver for the first iteration
|
||||
xTaskNotifyGive(xTaskGetCurrentTaskHandle());
|
||||
|
||||
app->InitHw();
|
||||
while (true) {
|
||||
app->Refresh();
|
||||
|
@ -94,7 +92,6 @@ void DisplayApp::DisplayLogo(uint16_t color) {
|
|||
Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
|
||||
for (int i = 0; i < displayWidth; i++) {
|
||||
rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
|
||||
ulTaskNotifyTake(pdTRUE, 500);
|
||||
lcd.DrawBuffer(0, i, displayWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), displayWidth * bytesPerPixel);
|
||||
}
|
||||
}
|
||||
|
@ -103,20 +100,15 @@ void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
|
|||
const uint8_t barHeight = 20;
|
||||
std::fill(displayBuffer, displayBuffer + (displayWidth * bytesPerPixel), color);
|
||||
for (int i = 0; i < barHeight; i++) {
|
||||
ulTaskNotifyTake(pdTRUE, 500);
|
||||
uint16_t barWidth = std::min(static_cast<float>(percent) * 2.4f, static_cast<float>(displayWidth));
|
||||
lcd.DrawBuffer(0, displayWidth - barHeight + i, barWidth, 1, reinterpret_cast<const uint8_t*>(displayBuffer), barWidth * bytesPerPixel);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayApp::PushMessage(Display::Messages msg) {
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken) {
|
||||
/* Actual macro used here is port specific. */
|
||||
// TODO : should I do something here?
|
||||
}
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
|
||||
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <drivers/SpiMaster.h>
|
||||
#include <bits/unique_ptr.h>
|
||||
#include <queue.h>
|
||||
#include "components/gfx/Gfx.h"
|
||||
#include "drivers/Cst816s.h"
|
||||
#include <drivers/Watchdog.h>
|
||||
#include <components/motor/MotorController.h>
|
||||
|
@ -19,6 +18,7 @@ namespace Pinetime {
|
|||
class St7789;
|
||||
class Cst816S;
|
||||
class Watchdog;
|
||||
class SpiNorFlash;
|
||||
}
|
||||
|
||||
namespace Controllers {
|
||||
|
@ -60,7 +60,8 @@ namespace Pinetime {
|
|||
Pinetime::Controllers::AlarmController& alarmController,
|
||||
Pinetime::Controllers::BrightnessController& brightnessController,
|
||||
Pinetime::Controllers::TouchHandler& touchHandler,
|
||||
Pinetime::Controllers::FS& filesystem);
|
||||
Pinetime::Controllers::FS& filesystem,
|
||||
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
|
||||
void Start();
|
||||
|
||||
void Start(Pinetime::System::BootErrors) {
|
||||
|
|
|
@ -152,10 +152,6 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
|
|||
void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
|
||||
uint16_t y1, y2, width, height = 0;
|
||||
|
||||
ulTaskNotifyTake(pdTRUE, 200);
|
||||
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
|
||||
// which cannot be set/clear during a transfer.
|
||||
|
||||
if ((scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
|
||||
writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
|
||||
} else if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
|
||||
|
@ -219,7 +215,6 @@ void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
|
|||
|
||||
if (height > 0) {
|
||||
lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast<const uint8_t*>(color_p), width * height * 2);
|
||||
ulTaskNotifyTake(pdTRUE, 100);
|
||||
}
|
||||
|
||||
uint16_t pixOffset = width * height;
|
||||
|
@ -253,6 +248,8 @@ void LittleVgl::SetNewTouchPoint(int16_t x, int16_t y, bool contact) {
|
|||
}
|
||||
}
|
||||
|
||||
// Cancel an ongoing tap
|
||||
// Signifies that LVGL should not handle the current tap
|
||||
void LittleVgl::CancelTap() {
|
||||
if (tapped) {
|
||||
isCancelled = true;
|
||||
|
@ -260,6 +257,13 @@ void LittleVgl::CancelTap() {
|
|||
}
|
||||
}
|
||||
|
||||
// Clear the current tapped state
|
||||
// Signifies that touch input processing is suspended
|
||||
void LittleVgl::ClearTouchState() {
|
||||
touchPoint = {-1, -1};
|
||||
tapped = false;
|
||||
}
|
||||
|
||||
bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
|
||||
ptr->point.x = touchPoint.x;
|
||||
ptr->point.y = touchPoint.y;
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Pinetime {
|
|||
void SetFullRefresh(FullRefreshDirections direction);
|
||||
void SetNewTouchPoint(int16_t x, int16_t y, bool contact);
|
||||
void CancelTap();
|
||||
void ClearTouchState();
|
||||
|
||||
bool GetFullRefresh() {
|
||||
bool returnValue = fullRefresh;
|
||||
|
|
|
@ -6,8 +6,8 @@ namespace Pinetime {
|
|||
namespace Display {
|
||||
enum class Messages : uint8_t {
|
||||
GoToSleep,
|
||||
GoToAOD,
|
||||
GoToRunning,
|
||||
UpdateDateTime,
|
||||
UpdateBleConnection,
|
||||
TouchEvent,
|
||||
ButtonPushed,
|
||||
|
@ -17,8 +17,9 @@ namespace Pinetime {
|
|||
NewNotification,
|
||||
TimerDone,
|
||||
BleFirmwareUpdateStarted,
|
||||
DimScreen,
|
||||
RestoreBrightness,
|
||||
// Resets the screen timeout timer when awake
|
||||
// Does nothing when asleep
|
||||
NotifyDeviceActivity,
|
||||
ShowPairingKey,
|
||||
AlarmTriggered,
|
||||
Chime,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Controllers.h"
|
||||
|
||||
#include "displayapp/screens/Alarm.h"
|
||||
#include "displayapp/screens/Dice.h"
|
||||
#include "displayapp/screens/Timer.h"
|
||||
#include "displayapp/screens/Twos.h"
|
||||
#include "displayapp/screens/Tile.h"
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace Pinetime {
|
|||
Metronome,
|
||||
Motion,
|
||||
Steps,
|
||||
Dice,
|
||||
Weather,
|
||||
PassKey,
|
||||
QuickSettings,
|
||||
Settings,
|
||||
|
@ -40,8 +42,7 @@ namespace Pinetime {
|
|||
SettingChimes,
|
||||
SettingShakeThreshold,
|
||||
SettingBluetooth,
|
||||
Error,
|
||||
Weather
|
||||
Error
|
||||
};
|
||||
|
||||
enum class WatchFace : uint8_t {
|
||||
|
@ -71,12 +72,7 @@ namespace Pinetime {
|
|||
static constexpr size_t Count = sizeof...(Ws);
|
||||
};
|
||||
|
||||
using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Digital,
|
||||
WatchFace::Analog,
|
||||
WatchFace::PineTimeStyle,
|
||||
WatchFace::Terminal,
|
||||
WatchFace::Infineat,
|
||||
WatchFace::CasioStyleG7710>;
|
||||
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;
|
||||
|
||||
static_assert(UserWatchFaceTypes::Count >= 1);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,35 @@
|
|||
if(DEFINED ENABLE_USERAPPS)
|
||||
set(USERAPP_TYPES ${ENABLE_USERAPPS} CACHE STRING "List of user apps to build into the firmware")
|
||||
else ()
|
||||
set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Alarm, Apps::Timer, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Paint, Apps::Paddle, Apps::Twos, Apps::Metronome" CACHE STRING "List of user apps to build into the firmware")
|
||||
set(DEFAULT_USER_APP_TYPES "Apps::StopWatch")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Alarm")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Timer")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Steps")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::HeartRate")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Music")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paint")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Paddle")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Twos")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
|
||||
set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
|
||||
#set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion")
|
||||
set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
|
||||
endif ()
|
||||
|
||||
if(DEFINED ENABLE_WATCHFACES)
|
||||
set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware")
|
||||
else()
|
||||
set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat")
|
||||
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
|
||||
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
|
||||
endif()
|
||||
|
||||
add_library(infinitime_apps INTERFACE)
|
||||
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
|
||||
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
|
||||
|
|
|
@ -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_14
|
||||
jetbrains_mono_extrabold_compressed lv_font_sys_48
|
||||
open_sans_light fontawesome_weathericons)
|
||||
find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
|
||||
|
@ -11,6 +11,7 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/jetbrains_mono_bold_20.c_M.patch
|
|||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
# FindPython3 module introduces with CMake 3.12
|
||||
# https://cmake.org/cmake/help/latest/module/FindPython3.html
|
||||
set(Python3_FIND_STRATEGY LOCATION) # https://discourse.cmake.org/t/find-package-python3-is-not-finding-the-correct-python/10563
|
||||
find_package(Python3 REQUIRED)
|
||||
else()
|
||||
set(Python3_EXECUTABLE "python")
|
||||
|
|
BIN
src/displayapp/fonts/NotoSerifCJKjp-VF.ttf
Normal file
|
@ -16,7 +16,7 @@
|
|||
- Define the new symbols in `src/displayapp/screens/Symbols.h`:
|
||||
|
||||
```
|
||||
static constexpr const char* newSymbol = "\xEF\x86\x85";
|
||||
static constexpr const char* newSymbol = "\xEF\x99\x81";
|
||||
```
|
||||
|
||||
### the config file format:
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
{
|
||||
"jetbrains_mono_14": {
|
||||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Regular.ttf",
|
||||
"range": "0x20-0x7e, 0x410-0x44f, 0xB0"
|
||||
},
|
||||
{
|
||||
"file": "NotoSerifCJKjp-VF.ttf",
|
||||
"symbols": "大雪"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
"size": 14
|
||||
},
|
||||
"jetbrains_mono_bold_20": {
|
||||
"sources": [
|
||||
{
|
||||
|
@ -7,7 +21,7 @@
|
|||
},
|
||||
{
|
||||
"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,
|
||||
|
@ -18,7 +32,7 @@
|
|||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Regular.ttf",
|
||||
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74"
|
||||
"range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -28,7 +42,7 @@
|
|||
"sources": [
|
||||
{
|
||||
"file": "JetBrainsMono-Light.ttf",
|
||||
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
|
||||
"range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
|
@ -54,6 +68,16 @@
|
|||
"bpp": 1,
|
||||
"size": 150
|
||||
},
|
||||
"noto_serif_cjk_14": {
|
||||
"sources": [
|
||||
{
|
||||
"file": "NotoSerifCJKjp-VF.ttf",
|
||||
"symbols": "大雪"
|
||||
}
|
||||
],
|
||||
"bpp": 1,
|
||||
"size": 14
|
||||
},
|
||||
"lv_font_sys_48": {
|
||||
"sources": [
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
|||
Controllers::Settings::ClockType clockType,
|
||||
System::SystemTask& systemTask,
|
||||
Controllers::MotorController& motorController)
|
||||
: alarmController {alarmController}, systemTask {systemTask}, motorController {motorController} {
|
||||
: alarmController {alarmController}, wakeLock(systemTask), motorController {motorController} {
|
||||
|
||||
hourCounter.Create();
|
||||
lv_obj_align(hourCounter.GetObject(), nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
|
||||
|
@ -117,7 +117,7 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
|||
|
||||
UpdateAlarmTime();
|
||||
|
||||
if (alarmController.State() == Controllers::AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
SetAlerting();
|
||||
} else {
|
||||
SetSwitchState(LV_ANIM_OFF);
|
||||
|
@ -125,14 +125,15 @@ Alarm::Alarm(Controllers::AlarmController& alarmController,
|
|||
}
|
||||
|
||||
Alarm::~Alarm() {
|
||||
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
StopAlerting();
|
||||
}
|
||||
lv_obj_clean(lv_scr_act());
|
||||
alarmController.SaveAlarm();
|
||||
}
|
||||
|
||||
void Alarm::DisableAlarm() {
|
||||
if (alarmController.State() == AlarmController::AlarmState::Set) {
|
||||
if (alarmController.IsEnabled()) {
|
||||
alarmController.DisableAlarm();
|
||||
lv_switch_off(enableSwitch, LV_ANIM_ON);
|
||||
}
|
||||
|
@ -172,7 +173,7 @@ bool Alarm::OnButtonPushed() {
|
|||
HideInfo();
|
||||
return true;
|
||||
}
|
||||
if (alarmController.State() == AlarmController::AlarmState::Alerting) {
|
||||
if (alarmController.IsAlerting()) {
|
||||
StopAlerting();
|
||||
return true;
|
||||
}
|
||||
|
@ -181,7 +182,7 @@ bool Alarm::OnButtonPushed() {
|
|||
|
||||
bool Alarm::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
// Don't allow closing the screen by swiping while the alarm is alerting
|
||||
return alarmController.State() == AlarmController::AlarmState::Alerting && event == TouchEvents::SwipeDown;
|
||||
return alarmController.IsAlerting() && event == TouchEvents::SwipeDown;
|
||||
}
|
||||
|
||||
void Alarm::OnValueChanged() {
|
||||
|
@ -205,7 +206,7 @@ void Alarm::SetAlerting() {
|
|||
lv_obj_set_hidden(btnStop, false);
|
||||
taskStopAlarm = lv_task_create(StopAlarmTaskCallback, pdMS_TO_TICKS(60 * 1000), LV_TASK_PRIO_MID, this);
|
||||
motorController.StartRinging();
|
||||
systemTask.PushMessage(System::Messages::DisableSleeping);
|
||||
wakeLock.Lock();
|
||||
}
|
||||
|
||||
void Alarm::StopAlerting() {
|
||||
|
@ -216,21 +217,16 @@ void Alarm::StopAlerting() {
|
|||
lv_task_del(taskStopAlarm);
|
||||
taskStopAlarm = nullptr;
|
||||
}
|
||||
systemTask.PushMessage(System::Messages::EnableSleeping);
|
||||
wakeLock.Release();
|
||||
lv_obj_set_hidden(enableSwitch, false);
|
||||
lv_obj_set_hidden(btnStop, true);
|
||||
}
|
||||
|
||||
void Alarm::SetSwitchState(lv_anim_enable_t anim) {
|
||||
switch (alarmController.State()) {
|
||||
case AlarmController::AlarmState::Set:
|
||||
if (alarmController.IsEnabled()) {
|
||||
lv_switch_on(enableSwitch, anim);
|
||||
break;
|
||||
case AlarmController::AlarmState::Not_Set:
|
||||
} else {
|
||||
lv_switch_off(enableSwitch, anim);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +243,7 @@ void Alarm::ShowInfo() {
|
|||
txtMessage = lv_label_create(btnMessage, nullptr);
|
||||
lv_obj_set_style_local_bg_color(btnMessage, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_NAVY);
|
||||
|
||||
if (alarmController.State() == AlarmController::AlarmState::Set) {
|
||||
if (alarmController.IsEnabled()) {
|
||||
auto timeToAlarm = alarmController.SecondsToAlarm();
|
||||
|
||||
auto daysToAlarm = timeToAlarm / 86400;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "displayapp/screens/Screen.h"
|
||||
#include "displayapp/widgets/Counter.h"
|
||||
#include "displayapp/Controllers.h"
|
||||
#include "systemtask/WakeLock.h"
|
||||
#include "Symbols.h"
|
||||
|
||||
namespace Pinetime {
|
||||
|
@ -43,7 +44,7 @@ namespace Pinetime {
|
|||
|
||||
private:
|
||||
Controllers::AlarmController& alarmController;
|
||||
System::SystemTask& systemTask;
|
||||
System::WakeLock wakeLock;
|
||||
Controllers::MotorController& motorController;
|
||||
|
||||
lv_obj_t *btnStop, *txtStop, *btnRecur, *txtRecur, *btnInfo, *enableSwitch;
|
||||
|
@ -69,7 +70,7 @@ namespace Pinetime {
|
|||
template <>
|
||||
struct AppTraits<Apps::Alarm> {
|
||||
static constexpr Apps app = Apps::Alarm;
|
||||
static constexpr const char* icon = Screens::Symbols::clock;
|
||||
static constexpr const char* icon = Screens::Symbols::bell;
|
||||
|
||||
static Screens::Screen* Create(AppControllers& controllers) {
|
||||
return new Screens::Alarm(controllers.alarmController,
|
||||
|
|